From e27010f9c41b3166cf6af24c11c0595da3c73268 Mon Sep 17 00:00:00 2001 From: Stefan Feilmeier Date: Wed, 2 Aug 2023 12:50:57 +0200 Subject: [PATCH 01/32] Start development of version 2023.9.0-SNAPSHOT --- io.openems.common/src/io/openems/common/OpenemsConstants.java | 4 ++-- ui/package-lock.json | 4 ++-- ui/package.json | 2 +- ui/src/app/user/user.component.html | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/io.openems.common/src/io/openems/common/OpenemsConstants.java b/io.openems.common/src/io/openems/common/OpenemsConstants.java index 355e440ecb4..5bd87b2c4ec 100644 --- a/io.openems.common/src/io/openems/common/OpenemsConstants.java +++ b/io.openems.common/src/io/openems/common/OpenemsConstants.java @@ -29,7 +29,7 @@ public class OpenemsConstants { *

* This is the month of the release. */ - public static final short VERSION_MINOR = 8; + public static final short VERSION_MINOR = 9; /** * The patch version of OpenEMS. @@ -43,7 +43,7 @@ public class OpenemsConstants { /** * The additional version string. */ - public static final String VERSION_STRING = ""; + public static final String VERSION_STRING = "SNAPSHOT"; /** * The complete version as a SemanticVersion. diff --git a/ui/package-lock.json b/ui/package-lock.json index 41fc3ff3641..5c51e20a8f1 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -1,12 +1,12 @@ { "name": "openems-ui", - "version": "2023.8.0", + "version": "2023.9.0-SNAPSHOT", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "openems-ui", - "version": "2023.8.0", + "version": "2023.9.0-SNAPSHOT", "license": "AGPL-3.0", "dependencies": { "@angular/animations": "~15.2.9", diff --git a/ui/package.json b/ui/package.json index 7fe89470aad..74794a73533 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "openems-ui", - "version": "2023.8.0", + "version": "2023.9.0-SNAPSHOT", "license": "AGPL-3.0", "private": true, "dependencies": { diff --git a/ui/src/app/user/user.component.html b/ui/src/app/user/user.component.html index 1c7626406c9..614d04a97f4 100644 --- a/ui/src/app/user/user.component.html +++ b/ui/src/app/user/user.component.html @@ -105,7 +105,7 @@

About.build

From a37da1ae03b287ddcb7ca885862f4f7e77243e73 Mon Sep 17 00:00:00 2001 From: Stefan Feilmeier Date: Wed, 2 Aug 2023 13:25:55 +0200 Subject: [PATCH 02/32] Modbus Bridge: optimize execution of Tasks (#1976) This Pull-Request represents a complete rewrite of the Modbus Bridge. For information is available at: - https://github.com/OpenEMS/openems/blob/develop/io.openems.edge.bridge.modbus/readme.adoc - https://github.com/OpenEMS/openems/blob/develop/io.openems.edge.bridge.modbus/doc/statemachine.md - https://community.openems.io/t/asking-for-feedback-new-modbusworker-implementation/1747/2 **Breaking changes:** - Some names of Interfaces and Classes have changed. - It might be required in your individual implementation of a ModbusComponent, to explicitely import `com.ghgande.j2mod` in the bnd file # Implementation details OpenEMS Components that use Modbus communication, must implement the `ModbusComponent` interface and provide a `ModbusProtocol`. A protocol uses the notion of a `Task` to define an individual Modbus Read or Write request that can cover multiple Modbus Registers or Coils depending on the Modbus function code. It is possible to add or remove tasks to/from a protocol at runtime or to change the execution `Priority`. The Modbus Bridge (`Bridge Modbus/RTU Serial` or `Bridge Modbus/TCP`) collects all protocols and manages the execution of Tasks. ### Execution of Modbus Tasks Execution of Modbus Tasks is managed by the `ModbusWorker`. It... * executes Write-Tasks as early as possible (directly after the EXECUTE_WRITE event) * executes Read-Tasks as late as possible to have values available exactly when they are needed (i.e. just before the BEFORE_PROCESS_IMAGE event). To achieve this, the ModbusWorker evaluates all execution times and 'learns' an ideal delay time, that is applied on every Cycle - the 'CycleDelay' * handles defective ModbusComponents (i.e. ones where tasks have repeatedly failed) and delays reading from/writing to those components in order to avoid having defective components block the entire communication bus. Maximum delay is 5 minutes for read from defective components. ModbusComponents can trigger a retry from a defective Component by calling the `retryModbusCommunication()` method. ### Priority Read-Tasks can have two different priorities, that are defined in the ModbusProtocol definition: * `HIGH`: the task is executed once every Cycle * `LOW`: only one task of all defined LOW priority tasks of all components registered on the same bridge is executed per Cycle Write-Tasks always have `HIGH` priority, i.e. a set-point is always executed as-soon-as-possible - as long as the Component is not marked as defective ### Channels Each Modbus Bridge provides Channels for more detailed information: * `CycleTimeIsTooShort`: the configured global Cycle-Time is too short to execute all planned tasks in one Cycle * `CycleDelay`: see 'CycleDelay' in the 'ModbusWorker' description above ### Logging Often it is useful to print detailed logging information on the Console for debugging purposes. Logging can be enabled on Task level in the definition of the ModbusProtocol by adding `.debug()` or globally per Modbus Bridge via the `LogVerbosity` configuration parameter: * `NONE`: Show no logs * `DEBUG_LOG`: Shows basic logging information via the Controller.Debug.Log * `READS_AND_WRITES`: Show logs for all read and write requests * `READS_AND_WRITES_VERBOSE`: Show logs for all read and write requests, including actual hex or binary values of request and response * `READS_AND_WRITES_DURATION`: Show logs for all read and write requests, including actual duration time per request * `READS_AND_WRITES_DURATION_TRACE_EVENTS`: Show logs for all read and write requests, including actual duration time per request & trace the internal Event-based State-Machine The log level via configuration parameter may be changed at any time during runtime without side-effects on the communication. --- io.openems.edge.battery.bmw/bnd.bnd | 4 +- io.openems.edge.battery.bydcommercial/bnd.bnd | 4 +- .../bnd.bnd | 4 +- io.openems.edge.battery.fenecon.home/bnd.bnd | 4 +- .../fenecon/home/BatteryFeneconHome.java | 3 +- .../fenecon/home/BatteryFeneconHomeImpl.java | 6 +- .../fenecon/home/statemachine/Context.java | 4 + .../home/statemachine/GoRunningHandler.java | 2 + io.openems.edge.battery.soltaro/bnd.bnd | 4 +- .../BatterySoltaroClusterVersionBImpl.java | 10 +- .../soltaro/cluster/versionb/SingleRack.java | 25 +- .../BatterySoltaroClusterVersionCImpl.java | 10 +- .../BatterySoltaroSingleRackVersionBImpl.java | 6 +- .../BatterySoltaroSingleRackVersionCImpl.java | 10 +- .../bnd.bnd | 4 +- .../bnd.bnd | 4 +- .../doc/statemachine.md | 23 ++ io.openems.edge.bridge.modbus/readme.adoc | 47 ++- .../bridge/modbus/BridgeModbusSerialImpl.java | 43 ++- .../bridge/modbus/BridgeModbusTcpImpl.java | 23 +- .../modbus/api/AbstractModbusBridge.java | 96 +++-- .../api/AbstractOpenemsModbusComponent.java | 47 ++- .../edge/bridge/modbus/api/BridgeModbus.java | 42 ++- .../edge/bridge/modbus/api/LogVerbosity.java | 25 +- .../bridge/modbus/api/ModbusComponent.java | 17 +- .../bridge/modbus/api/ModbusProtocol.java | 80 +--- .../edge/bridge/modbus/api/ModbusUtils.java | 61 +++- .../element/AbstractDoubleWordElement.java | 16 +- .../api/element/AbstractModbusElement.java | 59 +-- .../AbstractModbusRegisterElement.java | 15 +- .../element/AbstractQuadrupleWordElement.java | 8 +- .../api/element/AbstractWordElement.java | 6 +- .../modbus/api/element/CoilElement.java | 7 +- .../modbus/api/element/DummyCoilElement.java | 8 +- .../api/element/DummyRegisterElement.java | 12 +- .../modbus/api/element/ModbusElement.java | 17 +- .../modbus/api/element/ModbusReadElement.java | 15 - .../api/element/ModbusRegisterElement.java | 2 + .../task/AbstractReadDigitalInputsTask.java | 60 +-- .../task/AbstractReadInputRegistersTask.java | 29 +- .../modbus/api/task/AbstractReadTask.java | 202 ++++++----- .../bridge/modbus/api/task/AbstractTask.java | 333 +++++++++++++---- .../modbus/api/task/AbstractWriteTask.java | 86 +++++ .../api/task/FC16WriteRegistersTask.java | 175 +++++---- .../modbus/api/task/FC1ReadCoilsTask.java | 29 +- .../modbus/api/task/FC2ReadInputsTask.java | 30 +- .../modbus/api/task/FC3ReadRegistersTask.java | 29 +- .../api/task/FC4ReadInputRegistersTask.java | 29 +- .../modbus/api/task/FC5WriteCoilTask.java | 75 +--- .../modbus/api/task/FC6WriteRegisterTask.java | 98 ++--- .../edge/bridge/modbus/api/task/ReadTask.java | 3 +- .../edge/bridge/modbus/api/task/Task.java | 43 +-- .../edge/bridge/modbus/api/task/Utils.java | 96 ----- .../edge/bridge/modbus/api/task/WaitTask.java | 110 ++++-- .../bridge/modbus/api/task/WriteTask.java | 14 +- .../modbus/api/worker/ModbusWorker.java | 341 ++++++------------ .../api/worker/internal/CycleTasks.java | 71 ++++ .../worker/internal/CycleTasksManager.java | 222 ++++++++++++ .../worker/internal/DefectiveComponents.java | 116 ++++++ .../api/worker/internal/TasksSupplier.java | 20 + .../worker/internal/TasksSupplierImpl.java | 130 +++++++ .../api/worker/internal/WaitDelayHandler.java | 209 +++++++++++ .../AbstractOpenemsSunSpecComponent.java | 22 +- .../bridge/modbus/sunspec/SunSpecPoint.java | 2 +- .../bridge/modbus/test/DummyModbusBridge.java | 33 +- .../bridge/modbus/api/element/ModbusTest.java | 18 +- .../AbstractReadDigitalInputsTaskTest.java | 27 ++ .../api/task/FC16WriteRegistersTaskTest.java | 73 ++++ .../modbus/api/task/FC1ReadCoilsTaskTest.java | 30 ++ .../api/task/FC2ReadInputsTaskTest.java | 30 ++ .../api/task/FC3ReadRegistersTaskTest.java | 31 ++ .../task/FC4ReadInputRegistersTaskTest.java | 30 ++ .../modbus/api/task/FC5WriteCoilTaskTest.java | 29 ++ .../api/task/FC6WriteRegisterTaskTest.java | 29 ++ .../bridge/modbus/api/task/UtilsTest.java | 34 -- .../modbus/api/worker/AbstractDummyTask.java | 70 ++++ .../modbus/api/worker/DummyReadTask.java | 24 ++ .../modbus/api/worker/DummyWriteTask.java | 22 ++ .../internal/CycleTasksManagerTest.java | 180 +++++++++ .../internal/DefectiveComponentsTest.java | 51 +++ .../worker/internal/DummyTasksSupplier.java | 35 ++ .../api/worker/internal/FakeTicker.java | 97 +++++ .../internal/TasksSupplierImplTest.java | 106 ++++++ .../worker/internal/WaitDelayHandlerTest.java | 47 +++ .../common/taskmanager/MetaTasksManager.java | 121 ------- .../io/openems/edge/common/type/Tuple.java | 4 + io.openems.edge.edge2edge/bnd.bnd | 4 +- .../edge2edge/common/AbstractEdge2Edge.java | 22 +- io.openems.edge.ess.adstec.storaxe/bnd.bnd | 4 +- io.openems.edge.ess.byd.container/bnd.bnd | 4 +- .../bnd.bnd | 4 +- io.openems.edge.ess.mr.gridcon/bnd.bnd | 3 +- .../edge/ess/mr/gridcon/GridconPcsImpl.java | 2 +- .../onoffgrid/DecisionTableConditionImpl.java | 6 +- io.openems.edge.ess.sma/bnd.bnd | 4 +- io.openems.edge.evcs.webasto.next/bnd.bnd | 4 +- io.openems.edge.evcs.webasto.unite/bnd.bnd | 4 +- io.openems.edge.fenecon.dess/bnd.bnd | 4 +- io.openems.edge.fenecon.mini/bnd.bnd | 4 +- io.openems.edge.fenecon.pro/bnd.bnd | 4 +- io.openems.edge.goodwe/bnd.bnd | 4 +- .../edge/goodwe/common/AbstractGoodWe.java | 2 +- .../goodwe/gridmeter/GoodWeGridMeterImpl.java | 2 - io.openems.edge.io.kmtronic/bnd.bnd | 4 +- io.openems.edge.io.wago/bnd.bnd | 4 +- .../src/io/openems/edge/wago/IoWagoImpl.java | 5 +- .../io/openems/edge/wago/IoWagoImplTest.java | 62 +--- io.openems.edge.io.weidmueller/bnd.bnd | 4 +- io.openems.edge.meter.artemes.am2/bnd.bnd | 4 +- io.openems.edge.meter.bcontrol.em300/bnd.bnd | 4 +- io.openems.edge.meter.bgetech/bnd.bnd | 4 +- .../bnd.bnd | 4 +- io.openems.edge.meter.janitza/bnd.bnd | 4 +- io.openems.edge.meter.kdk/bnd.bnd | 4 +- .../bnd.bnd | 4 +- io.openems.edge.meter.phoenixcontact/bnd.bnd | 4 +- io.openems.edge.meter.plexlog/bnd.bnd | 4 +- io.openems.edge.meter.pqplus/bnd.bnd | 4 +- .../bnd.bnd | 4 +- io.openems.edge.meter.siemens/bnd.bnd | 4 +- io.openems.edge.meter.sma.shm20/bnd.bnd | 4 +- io.openems.edge.meter.socomec/bnd.bnd | 4 +- io.openems.edge.meter.weidmueller/bnd.bnd | 4 +- io.openems.edge.meter.ziehl/bnd.bnd | 4 +- io.openems.edge.pvinverter.solarlog/bnd.bnd | 4 +- .../simulator/modbus/SimulatorModbusImpl.java | 5 + 126 files changed, 3150 insertions(+), 1551 deletions(-) create mode 100644 io.openems.edge.bridge.modbus/doc/statemachine.md delete mode 100644 io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/ModbusReadElement.java create mode 100644 io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractWriteTask.java delete mode 100644 io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/Utils.java create mode 100644 io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasks.java create mode 100644 io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManager.java create mode 100644 io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponents.java create mode 100644 io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplier.java create mode 100644 io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImpl.java create mode 100644 io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/WaitDelayHandler.java create mode 100644 io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/AbstractReadDigitalInputsTaskTest.java create mode 100644 io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC16WriteRegistersTaskTest.java create mode 100644 io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC1ReadCoilsTaskTest.java create mode 100644 io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC2ReadInputsTaskTest.java create mode 100644 io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC3ReadRegistersTaskTest.java create mode 100644 io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC4ReadInputRegistersTaskTest.java create mode 100644 io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC5WriteCoilTaskTest.java create mode 100644 io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC6WriteRegisterTaskTest.java delete mode 100644 io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/UtilsTest.java create mode 100644 io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/AbstractDummyTask.java create mode 100644 io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/DummyReadTask.java create mode 100644 io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/DummyWriteTask.java create mode 100644 io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManagerTest.java create mode 100644 io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponentsTest.java create mode 100644 io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/DummyTasksSupplier.java create mode 100644 io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/FakeTicker.java create mode 100644 io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImplTest.java create mode 100644 io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/WaitDelayHandlerTest.java delete mode 100644 io.openems.edge.common/src/io/openems/edge/common/taskmanager/MetaTasksManager.java create mode 100644 io.openems.edge.common/src/io/openems/edge/common/type/Tuple.java diff --git a/io.openems.edge.battery.bmw/bnd.bnd b/io.openems.edge.battery.bmw/bnd.bnd index 1cf2675297e..8f2cc726fe3 100644 --- a/io.openems.edge.battery.bmw/bnd.bnd +++ b/io.openems.edge.battery.bmw/bnd.bnd @@ -5,11 +5,11 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.battery.api,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common -testpath: \ - ${testpath},\ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.battery.bydcommercial/bnd.bnd b/io.openems.edge.battery.bydcommercial/bnd.bnd index 6c70fdb81cb..8b3afccd4d5 100644 --- a/io.openems.edge.battery.bydcommercial/bnd.bnd +++ b/io.openems.edge.battery.bydcommercial/bnd.bnd @@ -5,6 +5,7 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.battery.api,\ io.openems.edge.bridge.modbus,\ @@ -12,5 +13,4 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.ess.api -testpath: \ - ${testpath},\ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.battery.fenecon.commercial/bnd.bnd b/io.openems.edge.battery.fenecon.commercial/bnd.bnd index d8767d64fdc..702da51b88d 100644 --- a/io.openems.edge.battery.fenecon.commercial/bnd.bnd +++ b/io.openems.edge.battery.fenecon.commercial/bnd.bnd @@ -5,6 +5,7 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.battery.api,\ io.openems.edge.bridge.modbus,\ @@ -13,5 +14,4 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.io.api,\ -testpath: \ - ${testpath},\ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.battery.fenecon.home/bnd.bnd b/io.openems.edge.battery.fenecon.home/bnd.bnd index 28d2edb80b8..b55d9e98b5c 100644 --- a/io.openems.edge.battery.fenecon.home/bnd.bnd +++ b/io.openems.edge.battery.fenecon.home/bnd.bnd @@ -5,6 +5,7 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.battery.api,\ io.openems.edge.bridge.modbus,\ @@ -14,5 +15,4 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.io.api,\ -testpath: \ - ${testpath},\ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHome.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHome.java index 1f7d5cf4787..c0d4e0596c1 100644 --- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHome.java +++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHome.java @@ -6,6 +6,7 @@ import io.openems.common.types.OpenemsType; import io.openems.edge.battery.api.Battery; import io.openems.edge.battery.fenecon.home.statemachine.StateMachine.State; +import io.openems.edge.bridge.modbus.api.ModbusComponent; import io.openems.edge.common.channel.Channel; import io.openems.edge.common.channel.Doc; import io.openems.edge.common.channel.IntegerDoc; @@ -14,7 +15,7 @@ import io.openems.edge.common.startstop.StartStop; import io.openems.edge.common.startstop.StartStoppable; -public interface BatteryFeneconHome extends Battery, OpenemsComponent, StartStoppable { +public interface BatteryFeneconHome extends Battery, ModbusComponent, OpenemsComponent, StartStoppable { /** * Gets the Channel for {@link ChannelId#BMS_CONTROL}. diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImpl.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImpl.java index e8286ea19db..7507cf6feef 100644 --- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImpl.java +++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImpl.java @@ -39,9 +39,9 @@ import io.openems.edge.bridge.modbus.api.ElementToChannelConverter; import io.openems.edge.bridge.modbus.api.ModbusComponent; import io.openems.edge.bridge.modbus.api.ModbusProtocol; -import io.openems.edge.bridge.modbus.api.element.AbstractModbusElement; import io.openems.edge.bridge.modbus.api.element.BitsWordElement; import io.openems.edge.bridge.modbus.api.element.DummyRegisterElement; +import io.openems.edge.bridge.modbus.api.element.ModbusElement; import io.openems.edge.bridge.modbus.api.element.SignedWordElement; import io.openems.edge.bridge.modbus.api.element.UnsignedDoublewordElement; import io.openems.edge.bridge.modbus.api.element.UnsignedWordElement; @@ -738,8 +738,8 @@ private synchronized void initializeTowerModulesChannels(int numberOfTowers, int * for Cell-Voltages.Channel-IDs are like "TOWER_0_OFFSET_2_TEMPERATURE_003". * Channel-IDs are like "TOWER_0_OFFSET_2_VOLTAGE_003". */ - var ameVolt = new AbstractModbusElement[SENSORS_PER_MODULE]; - var ameTemp = new AbstractModbusElement[SENSORS_PER_MODULE]; + var ameVolt = new ModbusElement[SENSORS_PER_MODULE]; + var ameTemp = new ModbusElement[SENSORS_PER_MODULE]; for (var j = 0; j < SENSORS_PER_MODULE; j++) { { // Create Voltage Channel diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/Context.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/Context.java index acf1ee66386..8c1a0be885a 100644 --- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/Context.java +++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/Context.java @@ -23,4 +23,8 @@ protected boolean isBatteryStarted() { } return !isNotStarted.get(); } + + protected void retryModbusCommunication() { + this.getParent().retryModbusCommunication(); + } } \ No newline at end of file diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/GoRunningHandler.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/GoRunningHandler.java index 1b32d6da281..cd05856e1a7 100644 --- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/GoRunningHandler.java +++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/GoRunningHandler.java @@ -64,6 +64,8 @@ public State runAndGetNextState(Context context) throws OpenemsNamedException { } case FINISHED: + // Finished. Battery should have started up. + context.retryModbusCommunication(); return State.RUNNING; } diff --git a/io.openems.edge.battery.soltaro/bnd.bnd b/io.openems.edge.battery.soltaro/bnd.bnd index 6bfbde72850..102059e918b 100644 --- a/io.openems.edge.battery.soltaro/bnd.bnd +++ b/io.openems.edge.battery.soltaro/bnd.bnd @@ -5,6 +5,7 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.battery.api,\ io.openems.edge.bridge.modbus,\ @@ -13,5 +14,4 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.ess.api -testpath: \ - ${testpath},\ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionBImpl.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionBImpl.java index 7da90d483cd..be9e350ac41 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionBImpl.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionBImpl.java @@ -682,17 +682,17 @@ private int getAddressContactorControl(int addressOffsetRack) { return addressOffsetRack + OFFSET_CONTACTOR_CONTROL; } - protected final AbstractModbusElement map(io.openems.edge.common.channel.ChannelId channelId, - AbstractModbusElement element) { + protected final > T map(io.openems.edge.common.channel.ChannelId channelId, + T element) { return this.m(channelId, element); } - protected final AbstractModbusElement map(io.openems.edge.common.channel.ChannelId channelId, - AbstractModbusElement element, ElementToChannelConverter converter) { + protected final > T map(io.openems.edge.common.channel.ChannelId channelId, + T element, ElementToChannelConverter converter) { return this.m(channelId, element, converter); } - protected final AbstractModbusElement map(BitsWordElement bitsWordElement) { + protected final BitsWordElement map(BitsWordElement bitsWordElement) { return super.m(bitsWordElement); } diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/SingleRack.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/SingleRack.java index 443e5119881..3c31e64c69a 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/SingleRack.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/SingleRack.java @@ -6,7 +6,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Optional; @@ -16,8 +15,8 @@ import io.openems.common.types.OpenemsType; import io.openems.edge.battery.soltaro.common.enums.ChargeIndication; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; -import io.openems.edge.bridge.modbus.api.element.AbstractModbusElement; import io.openems.edge.bridge.modbus.api.element.BitsWordElement; +import io.openems.edge.bridge.modbus.api.element.ModbusElement; import io.openems.edge.bridge.modbus.api.element.SignedWordElement; import io.openems.edge.bridge.modbus.api.element.UnsignedWordElement; import io.openems.edge.bridge.modbus.api.task.FC3ReadRegistersTask; @@ -253,11 +252,11 @@ protected Collection getTasks() { // Cell voltages for (var i = 0; i < this.numberOfSlaves; i++) { - List> elements = new ArrayList<>(); + var elements = new ArrayList>(); for (var j = i * VOLTAGE_SENSORS_PER_MODULE; j < (i + 1) * VOLTAGE_SENSORS_PER_MODULE; j++) { var key = this.getSingleCellPrefix(j) + "_" + VOLTAGE; var uwe = this.getUnsignedWordElement(VOLTAGE_ADDRESS_OFFSET + j); - AbstractModbusElement ame = this.parent.map(this.channelIds.get(key), uwe); + var ame = this.parent.map(this.channelIds.get(key), uwe); elements.add(ame); } @@ -265,9 +264,10 @@ protected Collection getTasks() { var taskCount = elements.size() / maxElementsPerTask + 1; for (var x = 0; x < taskCount; x++) { - var subElements = elements.subList(x * maxElementsPerTask, - Math.min((x + 1) * maxElementsPerTask, elements.size())); - var taskElements = subElements.toArray(new AbstractModbusElement[0]); + var taskElements = elements + .subList(x * maxElementsPerTask, Math.min((x + 1) * maxElementsPerTask, elements.size())) // + .stream() // + .toArray(ModbusElement[]::new); tasks.add(new FC3ReadRegistersTask(taskElements[0].getStartAddress(), Priority.LOW, taskElements)); } @@ -275,12 +275,12 @@ protected Collection getTasks() { // Cell temperatures for (var i = 0; i < this.numberOfSlaves; i++) { - List> elements = new ArrayList<>(); + var elements = new ArrayList>(); for (var j = i * TEMPERATURE_SENSORS_PER_MODULE; j < (i + 1) * TEMPERATURE_SENSORS_PER_MODULE; j++) { var key = this.getSingleCellPrefix(j) + "_" + TEMPERATURE; var swe = this.getSignedWordElement(TEMPERATURE_ADDRESS_OFFSET + j); - AbstractModbusElement ame = this.parent.map(this.channelIds.get(key), swe); + var ame = this.parent.map(this.channelIds.get(key), swe); elements.add(ame); } @@ -288,9 +288,10 @@ protected Collection getTasks() { var taskCount = elements.size() / maxElementsPerTask + 1; for (var x = 0; x < taskCount; x++) { - var subElements = elements.subList(x * maxElementsPerTask, - Math.min((x + 1) * maxElementsPerTask, elements.size())); - var taskElements = subElements.toArray(new AbstractModbusElement[0]); + var taskElements = elements + .subList(x * maxElementsPerTask, Math.min((x + 1) * maxElementsPerTask, elements.size())) // + .stream() // + .toArray(ModbusElement[]::new); tasks.add(new FC3ReadRegistersTask(taskElements[0].getStartAddress(), Priority.LOW, taskElements)); } } diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImpl.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImpl.java index 4664cfc436c..ab31dc4ed41 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImpl.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImpl.java @@ -49,9 +49,9 @@ import io.openems.edge.bridge.modbus.api.ModbusComponent; import io.openems.edge.bridge.modbus.api.ModbusProtocol; import io.openems.edge.bridge.modbus.api.ModbusUtils; -import io.openems.edge.bridge.modbus.api.element.AbstractModbusElement; import io.openems.edge.bridge.modbus.api.element.BitsWordElement; import io.openems.edge.bridge.modbus.api.element.DummyRegisterElement; +import io.openems.edge.bridge.modbus.api.element.ModbusElement; import io.openems.edge.bridge.modbus.api.element.SignedWordElement; import io.openems.edge.bridge.modbus.api.element.UnsignedWordElement; import io.openems.edge.bridge.modbus.api.task.FC16WriteRegistersTask; @@ -386,7 +386,7 @@ private void updateRackChannels(Integer numberOfModules, TreeSet racks) th } // Consumer addCellChannels = type -> { for (var i = 0; i < numberOfModules; i++) { - var elements = new AbstractModbusElement[type.getSensorsPerModule()]; + var elements = new ModbusElement[type.getSensorsPerModule()]; for (var j = 0; j < type.getSensorsPerModule(); j++) { var sensorIndex = i * type.getSensorsPerModule() + j; var channelId = CellChannelFactory.create(r, type, sensorIndex); @@ -411,7 +411,7 @@ private void updateRackChannels(Integer numberOfModules, TreeSet racks) th // WARN_LEVEL_Pre Alarm (Pre Alarm configuration registers RW) { - AbstractModbusElement[] elements = { + ModbusElement[] elements = { m(this.createChannelId(r, RackChannel.PRE_ALARM_CELL_OVER_VOLTAGE_ALARM), new UnsignedWordElement(r.offset + 0x080)), // m(this.createChannelId(r, RackChannel.PRE_ALARM_CELL_OVER_VOLTAGE_RECOVER), @@ -486,7 +486,7 @@ private void updateRackChannels(Integer numberOfModules, TreeSet racks) th // WARN_LEVEL1 (Level1 warning registers RW) { - AbstractModbusElement[] elements = { + ModbusElement[] elements = { m(this.createChannelId(r, RackChannel.LEVEL1_CELL_OVER_VOLTAGE_PROTECTION), new UnsignedWordElement(r.offset + 0x040)), // m(this.createChannelId(r, RackChannel.LEVEL1_CELL_OVER_VOLTAGE_RECOVER), @@ -561,7 +561,7 @@ private void updateRackChannels(Integer numberOfModules, TreeSet racks) th // WARN_LEVEL2 (Level2 Protection registers RW) { - AbstractModbusElement[] elements = { + ModbusElement[] elements = { m(this.createChannelId(r, RackChannel.LEVEL2_CELL_OVER_VOLTAGE_PROTECTION), new UnsignedWordElement(r.offset + 0x400)), // m(this.createChannelId(r, RackChannel.LEVEL2_CELL_OVER_VOLTAGE_RECOVER), diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionBImpl.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionBImpl.java index 52a629b4b12..e45f61768fe 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionBImpl.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionBImpl.java @@ -45,9 +45,9 @@ import io.openems.edge.bridge.modbus.api.ModbusComponent; import io.openems.edge.bridge.modbus.api.ModbusProtocol; import io.openems.edge.bridge.modbus.api.ModbusUtils; -import io.openems.edge.bridge.modbus.api.element.AbstractModbusElement; import io.openems.edge.bridge.modbus.api.element.BitsWordElement; import io.openems.edge.bridge.modbus.api.element.DummyRegisterElement; +import io.openems.edge.bridge.modbus.api.element.ModbusElement; import io.openems.edge.bridge.modbus.api.element.SignedWordElement; import io.openems.edge.bridge.modbus.api.element.UnsignedWordElement; import io.openems.edge.bridge.modbus.api.task.FC16WriteRegistersTask; @@ -966,8 +966,8 @@ private void calculateCapacity(Integer numberOfModules) { private void createDynamicChannels(int numberOfModules) { try { for (var i = 0; i < numberOfModules; i++) { - var ameVolt = new AbstractModbusElement[SENSORS_PER_MODULE]; - var ameTemp = new AbstractModbusElement[SENSORS_PER_MODULE]; + var ameVolt = new ModbusElement[SENSORS_PER_MODULE]; + var ameTemp = new ModbusElement[SENSORS_PER_MODULE]; for (var j = 0; j < SENSORS_PER_MODULE; j++) { var sensor = i * SENSORS_PER_MODULE + j; { diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImpl.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImpl.java index d0269b2d4e7..fdfc489c9ba 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImpl.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImpl.java @@ -42,9 +42,9 @@ import io.openems.edge.bridge.modbus.api.ModbusComponent; import io.openems.edge.bridge.modbus.api.ModbusProtocol; import io.openems.edge.bridge.modbus.api.ModbusUtils; -import io.openems.edge.bridge.modbus.api.element.AbstractModbusElement; import io.openems.edge.bridge.modbus.api.element.BitsWordElement; import io.openems.edge.bridge.modbus.api.element.DummyRegisterElement; +import io.openems.edge.bridge.modbus.api.element.ModbusElement; import io.openems.edge.bridge.modbus.api.element.SignedWordElement; import io.openems.edge.bridge.modbus.api.element.UnsignedDoublewordElement; import io.openems.edge.bridge.modbus.api.element.UnsignedWordElement; @@ -492,7 +492,7 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException { .bit(12, BatterySoltaroSingleRackVersionC.ChannelId.SLAVE_BMS_INIT)// ))); // { - AbstractModbusElement[] elements = { + ModbusElement[] elements = { m(BatterySoltaroSingleRackVersionC.ChannelId.PRE_ALARM_CELL_OVER_VOLTAGE_ALARM, new UnsignedWordElement(0x2080)), // m(BatterySoltaroSingleRackVersionC.ChannelId.PRE_ALARM_CELL_OVER_VOLTAGE_RECOVER, @@ -565,7 +565,7 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException { // WARN_LEVEL1 (Level1 warning registers RW) { - AbstractModbusElement[] elements = { + ModbusElement[] elements = { m(BatterySoltaroSingleRackVersionC.ChannelId.LEVEL1_CELL_OVER_VOLTAGE_PROTECTION, new UnsignedWordElement(0x2040)), // m(BatterySoltaroSingleRackVersionC.ChannelId.LEVEL1_CELL_OVER_VOLTAGE_RECOVER, @@ -638,7 +638,7 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException { // WARN_LEVEL2 (Level2 Protection registers RW) { - AbstractModbusElement[] elements = { + ModbusElement[] elements = { m(BatterySoltaroSingleRackVersionC.ChannelId.LEVEL2_CELL_OVER_VOLTAGE_PROTECTION, new UnsignedWordElement(0x2400)), // m(BatterySoltaroSingleRackVersionC.ChannelId.LEVEL2_CELL_OVER_VOLTAGE_RECOVER, @@ -725,7 +725,7 @@ void createCellVoltageAndTemperatureChannels(int numberOfModules) { */ Consumer addCellChannels = type -> { for (var i = 0; i < numberOfModules; i++) { - var elements = new AbstractModbusElement[type.getSensorsPerModule()]; + var elements = new ModbusElement[type.getSensorsPerModule()]; for (var j = 0; j < type.getSensorsPerModule(); j++) { var sensorIndex = i * type.getSensorsPerModule() + j; var channelId = CellChannelFactory.create(type, sensorIndex); diff --git a/io.openems.edge.batteryinverter.refu88k/bnd.bnd b/io.openems.edge.batteryinverter.refu88k/bnd.bnd index ca1a6ca0657..f209999cda9 100644 --- a/io.openems.edge.batteryinverter.refu88k/bnd.bnd +++ b/io.openems.edge.batteryinverter.refu88k/bnd.bnd @@ -5,6 +5,7 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.battery.api,\ io.openems.edge.batteryinverter.api,\ @@ -14,5 +15,4 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.timedata.api,\ -testpath: \ - ${testpath},\ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.batteryinverter.sinexcel/bnd.bnd b/io.openems.edge.batteryinverter.sinexcel/bnd.bnd index 3117583c4a8..7a297e7c55a 100644 --- a/io.openems.edge.batteryinverter.sinexcel/bnd.bnd +++ b/io.openems.edge.batteryinverter.sinexcel/bnd.bnd @@ -5,6 +5,7 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.battery.api,\ io.openems.edge.batteryinverter.api,\ @@ -14,5 +15,4 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.timedata.api,\ -testpath: \ - ${testpath},\ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/doc/statemachine.md b/io.openems.edge.bridge.modbus/doc/statemachine.md new file mode 100644 index 00000000000..bf810e9fb02 --- /dev/null +++ b/io.openems.edge.bridge.modbus/doc/statemachine.md @@ -0,0 +1,23 @@ +# State-Machine + +```mermaid +graph TD +ON_BEFORE_PROCESS_IMAGE>ON_BEFORE_PROCESS_IMAGE] +ON_EXECUTE_WRITE>ON_EXECUTE_WRITE] + +ON_EXECUTE_WRITE ==> WRITE +INITIAL_WAIT -->|sleep| READ_BEFORE_WRITE +INITIAL_WAIT -.- ON_EXECUTE_WRITE +READ_BEFORE_WRITE -->|read finished early| WAIT_FOR_WRITE +READ_BEFORE_WRITE -.- ON_EXECUTE_WRITE +WAIT_FOR_WRITE -.- ON_EXECUTE_WRITE + +WRITE -->|write finished| WAIT_BEFORE_READ +WAIT_BEFORE_READ -->|sleep| READ_AFTER_WRITE +READ_AFTER_WRITE -->|read finished| FINISHED + +ON_BEFORE_PROCESS_IMAGE ==> INITIAL_WAIT +FINISHED -.-o ON_BEFORE_PROCESS_IMAGE +``` + +View using Mermaid, e.g. https://mermaid-js.github.io/mermaid-live-editor \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/readme.adoc b/io.openems.edge.bridge.modbus/readme.adoc index 3131600326e..add356b345f 100644 --- a/io.openems.edge.bridge.modbus/readme.adoc +++ b/io.openems.edge.bridge.modbus/readme.adoc @@ -1,15 +1,48 @@ = Modbus -Modbus is a widely used standard for fieldbus connections. It is used by all kinds of hardware devices like photovoltaics inverters, electric meters, and so on. +Modbus is a widely used standard for fieldbus communications. It is used by all kinds of hardware devices like photovoltaics inverters, electric meters, and so on. == Modbus/TCP -link:https://github.com/OpenEMS/openems/blob/develop/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusTcpImpl.java[Modbus/TCP icon:code[]]:: -https://en.wikipedia.org/wiki/Modbus[Modbus/TCP icon:external-link[]] for fieldbus connections via TCP/IP network. -// TODO add configuration settings +https://github.com/OpenEMS/openems/blob/develop/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusTcpImpl.java[Bridge Modbus/RTU Serial] for fieldbus communication via TCP/IP network. == Modbus/RTU -link:https://github.com/OpenEMS/openems/blob/develop/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusSerialImpl.java[Modbus/Serial icon:code[]]:: -https://en.wikipedia.org/wiki/Modbus[Modbus/RTU icon:external-link[]] for fieldbus connections via RS485 serial bus. -// TODO add configuration settings \ No newline at end of file +https://github.com/OpenEMS/openems/blob/develop/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusSerialImpl.java[Bridge Modbus/TCP] for fieldbus communication via RS485 serial bus. + +== Implementation details + +OpenEMS Components that use Modbus communication, must implement the `ModbusComponent` interface and provide a `ModbusProtocol`. A protocol uses the notion of a `Task` to define an individual Modbus Read or Write request that can cover multiple Modbus Registers or Coils depending on the Modbus function code. It is possible to add or remove tasks to/from a protocol at runtime or to change the execution `Priority`. The Modbus Bridge (`Bridge Modbus/RTU Serial` or `Bridge Modbus/TCP`) collects all protocols and manages the execution of Tasks. + +=== Execution of Modbus Tasks + +Execution of Modbus Tasks is managed by the `ModbusWorker`. It... +- executes Write-Tasks as early as possible (directly after the EXECUTE_WRITE event) +- executes Read-Tasks as late as possible to have values available exactly when they are needed (i.e. just before the BEFORE_PROCESS_IMAGE event). To achieve this, the ModbusWorker evaluates all execution times and 'learns' an ideal delay time, that is applied on every Cycle - the 'CycleDelay' +- handles defective ModbusComponents (i.e. ones where tasks have repeatedly failed) and delays reading from/writing to those components in order to avoid having defective components block the entire communication bus. Maximum delay is 5 minutes for read from defective components. ModbusComponents can trigger a retry from a defective Component by calling the `retryModbusCommunication()` method. + +=== Priority + +Read-Tasks can have two different priorities, that are defined in the ModbusProtocol definition: +- `HIGH`: the task is executed once every Cycle +- `LOW`: only one task of all defined LOW priority tasks of all components registered on the same bridge is executed per Cycle +Write-Tasks always have `HIGH` priority, i.e. a set-point is always executed as-soon-as-possible - as long as the Component is not marked as defective + +=== Channels + +Each Modbus Bridge provides Channels for more detailed information: +- `CycleTimeIsTooShort`: the configured global Cycle-Time is too short to execute all planned tasks in one Cycle +- `CycleDelay`: see 'CycleDelay' in the 'ModbusWorker' description above + +=== Logging + +Often it is useful to print detailed logging information on the Console for debugging purposes. Logging can be enabled on Task level in the definition of the ModbusProtocol by adding `.debug()` or globally per Modbus Bridge via the `LogVerbosity` configuration parameter: + +- `NONE`: Show no logs +- `DEBUG_LOG`: Shows basic logging information via the Controller.Debug.Log +- `READS_AND_WRITES`: Show logs for all read and write requests +- `READS_AND_WRITES_VERBOSE`: Show logs for all read and write requests, including actual hex or binary values of request and response +- `READS_AND_WRITES_DURATION`: Show logs for all read and write requests, including actual duration time per request +- `READS_AND_WRITES_DURATION_TRACE_EVENTS`: Show logs for all read and write requests, including actual duration time per request & trace the internal Event-based State-Machine + +The log level via configuration parameter may be changed at any time during runtime without side-effects on the communication. \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusSerialImpl.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusSerialImpl.java index 4a8609384d5..dd13dd2c81f 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusSerialImpl.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusSerialImpl.java @@ -5,14 +5,18 @@ import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.ConfigurationPolicy; import org.osgi.service.component.annotations.Deactivate; -import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.Modified; import org.osgi.service.event.EventHandler; import org.osgi.service.event.propertytypes.EventTopics; import org.osgi.service.metatype.annotations.Designate; import com.ghgande.j2mod.modbus.Modbus; +import com.ghgande.j2mod.modbus.io.AbstractSerialTransportListener; import com.ghgande.j2mod.modbus.io.ModbusSerialTransaction; +import com.ghgande.j2mod.modbus.io.ModbusSerialTransport; import com.ghgande.j2mod.modbus.io.ModbusTransaction; +import com.ghgande.j2mod.modbus.msg.ModbusMessage; +import com.ghgande.j2mod.modbus.net.AbstractSerialConnection; import com.ghgande.j2mod.modbus.net.SerialConnection; import com.ghgande.j2mod.modbus.util.SerialParameters; @@ -23,7 +27,6 @@ import io.openems.edge.bridge.modbus.api.Parity; import io.openems.edge.bridge.modbus.api.Stopbit; import io.openems.edge.common.component.OpenemsComponent; -import io.openems.edge.common.cycle.Cycle; import io.openems.edge.common.event.EdgeEventConstants; /** @@ -43,9 +46,6 @@ public class BridgeModbusSerialImpl extends AbstractModbusBridge implements BridgeModbus, BridgeModbusSerial, OpenemsComponent, EventHandler { - @Reference - private Cycle cycle; - /** The configured Port-Name (e.g. '/dev/ttyUSB0' or 'COM3'). */ private String portName = ""; @@ -73,6 +73,18 @@ public BridgeModbusSerialImpl() { private void activate(ComponentContext context, ConfigSerial config) { super.activate(context, config.id(), config.alias(), config.enabled(), config.logVerbosity(), config.invalidateElementsAfterReadErrors()); + this.applyConfig(config); + } + + @Modified + private void modified(ComponentContext context, ConfigSerial config) { + super.modified(context, config.id(), config.alias(), config.enabled(), config.logVerbosity(), + config.invalidateElementsAfterReadErrors()); + this.applyConfig(config); + this.closeModbusConnection(); + } + + private void applyConfig(ConfigSerial config) { this.portName = config.portName(); this.baudrate = config.baudRate(); this.databits = config.databits(); @@ -86,11 +98,6 @@ protected void deactivate() { super.deactivate(); } - @Override - public Cycle getCycle() { - return this.cycle; - } - @Override public void closeModbusConnection() { if (this._connection != null) { @@ -131,7 +138,21 @@ private synchronized SerialConnection getModbusConnection() throws OpenemsExcept } catch (Exception e) { throw new OpenemsException("Connection via [" + this.portName + "] failed: " + e.getMessage()); } - this._connection.getModbusTransport().setTimeout(AbstractModbusBridge.DEFAULT_TIMEOUT); + + var transport = (ModbusSerialTransport) this._connection.getModbusTransport(); + transport.setTimeout(AbstractModbusBridge.DEFAULT_TIMEOUT); + + // Sometimes read after write happens too quickly and causes read errors. + // Add 1ms additional waiting time between write request and read response + transport.addListener(new AbstractSerialTransportListener() { + public void afterMessageWrite(AbstractSerialConnection port, ModbusMessage msg) { + try { + Thread.sleep(1); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }); } return this._connection; } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusTcpImpl.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusTcpImpl.java index 7375b09fcad..9cd9cd3dff3 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusTcpImpl.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusTcpImpl.java @@ -8,7 +8,7 @@ import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.ConfigurationPolicy; import org.osgi.service.component.annotations.Deactivate; -import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.Modified; import org.osgi.service.event.EventHandler; import org.osgi.service.event.propertytypes.EventTopics; import org.osgi.service.metatype.annotations.Designate; @@ -23,7 +23,6 @@ import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.BridgeModbusTcp; import io.openems.edge.common.component.OpenemsComponent; -import io.openems.edge.common.cycle.Cycle; import io.openems.edge.common.event.EdgeEventConstants; /** @@ -43,9 +42,6 @@ public class BridgeModbusTcpImpl extends AbstractModbusBridge implements BridgeModbus, BridgeModbusTcp, OpenemsComponent, EventHandler { - @Reference - private Cycle cycle; - /** The configured IP address. */ private InetAddress ipAddress = null; private int port; @@ -62,6 +58,18 @@ public BridgeModbusTcpImpl() { private void activate(ComponentContext context, ConfigTcp config) throws UnknownHostException { super.activate(context, config.id(), config.alias(), config.enabled(), config.logVerbosity(), config.invalidateElementsAfterReadErrors()); + this.applyConfig(config); + } + + @Modified + private void modified(ComponentContext context, ConfigTcp config) throws UnknownHostException { + super.modified(context, config.id(), config.alias(), config.enabled(), config.logVerbosity(), + config.invalidateElementsAfterReadErrors()); + this.applyConfig(config); + this.closeModbusConnection(); + } + + private void applyConfig(ConfigTcp config) { this.setIpAddress(InetAddressUtils.parseOrNull(config.ip())); this.port = config.port(); } @@ -72,11 +80,6 @@ protected void deactivate() { super.deactivate(); } - @Override - public Cycle getCycle() { - return this.cycle; - } - @Override public void closeModbusConnection() { if (this._connection != null) { diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractModbusBridge.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractModbusBridge.java index aaf1e04c340..52087943576 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractModbusBridge.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractModbusBridge.java @@ -1,16 +1,17 @@ package io.openems.edge.bridge.modbus.api; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; + import org.osgi.service.component.ComponentContext; import org.osgi.service.event.Event; import org.osgi.service.event.EventHandler; -import org.slf4j.Logger; import com.ghgande.j2mod.modbus.io.ModbusTransaction; import io.openems.common.exceptions.OpenemsException; import io.openems.edge.bridge.modbus.api.worker.ModbusWorker; import io.openems.edge.common.component.AbstractOpenemsComponent; -import io.openems.edge.common.cycle.Cycle; import io.openems.edge.common.event.EdgeEventConstants; /** @@ -34,16 +35,29 @@ public abstract class AbstractModbusBridge extends AbstractOpenemsComponent impl */ protected static final int DEFAULT_RETRIES = 1; - private LogVerbosity logVerbosity = LogVerbosity.NONE; + private final AtomicReference logVerbosity = new AtomicReference<>(LogVerbosity.NONE); private int invalidateElementsAfterReadErrors = 1; - protected final ModbusWorker worker = new ModbusWorker(this); + protected final ModbusWorker worker = new ModbusWorker( + // Execute Task + task -> task.execute(this), + // Invalidate ModbusElements + elements -> Stream.of(elements).forEach(e -> e.invalidate(this)), + // Set ChannelId.CYCLE_TIME_IS_TOO_SHORT + state -> this._setCycleTimeIsTooShort(state), + // Set ChannelId.CYCLE_DELAY + cycleDelay -> this._setCycleDelay(cycleDelay), + // LogVerbosity + this.logVerbosity // + ); protected AbstractModbusBridge(io.openems.edge.common.channel.ChannelId[] firstInitialChannelIds, io.openems.edge.common.channel.ChannelId[]... furtherInitialChannelIds) { super(firstInitialChannelIds, furtherInitialChannelIds); } + @Override + @Deprecated protected void activate(ComponentContext context, String id, String alias, boolean enabled) { throw new IllegalArgumentException("Use the other activate() method."); } @@ -51,9 +65,8 @@ protected void activate(ComponentContext context, String id, String alias, boole protected void activate(ComponentContext context, String id, String alias, boolean enabled, LogVerbosity logVerbosity, int invalidateElementsAfterReadErrors) { super.activate(context, id, alias, enabled); - this.logVerbosity = logVerbosity; - this.invalidateElementsAfterReadErrors = invalidateElementsAfterReadErrors; - if (this.isEnabled()) { + this.applyConfig(logVerbosity, invalidateElementsAfterReadErrors); + if (enabled) { this.worker.activate(id); } } @@ -65,12 +78,27 @@ protected void deactivate() { this.closeModbusConnection(); } - /** - * Gets the {@link Cycle}. - * - * @return the Cycle - */ - public abstract Cycle getCycle(); + @Override + @Deprecated + protected void modified(ComponentContext context, String id, String alias, boolean enabled) { + throw new IllegalArgumentException("Use the other modified() method."); + } + + protected void modified(ComponentContext context, String id, String alias, boolean enabled, + LogVerbosity logVerbosity, int invalidateElementsAfterReadErrors) { + super.modified(context, id, alias, enabled); + this.applyConfig(logVerbosity, invalidateElementsAfterReadErrors); + if (enabled) { + this.worker.modified(id); + } else { + this.worker.deactivate(); + } + } + + private void applyConfig(LogVerbosity logVerbosity, int invalidateElementsAfterReadErrors) { + this.logVerbosity.set(logVerbosity); + this.invalidateElementsAfterReadErrors = invalidateElementsAfterReadErrors; + } /** * Adds the protocol. @@ -81,6 +109,7 @@ protected void deactivate() { @Override public void addProtocol(String sourceId, ModbusProtocol protocol) { this.worker.addProtocol(sourceId, protocol); + this.retryModbusCommunication(sourceId); } /** @@ -108,6 +137,17 @@ public void handleEvent(Event event) { } } + @Override + public String debugLog() { + return switch (this.logVerbosity.get()) { + case NONE -> // + null; + case DEBUG_LOG, READS_AND_WRITES, READS_AND_WRITES_DURATION, READS_AND_WRITES_VERBOSE, + READS_AND_WRITES_DURATION_TRACE_EVENTS -> // + "CycleDelay:" + this.getCycleDelay().asString(); // + }; + } + /** * Creates a new Modbus Transaction on an open Modbus connection. * @@ -121,31 +161,27 @@ public void handleEvent(Event event) { */ public abstract void closeModbusConnection(); + /** + * Gets the configured {@link LogVerbosity}. + * + * @return {@link LogVerbosity} + */ public LogVerbosity getLogVerbosity() { - return this.logVerbosity; - } - - @Override - public void logInfo(Logger log, String message) { - super.logInfo(log, message); - } - - @Override - protected void logWarn(Logger log, String message) { - super.logWarn(log, message); - } - - @Override - protected void logError(Logger log, String message) { - super.logError(log, message); + return this.logVerbosity.get(); } /** - * After how many errors should a element be invalidated?. + * Gets the configured max number of errors before an element should be + * invalidated?. * * @return value */ public int invalidateElementsAfterReadErrors() { return this.invalidateElementsAfterReadErrors; } + + @Override + public void retryModbusCommunication(String sourceId) { + this.worker.retryModbusCommunication(sourceId); + } } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractOpenemsModbusComponent.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractOpenemsModbusComponent.java index bce15d7ac13..088eb694c73 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractOpenemsModbusComponent.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractOpenemsModbusComponent.java @@ -121,6 +121,7 @@ protected boolean activate(ComponentContext context, String id, String alias, bo var modbus = this.modbus.get(); if (this.isEnabled() && modbus != null) { modbus.addProtocol(this.id(), this.getModbusProtocol()); + modbus.retryModbusCommunication(this.id()); } return false; } @@ -164,6 +165,7 @@ protected boolean modified(ComponentContext context, String id, String alias, bo modbus.removeProtocol(this.id()); if (this.isEnabled() && modbus != null) { modbus.addProtocol(this.id(), this.getModbusProtocol()); + modbus.retryModbusCommunication(this.id()); } return false; } @@ -240,6 +242,12 @@ protected ModbusProtocol getModbusProtocol() throws OpenemsException { return this.protocol; } + @Override + public void retryModbusCommunication() { + var bridge = this.modbus.get(); + bridge.retryModbusCommunication(this.id()); + } + /** * Defines the Modbus protocol. * @@ -252,12 +260,12 @@ protected ModbusProtocol getModbusProtocol() throws OpenemsException { * Maps an Element to one or more ModbusChannels using converters, that convert * the value forward and backwards. */ - public class ChannelMapper> { + public class ChannelMapper> { - private final T element; + private final ELEMENT element; private final Map, ElementToChannelConverter> channelMaps = new HashMap<>(); - public ChannelMapper(T element) { + public ChannelMapper(ELEMENT element) { this.element = element; } @@ -268,7 +276,7 @@ public ChannelMapper(T element) { * @param converter the {@link ElementToChannelConverter} * @return the element parameter */ - public ChannelMapper m(io.openems.edge.common.channel.ChannelId channelId, + public ChannelMapper m(io.openems.edge.common.channel.ChannelId channelId, ElementToChannelConverter converter) { return this.m(channelId, converter, new ChannelMetaInfo(this.element.getStartAddress())); } @@ -282,7 +290,7 @@ public ChannelMapper m(io.openems.edge.common.channel.ChannelId channelId, * Channel * @return the element parameter */ - public ChannelMapper m(io.openems.edge.common.channel.ChannelId channelId, + public ChannelMapper m(io.openems.edge.common.channel.ChannelId channelId, ElementToChannelConverter converter, ChannelMetaInfo channelMetaInfo) { Channel channel = AbstractOpenemsModbusComponent.this.channel(channelId); channel.setMetaInfo(channelMetaInfo); @@ -301,7 +309,7 @@ public ChannelMapper m(io.openems.edge.common.channel.ChannelId channelId, * {@link WriteTask}s * @return the element parameter */ - public ChannelMapper m(io.openems.edge.common.channel.ChannelId channelId, + public ChannelMapper m(io.openems.edge.common.channel.ChannelId channelId, Function elementToChannel, Function channelToElement) { var converter = new ElementToChannelConverter(elementToChannel, channelToElement); return this.m(channelId, converter); @@ -312,7 +320,7 @@ public ChannelMapper m(io.openems.edge.common.channel.ChannelId channelId, * * @return the {@link ChannelMapper} */ - public T build() { + public ELEMENT build() { /* * Forward Element Read-Value to Channel */ @@ -336,15 +344,14 @@ public T build() { * Forward Channel Write-Value to Element */ this.channelMaps.keySet().forEach(channel -> { - if (channel instanceof WriteChannel) { - ((WriteChannel) channel).onSetNextWrite(value -> { + if (channel instanceof WriteChannel writeChannel) { + writeChannel.onSetNextWrite(value -> { // dynamically get the Converter; this allows the converter to be changed var converter = this.channelMaps.get(channel); var convertedValue = converter.channelToElement(value); - if (this.element instanceof ModbusRegisterElement) { + if (this.element instanceof ModbusRegisterElement registerElement) { try { - ((ModbusRegisterElement) this.element) - .setNextWriteValue(Optional.ofNullable(convertedValue)); + registerElement.setNextWriteValue(Optional.ofNullable(convertedValue)); } catch (OpenemsException | IllegalArgumentException e) { AbstractOpenemsModbusComponent.this.logWarn(AbstractOpenemsModbusComponent.this.log, "Unable to write to ModbusRegisterElement. " // @@ -357,9 +364,9 @@ public T build() { e.printStackTrace(); } } - } else if (this.element instanceof ModbusCoilElement) { + } else if (this.element instanceof ModbusCoilElement coilElement) { try { - ((ModbusCoilElement) this.element).setNextWriteValue( + coilElement.setNextWriteValue( Optional.ofNullable(TypeUtils.getAsType(OpenemsType.BOOLEAN, convertedValue))); } catch (OpenemsException e) { AbstractOpenemsModbusComponent.this.logWarn(AbstractOpenemsModbusComponent.this.log, @@ -387,7 +394,7 @@ public T build() { * @param element the ModbusElement * @return a {@link ChannelMapper} */ - protected final > ChannelMapper m(T element) { + protected final > ChannelMapper m(T element) { return new ChannelMapper<>(element); } @@ -397,7 +404,7 @@ protected final > ChannelMapper m(T elemen * @param bitsWordElement the ModbusElement * @return the element parameter */ - protected final AbstractModbusElement m(BitsWordElement bitsWordElement) { + protected final BitsWordElement m(BitsWordElement bitsWordElement) { return bitsWordElement; } @@ -409,7 +416,7 @@ protected final AbstractModbusElement m(BitsWordElement bitsWordElement) { * @param element the ModbusElement * @return the element parameter */ - protected final > T m(io.openems.edge.common.channel.ChannelId channelId, + protected final > T m(io.openems.edge.common.channel.ChannelId channelId, T element) { return this.m(channelId, element, DIRECT_1_TO_1); } @@ -424,7 +431,7 @@ protected final > T m(io.openems.edge.common. * Channel * @return the element parameter */ - protected final > T m(io.openems.edge.common.channel.ChannelId channelId, + protected final > T m(io.openems.edge.common.channel.ChannelId channelId, T element, ChannelMetaInfo channelMetaInfo) { return this.m(channelId, element, DIRECT_1_TO_1, channelMetaInfo); } @@ -439,7 +446,7 @@ protected final > T m(io.openems.edge.common. * @param converter the ElementToChannelConverter * @return the element parameter */ - protected final > T m(io.openems.edge.common.channel.ChannelId channelId, + protected final > T m(io.openems.edge.common.channel.ChannelId channelId, T element, ElementToChannelConverter converter) { return new ChannelMapper<>(element) // .m(channelId, converter) // @@ -458,7 +465,7 @@ protected final > T m(io.openems.edge.common. * Channel * @return the element parameter */ - protected final > T m(io.openems.edge.common.channel.ChannelId channelId, + protected final > T m(io.openems.edge.common.channel.ChannelId channelId, T element, ElementToChannelConverter converter, ChannelMetaInfo channelMetaInfo) { return new ChannelMapper<>(element) // .m(channelId, converter, channelMetaInfo) // diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/BridgeModbus.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/BridgeModbus.java index 60c2e365ef4..ea067ab45ec 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/BridgeModbus.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/BridgeModbus.java @@ -18,8 +18,12 @@ public interface BridgeModbus extends OpenemsComponent { public enum ChannelId implements io.openems.edge.common.channel.ChannelId { CYCLE_TIME_IS_TOO_SHORT(Doc.of(Level.INFO) // .debounce(10, Debounce.TRUE_VALUES_IN_A_ROW_TO_SET_TRUE)), // - EXECUTION_DURATION(Doc.of(OpenemsType.LONG) // - .unit(Unit.MILLISECONDS)); + /** + * Delay per Cycle before starting to execute Modbus Tasks. Global Cycle-Time + * can be reduced by this amount, without causing CYCLE_TIME_IS_TOO_SHORT. + */ + CYCLE_DELAY(Doc.of(OpenemsType.LONG) // + .unit(Unit.MILLISECONDS)); // private final Doc doc; @@ -63,32 +67,31 @@ public default void _setCycleTimeIsTooShort(boolean value) { } /** - * Gets the Channel for {@link ChannelId#EXECUTION_DURATION}. + * Gets the Channel for {@link ChannelId#CYCLE_DELAY}. * * @return the Channel */ - public default LongReadChannel getExecutionDurationChannel() { - return this.channel(ChannelId.EXECUTION_DURATION); + public default LongReadChannel getCycleDelayChannel() { + return this.channel(ChannelId.CYCLE_DELAY); } /** - * Gets the Execution Duration in [ms], see - * {@link ChannelId#EXECUTION_DURATION}. + * Gets the Cycle Delay in [ms], see {@link ChannelId#CYCLE_DELAY}. * * @return the Channel {@link Value} */ - public default Value getExecutionDuration() { - return this.getExecutionDurationChannel().value(); + public default Value getCycleDelay() { + return this.getCycleDelayChannel().value(); } /** - * Internal method to set the 'nextValue' on - * {@link ChannelId#EXECUTION_DURATION} Channel. + * Internal method to set the 'nextValue' on {@link ChannelId#CYCLE_DELAY} + * Channel. * * @param value the next value */ - public default void _setExecutionDuration(long value) { - this.getExecutionDurationChannel().setNextValue(value); + public default void _setCycleDelay(long value) { + this.getCycleDelayChannel().setNextValue(value); } /** @@ -106,4 +109,17 @@ public default void _setExecutionDuration(long value) { */ public void removeProtocol(String sourceId); + /** + * The Modbus Bridge marks defective Components, e.g. if there are communication + * failures. If a component is marked as defective, reads and writes are paused + * for an increasing waiting time. This method resets the waiting time, causing + * the Modbus Bridge to retry if a Component is not anymore defective. + * + *

+ * Use this method if there is good reason that a Modbus Component should be + * available again 'now', e.g. because it was turned on manually. + * + * @param sourceId the unique source identifier + */ + public void retryModbusCommunication(String sourceId); } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/LogVerbosity.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/LogVerbosity.java index 24857b56879..45ff724e2f8 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/LogVerbosity.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/LogVerbosity.java @@ -2,15 +2,30 @@ public enum LogVerbosity { /** - * Show now logs. + * Show no logs. */ NONE, /** - * Show logs for modbus write requests. + * Show basic information in Controller.Debug.Log. */ - WRITES, + DEBUG_LOG, /** - * Show verbose logs for modbus read and write requests. + * Show logs for all read and write requests. */ - READS_AND_WRITES; + READS_AND_WRITES, + /** + * Show logs for all read and write requests, including actual hex values of + * request and response. + */ + READS_AND_WRITES_VERBOSE, + /** + * Show logs for all read and write requests, including actual duration time per + * request. + */ + READS_AND_WRITES_DURATION, + /** + * Show logs for all read and write requests, including actual duration time per + * request & trace the internal Event-based State-Machine. + */ + READS_AND_WRITES_DURATION_TRACE_EVENTS; } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusComponent.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusComponent.java index 1a26260149b..ace3a2c4da4 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusComponent.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusComponent.java @@ -21,7 +21,7 @@ public interface ModbusComponent extends OpenemsComponent { public enum ChannelId implements io.openems.edge.common.channel.ChannelId { MODBUS_COMMUNICATION_FAILED(Doc.of(Level.FAULT) // - .debounce(10, Debounce.TRUE_VALUES_IN_A_ROW_TO_SET_TRUE) // + .debounce(10, Debounce.SAME_VALUES_IN_A_ROW_TO_CHANGE) // .text("Modbus Communication failed")) // ; @@ -52,8 +52,8 @@ public default StateChannel getModbusCommunicationFailedChannel() { * * @return the Channel {@link Value} */ - public default Value getModbusCommunicationFailed() { - return this.getModbusCommunicationFailedChannel().value(); + public default boolean getModbusCommunicationFailed() { + return this.getModbusCommunicationFailedChannel().value().get(); } /** @@ -66,4 +66,15 @@ public default void _setModbusCommunicationFailed(boolean value) { this.getModbusCommunicationFailedChannel().setNextValue(value); } + /** + * The Modbus Bridge marks defective Components, e.g. if there are communication + * failures. If a component is marked as defective, reads and writes are paused + * for an increasing waiting time. This method resets the waiting time, causing + * the Modbus Bridge to retry if a Component is not anymore defective. + * + *

+ * Use this method if there is good reason that a Modbus Component should be + * available again 'now', e.g. because it was turned on manually. + */ + public void retryModbusCommunication(); } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusProtocol.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusProtocol.java index 60c29912101..e4e922aead0 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusProtocol.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusProtocol.java @@ -1,10 +1,7 @@ package io.openems.edge.bridge.modbus.api; import io.openems.common.exceptions.OpenemsException; -import io.openems.edge.bridge.modbus.api.element.ModbusElement; -import io.openems.edge.bridge.modbus.api.task.ReadTask; import io.openems.edge.bridge.modbus.api.task.Task; -import io.openems.edge.bridge.modbus.api.task.WriteTask; import io.openems.edge.common.taskmanager.TasksManager; public class ModbusProtocol { @@ -17,12 +14,7 @@ public class ModbusProtocol { /** * TaskManager for ReadTasks. */ - private final TasksManager readTaskManager = new TasksManager<>(); - - /** - * TaskManager for WriteTasks. - */ - private final TasksManager writeTaskManager = new TasksManager<>(); + private final TasksManager taskManager = new TasksManager<>(); /** * Creates a new {@link ModbusProtocol}. @@ -33,9 +25,7 @@ public class ModbusProtocol { */ public ModbusProtocol(AbstractOpenemsModbusComponent parent, Task... tasks) throws OpenemsException { this.parent = parent; - for (Task task : tasks) { - this.addTask(task); - } + this.addTasks(tasks); } /** @@ -59,20 +49,8 @@ public synchronized void addTasks(Task... tasks) throws OpenemsException { public synchronized void addTask(Task task) throws OpenemsException { // add the the parent to the Task task.setParent(this.parent); - // check abstractTask for plausibility - this.checkTask(task); - /* - * fill writeTasks - */ - if (task instanceof WriteTask) { - this.writeTaskManager.addTask((WriteTask) task); - } - /* - * fill readTaskManager - */ - if (task instanceof ReadTask) { - this.readTaskManager.addTask((ReadTask) task); - } + // fill taskManager + this.taskManager.addTask(task); } /** @@ -81,12 +59,7 @@ public synchronized void addTask(Task task) throws OpenemsException { * @param task the task */ public synchronized void removeTask(Task task) { - if (task instanceof ReadTask) { - this.readTaskManager.removeTask((ReadTask) task); - } - if (task instanceof WriteTask) { - this.writeTaskManager.removeTask((WriteTask) task); - } + this.taskManager.removeTask(task); } /** @@ -94,50 +67,15 @@ public synchronized void removeTask(Task task) { * * @return a the TaskManager */ - public TasksManager getReadTasksManager() { - return this.readTaskManager; - } - - /** - * Gets the Write-Tasks Manager. - * - * @return a the TaskManager - */ - public TasksManager getWriteTasksManager() { - return this.writeTaskManager; - } - - /** - * Checks a {@link Task} for plausibility. - * - * @param task the Task that should be checked - * @throws OpenemsException on error - */ - private synchronized void checkTask(Task task) throws OpenemsException { - var address = task.getStartAddress(); - for (ModbusElement element : task.getElements()) { - if (element.getStartAddress() != address) { - throw new OpenemsException("Start address is wrong. It is [" + element.getStartAddress() + "/0x" - + Integer.toHexString(element.getStartAddress()) + "] but should be [" + address + "/0x" - + Integer.toHexString(address) + "]."); - } - address += element.getLength(); - // TODO: check BitElements - } + public TasksManager getTaskManager() { + return this.taskManager; } /** * Deactivate the {@link ModbusProtocol}. */ public void deactivate() { - var readTasks = this.readTaskManager.getTasks(); - for (ReadTask readTask : readTasks) { - readTask.deactivate(); - } - - var writeTasks = this.writeTaskManager.getTasks(); - for (WriteTask writeTask : writeTasks) { - writeTask.deactivate(); - } + this.taskManager.getTasks() // + .forEach(Task::deactivate); } } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusUtils.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusUtils.java index d0d8e203594..ac0672a6204 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusUtils.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusUtils.java @@ -3,12 +3,20 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import com.ghgande.j2mod.modbus.procimg.InputRegister; +import com.ghgande.j2mod.modbus.procimg.Register; import io.openems.common.exceptions.OpenemsException; import io.openems.edge.bridge.modbus.api.element.AbstractModbusElement; +import io.openems.edge.bridge.modbus.api.element.AbstractModbusRegisterElement; import io.openems.edge.bridge.modbus.api.task.FC3ReadRegistersTask; import io.openems.edge.bridge.modbus.api.task.Task; import io.openems.edge.common.taskmanager.Priority; @@ -28,7 +36,7 @@ public class ModbusUtils { * @throws OpenemsException on error with the {@link ModbusProtocol} object */ public static CompletableFuture readELementOnce(ModbusProtocol modbusProtocol, - AbstractModbusElement element, boolean tryAgainOnError) throws OpenemsException { + AbstractModbusRegisterElement element, boolean tryAgainOnError) throws OpenemsException { // Prepare result final var result = new CompletableFuture(); @@ -68,7 +76,7 @@ public static CompletableFuture readELementOnce(ModbusProtocol modbusProt * @throws OpenemsException on error with the {@link ModbusProtocol} object */ public static CompletableFuture> readELementsOnce(ModbusProtocol modbusProtocol, - AbstractModbusElement[] elements, boolean tryAgainOnError) throws OpenemsException { + AbstractModbusRegisterElement[] elements, boolean tryAgainOnError) throws OpenemsException { if (elements.length == 0) { return CompletableFuture.completedFuture(Collections.emptyList()); } @@ -142,4 +150,53 @@ public static Short convert(int value, int upperBytes) { return shortBuf.getShort(); } + + /** + * Converts a byte array to a String in the form "00C1 00B2". + * + * @param data byte array + * @return string + */ + public static String byteArrayToHexString(byte[] data) { + return IntStream.range(0, data.length / 2) // + .mapToObj(i -> String.format("%2s%2s", // + Integer.toHexString(data[i]), Integer.toHexString(data[i + 1])).replace(' ', '0')) + .collect(Collectors.joining(" ")); + } + + /** + * Converts a int to a String in the form "00C1". + * + * @param data byte array + * @return string + */ + public static String intToHexString(int data) { + return String.format("%4s", Integer.toHexString(data)).replace(' ', '0'); + } + + /** + * Converts a {@link Register} array to a String in the form "00C1 00B2". + * + * @param registers {@link Register} array + * @return string + */ + public static String registersToHexString(Register... registers) { + return registersToHexString(registers, Register::getValue); + } + + /** + * Converts a {@link InputRegister} array to a String in the form "00C1 00B2". + * + * @param registers {@link InputRegister} array + * @return string + */ + public static String registersToHexString(InputRegister... registers) { + return registersToHexString(registers, InputRegister::getValue); + } + + private static String registersToHexString(T[] registers, Function fnct) { + return Arrays.stream(registers) // + .map(r -> intToHexString(fnct.apply(r))) // + .collect(Collectors.joining(" ")); + } } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractDoubleWordElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractDoubleWordElement.java index 7ac4e5933ba..48f4c1e5da3 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractDoubleWordElement.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractDoubleWordElement.java @@ -16,10 +16,10 @@ /** * A DoubleWordElement has a size of two Modbus Registers or 32 bit. * - * @param the subclass of myself - * @param the target OpenemsType + * @param the subclass of myself + * @param the target type */ -public abstract class AbstractDoubleWordElement extends AbstractModbusRegisterElement { +public abstract class AbstractDoubleWordElement extends AbstractModbusRegisterElement { private final Logger log = LoggerFactory.getLogger(AbstractDoubleWordElement.class); @@ -27,14 +27,6 @@ public AbstractDoubleWordElement(OpenemsType type, int startAddress) { super(type, startAddress); } - /** - * Gets an instance of the correct subclass of myself. - * - * @return myself - */ - @Override - protected abstract E self(); - @Override public final int getLength() { return 2; @@ -104,7 +96,7 @@ public final void _setNextWriteValue(Optional valueOpt) throws OpenemsExcepti * @param wordOrder the new Word-Order * @return myself */ - public final E wordOrder(WordOrder wordOrder) { + public final SELF wordOrder(WordOrder wordOrder) { this.wordOrder = wordOrder; return this.self(); } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractModbusElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractModbusElement.java index 4545b945f16..62002c5adea 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractModbusElement.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractModbusElement.java @@ -11,14 +11,15 @@ import io.openems.common.types.OpenemsType; import io.openems.edge.bridge.modbus.api.AbstractModbusBridge; -import io.openems.edge.bridge.modbus.api.task.AbstractTask; +import io.openems.edge.bridge.modbus.api.task.Task; /** * A ModbusElement represents one row of a Modbus definition table. * - * @param the target OpenemsType + * @param the subclass of myself + * @param the target type */ -public abstract class AbstractModbusElement implements ModbusElement { +public abstract class AbstractModbusElement implements ModbusElement { protected final List>> onSetNextWriteCallbacks = new ArrayList<>(); @@ -26,23 +27,24 @@ public abstract class AbstractModbusElement implements ModbusElement { private final OpenemsType type; private final int startAddress; - private final boolean isIgnored; // Counts for how many cycles no valid value was private int invalidValueCounter = 0; - protected AbstractTask abstractTask = null; + protected Task task = null; public AbstractModbusElement(OpenemsType type, int startAddress) { - this(type, startAddress, false); - } - - public AbstractModbusElement(OpenemsType type, int startAddress, boolean isIgnored) { this.type = type; this.startAddress = startAddress; - this.isIgnored = isIgnored; } + /** + * Gets an instance of the correct subclass of myself. + * + * @return myself + */ + protected abstract SELF self(); + @Override public final void onSetNextWrite(Consumer> callback) { this.onSetNextWriteCallbacks.add(callback); @@ -65,9 +67,9 @@ public OpenemsType getType() { * @param onUpdateCallback the Callback * @return myself */ - public AbstractModbusElement onUpdateCallback(Consumer onUpdateCallback) { + public SELF onUpdateCallback(Consumer onUpdateCallback) { this.onUpdateCallbacks.add(onUpdateCallback); - return this; + return this.self(); } @Override @@ -76,17 +78,12 @@ public int getStartAddress() { } @Override - public boolean isIgnored() { - return this.isIgnored; - } - - @Override - public void setModbusTask(AbstractTask abstractTask) { - this.abstractTask = abstractTask; + public void setModbusTask(Task task) { + this.task = task; } - public AbstractTask getModbusTask() { - return this.abstractTask; + public Task getModbusTask() { + return this.task; } protected void setValue(T value) { @@ -102,13 +99,11 @@ protected void setValue(T value) { } @Override - public boolean invalidate(AbstractModbusBridge bridge) { + public void invalidate(AbstractModbusBridge bridge) { this.invalidValueCounter++; if (bridge.invalidateElementsAfterReadErrors() <= this.invalidValueCounter) { this.setValue(null); - return true; } - return false; } /* @@ -121,7 +116,7 @@ public boolean invalidate(AbstractModbusBridge bridge) { * * @return myself */ - public AbstractModbusElement debug() { + public AbstractModbusElement debug() { this.isDebug = true; return this; } @@ -132,7 +127,19 @@ protected boolean isDebug() { @Override public String toString() { - return this.startAddress + "/0x" + Integer.toHexString(this.startAddress); + StringBuilder sb = new StringBuilder(); + sb.append(this.getClass().getSimpleName()); + sb.append("type="); + sb.append(this.type.name()); + sb.append(";ref="); + sb.append(this.startAddress); + sb.append("/0x"); + sb.append(Integer.toHexString(this.startAddress)); + if (this.isDebug) { + sb.append(";DEBUG"); + } + sb.append("]"); + return sb.toString(); } @Override diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractModbusRegisterElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractModbusRegisterElement.java index 319968a6981..4db45a8f334 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractModbusRegisterElement.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractModbusRegisterElement.java @@ -15,10 +15,10 @@ /** * A ModbusRegisterElement represents one or more Modbus Registers. * - * @param the subclass of myself - * @param the target OpenemsType + * @param the subclass of myself + * @param the target type */ -public abstract class AbstractModbusRegisterElement extends AbstractModbusElement +public abstract class AbstractModbusRegisterElement extends AbstractModbusElement implements ModbusRegisterElement { private final Logger log = LoggerFactory.getLogger(AbstractModbusRegisterElement.class); @@ -29,13 +29,6 @@ public AbstractModbusRegisterElement(OpenemsType type, int startAddress) { super(type, startAddress); } - /** - * Gets an instance of the correct subclass of myself. - * - * @return myself - */ - protected abstract E self(); - protected void setNextWriteValueRegisters(Optional writeValueOpt) throws OpenemsException { if (writeValueOpt.isPresent() && writeValueOpt.get().length != this.getLength()) { throw new OpenemsException("Modbus Element [" + this + "]: write registers length [" @@ -61,7 +54,7 @@ public Optional getNextWriteValue() { * @param byteOrder the ByteOrder * @return myself */ - public final E byteOrder(ByteOrder byteOrder) { + public final SELF byteOrder(ByteOrder byteOrder) { this.byteOrder = byteOrder; return this.self(); } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractQuadrupleWordElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractQuadrupleWordElement.java index 74cd62919fe..442e42197ad 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractQuadrupleWordElement.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractQuadrupleWordElement.java @@ -16,10 +16,10 @@ /** * A QuadrupleWordElement has a size of four Modbus Registers or 64 bit. * - * @param the subclass of myself - * @param the target OpenemsType + * @param the subclass of myself + * @param the target type */ -public abstract class AbstractQuadrupleWordElement extends AbstractModbusRegisterElement { +public abstract class AbstractQuadrupleWordElement extends AbstractModbusRegisterElement { private final Logger log = LoggerFactory.getLogger(AbstractDoubleWordElement.class); @@ -108,7 +108,7 @@ public final void _setNextWriteValue(Optional valueOpt) throws OpenemsExcepti * @param wordOrder the WordOrder * @return myself */ - public final E wordOrder(WordOrder wordOrder) { + public final SELF wordOrder(WordOrder wordOrder) { this.wordOrder = wordOrder; return this.self(); } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractWordElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractWordElement.java index b35a9d88efe..44d074438ef 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractWordElement.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractWordElement.java @@ -16,10 +16,10 @@ /** * A WordElement has a size of one Modbus Registers or 16 bit. * - * @param the subclass of myself - * @param the target OpenemsType + * @param the subclass of myself + * @param the target type */ -public abstract class AbstractWordElement extends AbstractModbusRegisterElement { +public abstract class AbstractWordElement extends AbstractModbusRegisterElement { private final Logger log = LoggerFactory.getLogger(AbstractWordElement.class); diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/CoilElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/CoilElement.java index 7ff4ba419b0..6d7814760d9 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/CoilElement.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/CoilElement.java @@ -14,7 +14,7 @@ /** * A CoilElement has a size of one Modbus Coil or 1 bit. */ -public class CoilElement extends AbstractModbusElement implements ModbusCoilElement { +public class CoilElement extends AbstractModbusElement implements ModbusCoilElement { private final Logger log = LoggerFactory.getLogger(CoilElement.class); private final List>> onSetNextWriteCallbacks = new ArrayList<>(); @@ -52,4 +52,9 @@ public void setInputCoil(Boolean coil) throws OpenemsException { // set value super.setValue(coil); } + + @Override + protected CoilElement self() { + return this; + } } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/DummyCoilElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/DummyCoilElement.java index f0a501fbb8e..463cc49694a 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/DummyCoilElement.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/DummyCoilElement.java @@ -7,7 +7,8 @@ /** * A DummyCoilElement is a placeholder for an empty {@link ModbusCoilElement}. */ -public class DummyCoilElement extends AbstractModbusElement implements ModbusCoilElement, DummyElement { +public class DummyCoilElement extends AbstractModbusElement + implements ModbusCoilElement, DummyElement { public DummyCoilElement(int startAddress) { super(OpenemsType.BOOLEAN, startAddress); @@ -35,4 +36,9 @@ public void setInputCoil(Boolean coil) { public Optional getNextWriteValue() { return Optional.empty(); } + + @Override + protected DummyCoilElement self() { + return this; + } } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/DummyRegisterElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/DummyRegisterElement.java index 50c54f1faf4..99bbdcf83ad 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/DummyRegisterElement.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/DummyRegisterElement.java @@ -11,7 +11,7 @@ * A DummyRegisterElement is a placeholder for an empty * {@link ModbusRegisterElement}. */ -public class DummyRegisterElement extends AbstractModbusElement +public class DummyRegisterElement extends AbstractModbusRegisterElement implements ModbusRegisterElement, DummyElement { private final int length; @@ -37,6 +37,10 @@ public int getLength() { public void setInputRegisters(InputRegister... registers) { } + @Override + protected void _setInputRegisters(InputRegister... registers) { + } + @Override @Deprecated public void _setNextWriteValue(Optional valueOpt) { @@ -48,4 +52,10 @@ public void _setNextWriteValue(Optional valueOpt) { public Optional getNextWriteValue() { return Optional.empty(); } + + @Override + protected DummyRegisterElement self() { + return this; + } + } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/ModbusElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/ModbusElement.java index 209fcdb5d04..6bde1fe8911 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/ModbusElement.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/ModbusElement.java @@ -7,6 +7,7 @@ import io.openems.common.types.OpenemsType; import io.openems.edge.bridge.modbus.api.AbstractModbusBridge; import io.openems.edge.bridge.modbus.api.task.AbstractTask; +import io.openems.edge.bridge.modbus.api.task.Task; /** * A ModbusElement represents one or more registers or coils in an @@ -29,21 +30,14 @@ public interface ModbusElement { public abstract int getLength(); /** - * Set the {@link AbstractTask}, where this Element belongs to. + * Set the {@link Task}, where this Element belongs to. * *

* This is called by the {@link AbstractTask} constructor. * - * @param abstractTask the AbstractTask + * @param task the {@link Task} */ - public void setModbusTask(AbstractTask abstractTask); - - /** - * Whether this Element should be ignored (= DummyElement). - * - * @return true for ignored elements - */ - public boolean isIgnored(); + public void setModbusTask(Task task); /** * Gets the type of this Register, e.g. INTEGER, BOOLEAN,.. @@ -75,9 +69,8 @@ public interface ModbusElement { * 'invalidateElementsAfterReadErrors' config setting of the bridge. * * @param bridge the {@link AbstractModbusBridge} - * @return true if Channel was invalidated */ - public boolean invalidate(AbstractModbusBridge bridge); + public void invalidate(AbstractModbusBridge bridge); /** * This is called on deactivate of the Modbus-Bridge. It can be used to clear diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/ModbusReadElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/ModbusReadElement.java deleted file mode 100644 index a63c9dccd0f..00000000000 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/ModbusReadElement.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.openems.edge.bridge.modbus.api.element; - -import java.util.function.Consumer; - -public interface ModbusReadElement extends ModbusElement { - - /** - * Adds an On-Update-Callback. - * - * @param onUpdateCallback the Callback - * @return myself - */ - public AbstractModbusElement onUpdateCallback(Consumer onUpdateCallback); - -} diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/ModbusRegisterElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/ModbusRegisterElement.java index 057244070a6..8b63eecfe71 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/ModbusRegisterElement.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/ModbusRegisterElement.java @@ -10,6 +10,8 @@ /** * A ModbusRegisterElement represents one or more Modbus Registers. + * + * @param the target type */ public interface ModbusRegisterElement extends ModbusElement { diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadDigitalInputsTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadDigitalInputsTask.java index 5dac008b417..1e72aa8f9ba 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadDigitalInputsTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadDigitalInputsTask.java @@ -1,52 +1,62 @@ package io.openems.edge.bridge.modbus.api.task; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.ghgande.j2mod.modbus.msg.ModbusRequest; import com.ghgande.j2mod.modbus.msg.ModbusResponse; import com.ghgande.j2mod.modbus.util.BitVector; import io.openems.common.exceptions.OpenemsException; -import io.openems.edge.bridge.modbus.api.element.AbstractModbusElement; import io.openems.edge.bridge.modbus.api.element.ModbusCoilElement; import io.openems.edge.bridge.modbus.api.element.ModbusElement; import io.openems.edge.common.taskmanager.Priority; -public abstract class AbstractReadDigitalInputsTask extends AbstractReadTask { +public abstract class AbstractReadDigitalInputsTask // + extends AbstractReadTask { - public AbstractReadDigitalInputsTask(int startAddress, Priority priority, AbstractModbusElement... elements) { - super(startAddress, priority, elements); + public AbstractReadDigitalInputsTask(String name, Class responseClazz, int startAddress, + Priority priority, ModbusCoilElement... elements) { + super(name, responseClazz, ModbusCoilElement.class, startAddress, priority, elements); } @Override - protected boolean isCorrectElementInstance(ModbusElement modbusElement) { - return modbusElement instanceof ModbusCoilElement; + protected void handleResponse(ModbusCoilElement element, int position, Boolean[] response) throws OpenemsException { + element.setInputCoil(response[position]); } @Override - protected String getRequiredElementName() { - return "ModbusCoilElement"; + protected int calculateNextPosition(ModbusElement modbusElement, int position) { + return position + 1; } @Override - protected void doElementSetInput(ModbusElement modbusElement, int position, Boolean[] response) - throws OpenemsException { - ((ModbusCoilElement) modbusElement).setInputCoil(response[position]); + protected final Boolean[] parseResponse(RESPONSE response) { + return toBooleanArray(this.parseBitResponse(response)); } - @Override - protected int increasePosition(int position, ModbusElement modbusElement) { - return position + 1; + protected abstract BitVector parseBitResponse(RESPONSE response); + + /** + * Convert a {@link BitVector} to a {@link Boolean} array. + * + * @param v the {@link BitVector} + * @return the {@link Boolean} array + */ + protected static Boolean[] toBooleanArray(BitVector v) { + var bools = new Boolean[v.size()]; + for (var i = 0; i < v.size(); i++) { + bools[i] = v.getBit(i); + } + return bools; } @Override - protected Boolean[] handleResponse(ModbusResponse response) throws OpenemsException { - try { - return Utils.toBooleanArray(this.getBitVector(response)); - } catch (ClassCastException e) { - throw new OpenemsException("Unexpected Modbus response. Expected [" + this.getExpectedInputClassname() - + "], got [" + response.getClass().getSimpleName() + "]"); - } + protected final String payloadToString(RESPONSE response) { + return Stream.of(this.parseResponse(response)) // + .map(b -> b == null ? "-" : (b ? "1" : "0")) // + .collect(Collectors.joining()); } - - protected abstract String getExpectedInputClassname(); - - protected abstract BitVector getBitVector(ModbusResponse response) throws OpenemsException; } \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadInputRegistersTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadInputRegistersTask.java index 5f0b665db7e..ac144f3ae0b 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadInputRegistersTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadInputRegistersTask.java @@ -2,39 +2,32 @@ import java.util.Arrays; +import com.ghgande.j2mod.modbus.msg.ModbusRequest; +import com.ghgande.j2mod.modbus.msg.ModbusResponse; import com.ghgande.j2mod.modbus.procimg.InputRegister; import io.openems.common.exceptions.OpenemsException; -import io.openems.edge.bridge.modbus.api.element.AbstractModbusElement; import io.openems.edge.bridge.modbus.api.element.ModbusElement; import io.openems.edge.bridge.modbus.api.element.ModbusRegisterElement; import io.openems.edge.common.taskmanager.Priority; -public abstract class AbstractReadInputRegistersTask extends AbstractReadTask { +@SuppressWarnings("rawtypes") +public abstract class AbstractReadInputRegistersTask + extends AbstractReadTask { - public AbstractReadInputRegistersTask(int startAddress, Priority priority, AbstractModbusElement... elements) { - super(startAddress, priority, elements); + public AbstractReadInputRegistersTask(String name, Class responseClazz, int startAddress, + Priority priority, ModbusElement... elements) { + super(name, responseClazz, ModbusRegisterElement.class, startAddress, priority, elements); } @Override - protected boolean isCorrectElementInstance(ModbusElement modbusElement) { - return modbusElement instanceof ModbusRegisterElement; - } - - @Override - protected void doElementSetInput(ModbusElement modbusElement, int position, InputRegister[] response) + protected void handleResponse(ModbusRegisterElement element, int position, InputRegister[] response) throws OpenemsException { - ((ModbusRegisterElement) modbusElement) - .setInputRegisters(Arrays.copyOfRange(response, position, position + modbusElement.getLength())); - } - - @Override - protected String getRequiredElementName() { - return "ModbusRegisterElement"; + element.setInputRegisters(Arrays.copyOfRange(response, position, position + element.getLength())); } @Override - protected int increasePosition(int position, ModbusElement modbusElement) { + protected int calculateNextPosition(ModbusElement modbusElement, int position) { return position + modbusElement.getLength(); } } \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadTask.java index fce9a74db32..89e747224de 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadTask.java @@ -1,15 +1,13 @@ package io.openems.edge.bridge.modbus.api.task; -import java.util.Arrays; -import java.util.stream.Collectors; +import java.util.ArrayList; +import java.util.stream.Stream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.ghgande.j2mod.modbus.ModbusException; import com.ghgande.j2mod.modbus.msg.ModbusRequest; import com.ghgande.j2mod.modbus.msg.ModbusResponse; -import com.ghgande.j2mod.modbus.procimg.InputRegister; import io.openems.common.exceptions.OpenemsException; import io.openems.edge.bridge.modbus.api.AbstractModbusBridge; @@ -22,103 +20,95 @@ * {@link AbstractModbusElement} which have register addresses in the same * range. */ -public abstract class AbstractReadTask extends AbstractTask implements ReadTask { +@SuppressWarnings("rawtypes") +public abstract class AbstractReadTask // + extends AbstractTask implements ReadTask { private final Logger log = LoggerFactory.getLogger(AbstractReadTask.class); - private final Priority priority; + private final Class elementClazz; - public AbstractReadTask(int startAddress, Priority priority, AbstractModbusElement... elements) { - super(startAddress, elements); + public AbstractReadTask(String name, Class responseClazz, Class elementClazz, int startAddress, + Priority priority, ModbusElement... elements) { + super(name, responseClazz, startAddress, elements); + this.elementClazz = elementClazz; this.priority = priority; } @Override - public int _execute(AbstractModbusBridge bridge) throws OpenemsException { - T[] response; + public ExecuteState execute(AbstractModbusBridge bridge) { try { - /* - * First try - */ - response = this.readElements(bridge); - - } catch (OpenemsException | ModbusException e) { - /* - * Second try: with new connection - */ - bridge.closeModbusConnection(); + var response = this.executeRequest(bridge, this.createModbusRequest()); + // On error a log message has already been logged + try { - response = this.readElements(bridge); + var result = this.parseResponse(response); + validateResponse(result, this.length); + this.fillElements(result); - } catch (ModbusException e2) { - for (ModbusElement elem : this.getElements()) { - if (!elem.isIgnored()) { - elem.invalidate(bridge); - } - } - throw new OpenemsException("Transaction failed: " + e.getMessage(), e2); + } catch (OpenemsException e1) { + logError(this.log, e1, "Parsing Response failed."); + throw e1; } - } + return ExecuteState.OK; - // Verify response length - if (response.length < this.getLength()) { - throw new OpenemsException("Received message is too short. Expected [" + this.getLength() + "], got [" - + response.length + "]"); - } + } catch (Exception e) { + // Invalidate Elements + Stream.of(this.elements).forEach(el -> el.invalidate(bridge)); - this.fillElements(response); - return 1; + return ExecuteState.ERROR; + } } - protected T[] readElements(AbstractModbusBridge bridge) throws OpenemsException, ModbusException { - var request = this.getRequest(); - int unitId = this.getParent().getUnitId(); - var response = Utils.getResponse(request, this.getParent().getUnitId(), bridge); - - var result = this.handleResponse(response); - - // debug output - switch (this.getLogVerbosity(bridge)) { - case READS_AND_WRITES: - bridge.logInfo(this.log, this.getActiondescription() // - + " [" + unitId + ":" + this.getStartAddress() + "/0x" + Integer.toHexString(this.getStartAddress()) - + "]: " // - + Arrays.stream(result).map(r -> { - if (r instanceof InputRegister) { - return String.format("%4s", Integer.toHexString(((InputRegister) r).getValue())) - .replace(' ', '0'); - } - if (r instanceof Boolean) { - return (Boolean) r ? "x" : "-"; - } else { - return r.toString(); - } - }) // - .collect(Collectors.joining(" "))); - break; - case WRITES: - case NONE: - break; + /** + * Verify length of response array. + * + * @param response response array + * @param length expected length + * @throws OpenemsException on failed validation + */ + private static void validateResponse(Object[] response, int length) throws OpenemsException { + if (response.length < length) { + throw new OpenemsException("Received message is too short. " // + + "Expected [" + length + "] " // + + "Got [" + response.length + "]"); } - - return result; } - protected void fillElements(T[] response) { + /** + * Fills {@link ModbusElement}s with values from response. + * + * @param response the response values + * @throws OpenemsException on error + */ + @SuppressWarnings("unchecked") + private void fillElements(T[] response) throws OpenemsException { var position = 0; - for (ModbusElement modbusElement : this.getElements()) { - if (!this.isCorrectElementInstance(modbusElement)) { - this.doErrorLog(modbusElement); - } else { + var errors = new ArrayList(); + + for (var element : this.elements) { + if (this.elementClazz.isInstance(element)) { try { - if (!modbusElement.isIgnored()) { - this.doElementSetInput(modbusElement, position, response); - } + this.handleResponse((ELEMENT) element, position, response); } catch (OpenemsException e) { - this.doWarnLog(e); + errors.add("Unable to fill Modbus Element. " // + + element.toString() + " Error: " + e.getMessage()); } + } else { + errors.add("Wrong type while filling Modbus Element. " // + + element.toString() + " " // + + "Expected [" + this.elementClazz.getSimpleName() + "] " // + + "Got [" + element.getClass().getSimpleName() + "]"); } - position = this.increasePosition(position, modbusElement); + position = this.calculateNextPosition(element, position); + } + + if (!errors.isEmpty()) { + throw new OpenemsException(String.join(", ", errors)); } } @@ -127,27 +117,43 @@ public Priority getPriority() { return this.priority; } - protected abstract int increasePosition(int position, ModbusElement modbusElement); - - protected abstract void doElementSetInput(ModbusElement modbusElement, int position, T[] response) - throws OpenemsException; - - protected abstract String getRequiredElementName(); - - protected abstract boolean isCorrectElementInstance(ModbusElement modbusElement); - - protected abstract ModbusRequest getRequest(); + /** + * Handle a Response, e.g. set the internal value. + * + * @param element the {@link ModbusElement} + * @param position the current position + * @param response the converted {@link ModbusResponse} values + * @throws OpenemsException on error + */ + protected abstract void handleResponse(ELEMENT element, int position, T[] response) throws OpenemsException; + + /** + * Calculate the position of the next Element. + * + * @param position current position + * @param modbusElement current Element + * @return next position + */ + protected abstract int calculateNextPosition(ModbusElement modbusElement, int position); + + /** + * Factory for a {@link ModbusRequest}. + * + * @return a new {@link ModbusRequest} + */ + protected abstract REQUEST createModbusRequest(); + + /** + * Parses a {@link ModbusResponse} to an array of values. + * + * @param response the {@link ModbusResponse} + * @return array of results + * @throws OpenemsException on error + */ + protected abstract T[] parseResponse(RESPONSE response) throws OpenemsException; - protected abstract T[] handleResponse(ModbusResponse response) throws OpenemsException; - - private void doWarnLog(OpenemsException e) { - this.log.warn("Unable to fill modbus element. UnitId [" + this.getParent().getUnitId() + "] Address [" - + this.getStartAddress() + "] Length [" + this.getLength() + "]: " + e.getMessage()); - } - - private void doErrorLog(ModbusElement modbusElement) { - this.log.error("A " + this.getRequiredElementName() + " is required for a " + this.getActiondescription() - + "Task! Element [" + modbusElement + "]"); + @Override + protected final String payloadToString(REQUEST request) { + return ""; } - } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractTask.java index 523335684cd..b4b981472b2 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractTask.java @@ -1,12 +1,23 @@ package io.openems.edge.bridge.modbus.api.task; +import java.util.Arrays; import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.ghgande.j2mod.modbus.Modbus; +import com.ghgande.j2mod.modbus.ModbusSlaveException; +import com.ghgande.j2mod.modbus.msg.ModbusRequest; +import com.ghgande.j2mod.modbus.msg.ModbusResponse; import com.google.common.base.Stopwatch; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.function.ThrowingSupplier; import io.openems.edge.bridge.modbus.api.AbstractModbusBridge; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; +import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.LogVerbosity; import io.openems.edge.bridge.modbus.api.element.ModbusElement; @@ -14,53 +25,59 @@ * An abstract Modbus 'AbstractTask' is holding references to one or more Modbus * {@link ModbusElement} which have register addresses in the same range. */ -public abstract class AbstractTask implements Task { +public abstract non-sealed class AbstractTask implements Task { - private static final long DEFAULT_EXECUTION_DURATION = 300; + protected final String name; + protected final Class responseClazz; + protected final int startAddress; + protected final int length; + protected final ModbusElement[] elements; - private final int length; - private final int startAddress; - private final Stopwatch stopwatch = Stopwatch.createUnstarted(); + private final Logger log = LoggerFactory.getLogger(AbstractTask.class); - private final ModbusElement[] elements; private AbstractOpenemsModbusComponent parent = null; // this is always set by ModbusProtocol.addTask() - private boolean hasBeenExecutedSuccessfully = false; - private long lastExecuteDuration = DEFAULT_EXECUTION_DURATION; // initialize to some default - public AbstractTask(int startAddress, ModbusElement... elements) { + public AbstractTask(String name, Class responseClazz, int startAddress, ModbusElement... elements) { + this.name = name; + this.responseClazz = responseClazz; this.startAddress = startAddress; this.elements = elements; - for (ModbusElement element : elements) { - element.setModbusTask(this); - } + var nextStartAddress = startAddress; var length = 0; - for (ModbusElement element : elements) { + for (var element : elements) { + if (element.getStartAddress() != nextStartAddress) { + throw new IllegalArgumentException("StartAddress for Modbus Element wrong. " // + + "Got [" + element.getStartAddress() + "/0x" + Integer.toHexString(element.getStartAddress()) + + "] Expected [" + nextStartAddress + "/0x" + Integer.toHexString(nextStartAddress) + "]"); + } + nextStartAddress += element.getLength(); length += element.getLength(); + element.setModbusTask(this); } this.length = length; } - @Override + // Override for Task.getElements() public ModbusElement[] getElements() { return this.elements; } - @Override + // Override for Task.getLength() public int getLength() { return this.length; } - @Override + // Override for Task.getStartAddress() public int getStartAddress() { return this.startAddress; } - @Override public void setParent(AbstractOpenemsModbusComponent parent) { this.parent = parent; } - @Override public AbstractOpenemsModbusComponent getParent() { return this.parent; } @@ -69,34 +86,101 @@ public AbstractOpenemsModbusComponent getParent() { * Executes the tasks - i.e. sends the query of a ReadTask or writes a * WriteTask. * - *

- * Internally the _execute()-method of the specific subclass gets called. - * * @param bridge the Modbus-Bridge * @return the number of executed Sub-Tasks + */ + public abstract ExecuteState execute(AbstractModbusBridge bridge); + + /** + * Actually executes a {@link ModbusRequest} and returns its + * {@link ModbusResponse}. + * + *

+ * If first request fails, the implementation reconnects the Modbus connection + * and tries again. + * + *

+ * Successful execution is produces a log message if {@link LogVerbosity} != + * 'NONE' was configured. Errors are always logged. + * + * @param bridge the {@link AbstractModbusBridge} + * @param request the typed {@link ModbusRequest} + * @return the typed {@link ModbusResponse} * @throws OpenemsException on error */ - @Override - public final synchronized int execute(AbstractModbusBridge bridge) throws OpenemsException { - this.stopwatch.reset(); - this.stopwatch.start(); + protected RESPONSE executeRequest(AbstractModbusBridge bridge, REQUEST request) throws Exception { + var unitId = this.getParent().getUnitId(); + var logVerbosity = this.getLogVerbosity(bridge); try { - var noOfSubTasksExecuted = this._execute(bridge); - - // no exception -> mark this task as successfully executed - this.hasBeenExecutedSuccessfully = true; - return noOfSubTasksExecuted; + // First try + return this.logRequest(bridge, logVerbosity, request, + () -> sendRequest(bridge, unitId, this.responseClazz, request)); - } finally { - this.lastExecuteDuration = this.stopwatch.elapsed(TimeUnit.MILLISECONDS); + } catch (Exception e) { + // Second try; with new connection + bridge.closeModbusConnection(); + return this.logRequest(bridge, logVerbosity, request, + () -> sendRequest(bridge, unitId, this.responseClazz, request)); } } - protected abstract int _execute(AbstractModbusBridge bridge) throws OpenemsException; + /** + * Logs the execution of a {@link ModbusRequest}. + * + * @param bridge the {@link BridgeModbus} + * @param logVerbosity the {@link LogVerbosity} + * @param request the {@link ModbusRequest} + * @param supplier {@link ThrowingSupplier} that executes the Request and + * returns a Response + * @return typed {@link ModbusResponse} + * @throws Exception on error + */ + protected RESPONSE logRequest(BridgeModbus bridge, LogVerbosity logVerbosity, REQUEST request, + ThrowingSupplier supplier) throws Exception { + return switch (logVerbosity) { + case NONE, DEBUG_LOG -> { + try { + yield supplier.get(); + + } catch (Exception e) { + this.logError(e, "Execute failed", this.toLogMessage(logVerbosity, request, e)); + throw e; + } + } + + case READS_AND_WRITES, READS_AND_WRITES_VERBOSE -> { + try { + var response = supplier.get(); + this.logInfo(" Execute", this.toLogMessage(logVerbosity, request, response)); + yield response; + + } catch (Exception e) { + this.logError(e, " Execute failed", this.toLogMessage(logVerbosity, request, e)); + throw e; + } + } + + case READS_AND_WRITES_DURATION, READS_AND_WRITES_DURATION_TRACE_EVENTS -> { + var stopwatch = Stopwatch.createStarted(); + try { + var response = supplier.get(); + stopwatch.stop(); + this.logInfo(" Execute", this.toLogMessage(logVerbosity, request, response), + "Elapsed [" + stopwatch.elapsed(TimeUnit.MILLISECONDS) + "ms]"); + yield response; + + } catch (Exception e) { + stopwatch.stop(); + this.logError(e, " Execute failed", this.toLogMessage(logVerbosity, request, e), + "Elapsed [" + stopwatch.elapsed(TimeUnit.MILLISECONDS) + "ms]"); + throw e; + } + } + }; + } /* - * Enable Debug mode for this Element. Activates verbose logging. TODO: - * implement debug write in all implementations (FC16 is already done) + * Enable Debug mode for this Element. Activates verbose logging. */ private boolean isDebug = false; @@ -105,15 +189,11 @@ public final synchronized int execute(AbstractModbusBridge bridge) throws Openem * * @return myself */ - public AbstractTask debug() { + public AbstractTask debug() { this.isDebug = true; return this; } - public boolean isDebug() { - return this.isDebug; - } - /** * Combines the global and local (via {@link #isDebug} log verbosity. * @@ -122,45 +202,164 @@ public boolean isDebug() { */ protected LogVerbosity getLogVerbosity(AbstractModbusBridge bridge) { if (this.isDebug) { - return LogVerbosity.READS_AND_WRITES; + return LogVerbosity.READS_AND_WRITES_VERBOSE; } return bridge.getLogVerbosity(); } - @Override - public boolean hasBeenExecuted() { - return this.hasBeenExecutedSuccessfully; - } - - @Override + /** + * Deactivate. + */ public void deactivate() { for (ModbusElement element : this.elements) { element.deactivate(); } } - @Override - public String toString() { - var sb = new StringBuilder(); - sb.append(this.getActiondescription()); - sb.append(" ["); - sb.append(this.parent.id()); - sb.append(";unitid="); - sb.append(this.parent.getUnitId()); - sb.append(";ref="); - sb.append(this.getStartAddress()); - sb.append("/0x"); - sb.append(Integer.toHexString(this.getStartAddress())); - sb.append(";length="); - sb.append(this.getLength()); - sb.append("]"); - return sb.toString(); + private void logInfo(String... messages) { + logInfo(this.log, messages); } - @Override - public long getExecuteDuration() { - return this.lastExecuteDuration; + protected static void logInfo(Logger log, String... messages) { + log(log, Logger::info, messages); } - protected abstract String getActiondescription(); + private void logError(Exception e, String... messages) { + logError(this.log, e, messages); + } + + protected static void logError(Logger log, Exception e, String... messages) { + messages = Arrays.copyOf(messages, messages.length + 1); + messages[messages.length - 1] = e.getClass().getSimpleName() + ": " + e.getMessage(); + log(log, Logger::error, messages); + } + + private static void log(Logger log, BiConsumer logger, String... messages) { + logger.accept(log, String.join(" ", messages)); + } + + protected final String toLogMessage(LogVerbosity logVerbosity, REQUEST request, Exception e) { + return this.toLogMessage(logVerbosity, request, null, e); + } + + protected final String toLogMessage(LogVerbosity logVerbosity, REQUEST request, RESPONSE response) { + return this.toLogMessage(logVerbosity, request, response, null); + } + + // This method has an @Override in FC16WriteRegistersTask + protected String toLogMessage(LogVerbosity logVerbosity, REQUEST request, RESPONSE response, Exception e) { + return this.toLogMessage(logVerbosity, this.startAddress, this.length, request, response, e); + } + + /** + * Generates a log message for this task. + * + *

+ * StartAddress and length need to be provided explicitly, because FC16 task + * might be split to multiple requests. + * + *

+ * For certain Exceptions we internally increase the LogVerbosity to always show + * helpful information + * + * @param logVerbosity the {@link LogVerbosity} + * @param startAddress the start address of the request + * @param length the length of the request payload + * @param request the {@link ModbusRequest} + * @param response the {@link ModbusResponse}, possibly null + * @param exception a {@link Exception}, possibly null + * @return a log message String + */ + protected final String toLogMessage(LogVerbosity logVerbosity, int startAddress, int length, REQUEST request, + RESPONSE response, Exception exception) { + // Handle Exception + if (exception != null) { + if (exception instanceof ModbusSlaveException e && e.isType(Modbus.ILLEGAL_VALUE_EXCEPTION)) { + // In this case it is helpful to get see the detailed request payload + logVerbosity = LogVerbosity.READS_AND_WRITES_VERBOSE; + } + } + + // Build log message + var b = new StringBuilder() // + .append(this.name) // + .append(" [") // + .append(this.parent.id()) // + .append(";unitid=").append(this.parent.getUnitId()); // + if (!(this instanceof WriteTask)) { // WriteTasks anyway default to HIGH priority + b.append(";priority=").append(this.getPriority()); + } + b // + .append(";ref=").append(startAddress).append("/0x").append(Integer.toHexString(startAddress)) // + .append(";length=").append(length); // + switch (logVerbosity) { + case NONE, DEBUG_LOG, READS_AND_WRITES, READS_AND_WRITES_DURATION, READS_AND_WRITES_DURATION_TRACE_EVENTS -> { + } + case READS_AND_WRITES_VERBOSE -> { + if (request != null) { + var hexString = this.payloadToString(request); + if (!hexString.isBlank()) { + b.append(";request=").append(hexString); + } + } + if (response != null) { + var hexString = this.payloadToString(response); + if (!hexString.isBlank()) { + b.append(";response=").append(hexString); + } + } + } + } + return b // + .append("]") // + .toString(); + } + + /** + * Converts the actual payload of the REQUEST to a human readable format + * suitable for logs; without header data (like Unit-ID, function code, + * checksum, etc). + * + * @param request the request + * @return a string + */ + protected abstract String payloadToString(REQUEST request); + + /** + * Converts the actual payload of the RESPONSE to a human readable format + * suitable for logs; without header data (like Unit-ID, function code, + * checksum, etc). + * + * @param response the response + * @return a string + */ + protected abstract String payloadToString(RESPONSE response); + + /** + * Sends a {@link ModbusRequest} and returns the {@link ModbusResponse}. + * + * @param the type of the response + * @param bridge the {@link AbstractModbusBridge} + * @param unitId the Modbus Unit-ID + * @param clazz the class of the response + * @param request the {@link ModbusRequest} + * @return the {@link ModbusResponse} + * @throws Exception on error + */ + private static RESPONSE sendRequest(AbstractModbusBridge bridge, int unitId, + Class clazz, ModbusRequest request) throws Exception { + request.setUnitID(unitId); + var transaction = bridge.getNewModbusTransaction(); + transaction.setRequest(request); + transaction.execute(); + + var response = transaction.getResponse(); + if (clazz.isInstance(response)) { + return (RESPONSE) clazz.cast(response); + } + + throw new OpenemsException("Unexpected Modbus response. " // + + "Expected [" + clazz.getSimpleName() + "] " // + + "Got [" + response.getClass().getSimpleName() + "]"); + } } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractWriteTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractWriteTask.java new file mode 100644 index 00000000000..7209e1ef08d --- /dev/null +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractWriteTask.java @@ -0,0 +1,86 @@ +package io.openems.edge.bridge.modbus.api.task; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.ghgande.j2mod.modbus.msg.ModbusRequest; +import com.ghgande.j2mod.modbus.msg.ModbusResponse; + +import io.openems.common.exceptions.OpenemsException; +import io.openems.edge.bridge.modbus.api.AbstractModbusBridge; +import io.openems.edge.bridge.modbus.api.element.ModbusElement; +import io.openems.edge.common.taskmanager.Priority; + +public abstract class AbstractWriteTask // + extends AbstractTask implements WriteTask { + + public AbstractWriteTask(String name, Class responseClazz, int startAddress, + ModbusElement... elements) { + super(name, responseClazz, startAddress, elements); + } + + /** + * Priority for WriteTasks is by default always HIGH. + * + * @return {@link Priority#HIGH} + */ + @Override + public Priority getPriority() { + return Priority.HIGH; + } + + @Override + protected final String payloadToString(RESPONSE response) { + return ""; + } + + public abstract static class Single> // + extends AbstractWriteTask { + + protected final ELEMENT element; + + private final Logger log = LoggerFactory.getLogger(Single.class); + + public Single(String name, Class responseClazz, int startAddress, ELEMENT element) { + super(name, responseClazz, startAddress, element); + this.element = element; + } + + @Override + public final ExecuteState execute(AbstractModbusBridge bridge) { + final REQUEST request; + try { + request = this.createModbusRequest(); + } catch (OpenemsException e) { + logError(this.log, e, "Creating Modbus Request failed."); + return ExecuteState.ERROR; + } + + if (request == null) { + return ExecuteState.NO_OP; + } + + try { + this.executeRequest(bridge, request); + return ExecuteState.OK; + + } catch (Exception e) { + // On error a log message has already been logged + return ExecuteState.ERROR; + } + } + + /** + * Factory for a {@link ModbusRequest}. + * + * @return a new {@link ModbusRequest} + * @throws OpenemsException on error + */ + protected abstract REQUEST createModbusRequest() throws OpenemsException; + } +} diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC16WriteRegistersTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC16WriteRegistersTask.java index 874fbe728a4..4af28910fab 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC16WriteRegistersTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC16WriteRegistersTask.java @@ -1,22 +1,21 @@ package io.openems.edge.bridge.modbus.api.task; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; +import java.util.function.Consumer; +import java.util.stream.Stream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.ghgande.j2mod.modbus.ModbusException; import com.ghgande.j2mod.modbus.msg.WriteMultipleRegistersRequest; import com.ghgande.j2mod.modbus.msg.WriteMultipleRegistersResponse; import com.ghgande.j2mod.modbus.procimg.Register; -import io.openems.common.exceptions.OpenemsException; import io.openems.edge.bridge.modbus.api.AbstractModbusBridge; -import io.openems.edge.bridge.modbus.api.element.AbstractModbusElement; +import io.openems.edge.bridge.modbus.api.LogVerbosity; +import io.openems.edge.bridge.modbus.api.ModbusUtils; import io.openems.edge.bridge.modbus.api.element.ModbusElement; import io.openems.edge.bridge.modbus.api.element.ModbusRegisterElement; @@ -24,122 +23,114 @@ * Implements a Write Holding Registers Task, using Modbus function code 16 * (http://www.simplymodbus.ca/FC16.htm). */ -public class FC16WriteRegistersTask extends AbstractTask implements WriteTask { +public class FC16WriteRegistersTask + extends AbstractWriteTask { private final Logger log = LoggerFactory.getLogger(FC16WriteRegistersTask.class); - public FC16WriteRegistersTask(int startAddress, AbstractModbusElement... elements) { - super(startAddress, elements); + public FC16WriteRegistersTask(int startAddress, ModbusElement... elements) { + super("FC16WriteRegisters", WriteMultipleRegistersResponse.class, startAddress, elements); } - private static class CombinedWriteRegisters { - public final int startAddress; - private final List registers = new ArrayList<>(); - - public CombinedWriteRegisters(int startAddress) { - this.startAddress = startAddress; - } + @Override + public ExecuteState execute(AbstractModbusBridge bridge) { + var requests = mergeWriteRegisters(this.elements, message -> this.log.warn(message)).stream() // + .map(e -> new WriteMultipleRegistersRequest(e.startAddress(), e.getRegisters())) // + .toList(); - public void add(Register... registers) { - Collections.addAll(this.registers, registers); + if (requests.isEmpty()) { + return ExecuteState.NO_OP; } - public Register[] getRegisters() { - return this.registers.toArray(new Register[this.registers.size()]); - } + boolean hasError = false; + for (var request : requests) { + try { + this.executeRequest(bridge, request); - public int getLastAddress() { - return this.startAddress + this.registers.size() - 1; - } - } + } catch (Exception e) { + // On error a log message has already been logged - @Override - public int _execute(AbstractModbusBridge bridge) throws OpenemsException { - var noOfWrittenRegisters = 0; - var writes = this.mergeWriteRegisters(); - // Execute combined writes - for (CombinedWriteRegisters write : writes) { - var registers = write.getRegisters(); - try { - /* - * First try - */ - this.writeMultipleRegisters(bridge, this.getParent().getUnitId(), write.startAddress, registers); - } catch (OpenemsException | ModbusException e) { - /* - * Second try: with new connection - */ - bridge.closeModbusConnection(); - try { - this.writeMultipleRegisters(bridge, this.getParent().getUnitId(), write.startAddress, - write.getRegisters()); - } catch (ModbusException e2) { - throw new OpenemsException("Transaction failed: " + e.getMessage(), e2); - } + // Invalidate Elements + Stream.of(this.elements).forEach(el -> el.invalidate(bridge)); + hasError = true; } - noOfWrittenRegisters += registers.length; - } - return noOfWrittenRegisters; - } - - private void writeMultipleRegisters(AbstractModbusBridge bridge, int unitId, int startAddress, Register[] registers) - throws ModbusException, OpenemsException { - var request = new WriteMultipleRegistersRequest(startAddress, registers); - var response = Utils.getResponse(request, unitId, bridge); - - // debug output - switch (this.getLogVerbosity(bridge)) { - case READS_AND_WRITES: - bridge.logInfo(this.log, "FC16WriteRegisters " // - + "[" + unitId + ":" + startAddress + "/0x" + Integer.toHexString(startAddress) + "]: " // - + Arrays.stream(registers) // - .map(r -> String.format("%4s", Integer.toHexString(r.getValue())).replace(' ', '0')) // - .collect(Collectors.joining(" "))); - break; - case WRITES: - case NONE: - break; } - if (!(response instanceof WriteMultipleRegistersResponse)) { - throw new OpenemsException("Unexpected Modbus response. Expected [WriteMultipleRegistersResponse], got [" - + response.getClass().getSimpleName() + "]"); + if (hasError) { + return ExecuteState.ERROR; + } else { + return ExecuteState.OK; } } /** * Combine WriteRegisters without holes in between. - * + * + * @param elements the {@link ModbusElement}s + * @param logWarn {@link Consumer} to log a warning * @return a list of CombinedWriteRegisters */ - private List mergeWriteRegisters() { - List writes = new ArrayList<>(); - var elements = this.getElements(); - for (ModbusElement element : elements) { - if (element instanceof ModbusRegisterElement) { - var valueOpt = ((ModbusRegisterElement) element).getNextWriteValueAndReset(); - if (valueOpt.isPresent()) { - // found value -> add to 'writes' - CombinedWriteRegisters write; - if (writes.isEmpty() /* no writes created yet */ - || writes.get(writes.size() - 1).getLastAddress() + 1 != element - .getStartAddress() /* there is a hole between last element and current element */) { - write = new CombinedWriteRegisters(element.getStartAddress()); + protected static List mergeWriteRegisters(ModbusElement[] elements, + Consumer logWarn) { + final var writes = new ArrayList(); + for (var element : elements) { + if (element instanceof ModbusRegisterElement e) { + e.getNextWriteValueAndReset().ifPresent(registers -> { + // found value registers -> add to 'writes' + final MergedWriteRegisters write; + if (writes.isEmpty()) { + // no writes created yet + write = MergedWriteRegisters.of(e.getStartAddress()); writes.add(write); } else { - write = writes.get(writes.size() - 1); // no hole -> combine writes + var lastWrite = writes.get(writes.size() - 1); + if (lastWrite.getLastAddress() + 1 != e.getStartAddress()) { + // there is a hole between last element and current element + write = MergedWriteRegisters.of(e.getStartAddress()); + writes.add(write); + } else { + // no hole -> combine writes + write = writes.get(writes.size() - 1); + } } - write.add(valueOpt.get()); - } + write.add(registers); + }); } else { - this.log.warn("Unable to execute Write for ModbusElement [" + element + "]: No ModbusRegisterElement!"); + logWarn.accept( + "Unable to execute Write for ModbusElement [" + element + "]: No ModbusRegisterElement!"); } } return writes; } + protected static record MergedWriteRegisters(int startAddress, List registers) { + public static MergedWriteRegisters of(int startAddress) { + return new MergedWriteRegisters(startAddress, new ArrayList<>()); + } + + public void add(Register... registers) { + Collections.addAll(this.registers, registers); + } + + public Register[] getRegisters() { + return this.registers.toArray(new Register[this.registers.size()]); + } + + public int getLastAddress() { + return this.startAddress + this.registers.size() - 1; + } + } + + @Override + protected String payloadToString(WriteMultipleRegistersRequest request) { + return ModbusUtils.registersToHexString(request.getRegisters()); + } + @Override - protected String getActiondescription() { - return "FC16 Write Registers"; + protected String toLogMessage(LogVerbosity logVerbosity, WriteMultipleRegistersRequest request, + WriteMultipleRegistersResponse response, Exception exception) { + // Read StartAddress and Length from the actual Sub-Request + return this.toLogMessage(logVerbosity, request.getReference(), request.getWordCount(), request, response, + exception); } } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC1ReadCoilsTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC1ReadCoilsTask.java index 62ba63f9bf9..141bdaedecf 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC1ReadCoilsTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC1ReadCoilsTask.java @@ -1,42 +1,29 @@ package io.openems.edge.bridge.modbus.api.task; -import com.ghgande.j2mod.modbus.msg.ModbusRequest; -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.util.BitVector; -import io.openems.edge.bridge.modbus.api.element.AbstractModbusElement; +import io.openems.edge.bridge.modbus.api.element.ModbusCoilElement; import io.openems.edge.common.taskmanager.Priority; /** * Implements a Read Coils Task, implementing Modbus function code 1 * (http://www.simplymodbus.ca/FC01.htm). */ -public class FC1ReadCoilsTask extends AbstractReadDigitalInputsTask implements ReadTask { +public class FC1ReadCoilsTask extends AbstractReadDigitalInputsTask { - public FC1ReadCoilsTask(int startAddress, Priority priority, AbstractModbusElement... elements) { - super(startAddress, priority, elements); + public FC1ReadCoilsTask(int startAddress, Priority priority, ModbusCoilElement... elements) { + super("FC1ReadCoils", ReadCoilsResponse.class, startAddress, priority, elements); } @Override - protected BitVector getBitVector(ModbusResponse response) { - var coilsResponse = (ReadCoilsResponse) response; - return coilsResponse.getCoils(); + protected ReadCoilsRequest createModbusRequest() { + return new ReadCoilsRequest(this.startAddress, this.length); } @Override - protected String getExpectedInputClassname() { - return "ReadCoilsResponse"; - } - - @Override - protected ModbusRequest getRequest() { - return new ReadCoilsRequest(this.getStartAddress(), this.getLength()); - } - - @Override - protected String getActiondescription() { - return "FC1ReadCoils"; + protected BitVector parseBitResponse(ReadCoilsResponse response) { + return response.getCoils(); } } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC2ReadInputsTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC2ReadInputsTask.java index c9c45853240..8d3ac19b3f9 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC2ReadInputsTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC2ReadInputsTask.java @@ -1,42 +1,30 @@ package io.openems.edge.bridge.modbus.api.task; -import com.ghgande.j2mod.modbus.msg.ModbusRequest; -import com.ghgande.j2mod.modbus.msg.ModbusResponse; import com.ghgande.j2mod.modbus.msg.ReadInputDiscretesRequest; import com.ghgande.j2mod.modbus.msg.ReadInputDiscretesResponse; import com.ghgande.j2mod.modbus.util.BitVector; -import io.openems.edge.bridge.modbus.api.element.AbstractModbusElement; +import io.openems.edge.bridge.modbus.api.element.ModbusCoilElement; import io.openems.edge.common.taskmanager.Priority; /** * Implements a Read Inputs Task, implementing Modbus function code 2 * (http://www.simplymodbus.ca/FC02.htm). */ -public class FC2ReadInputsTask extends AbstractReadDigitalInputsTask implements ReadTask { +public class FC2ReadInputsTask + extends AbstractReadDigitalInputsTask { - public FC2ReadInputsTask(int startAddress, Priority priority, AbstractModbusElement... elements) { - super(startAddress, priority, elements); + public FC2ReadInputsTask(int startAddress, Priority priority, ModbusCoilElement... elements) { + super("FC2ReadCoils", ReadInputDiscretesResponse.class, startAddress, priority, elements); } @Override - protected BitVector getBitVector(ModbusResponse response) { - var readInputDiscretesResponse = (ReadInputDiscretesResponse) response; - return readInputDiscretesResponse.getDiscretes(); + protected ReadInputDiscretesRequest createModbusRequest() { + return new ReadInputDiscretesRequest(this.startAddress, this.length); } @Override - protected String getActiondescription() { - return "FC2ReadCoils"; - } - - @Override - protected String getExpectedInputClassname() { - return "ReadInputDiscretesResponse"; - } - - @Override - protected ModbusRequest getRequest() { - return new ReadInputDiscretesRequest(this.getStartAddress(), this.getLength()); + protected BitVector parseBitResponse(ReadInputDiscretesResponse response) { + return response.getDiscretes(); } } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC3ReadRegistersTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC3ReadRegistersTask.java index 273fecd66a2..06fc6d700a4 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC3ReadRegistersTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC3ReadRegistersTask.java @@ -1,42 +1,37 @@ package io.openems.edge.bridge.modbus.api.task; -import com.ghgande.j2mod.modbus.msg.ModbusRequest; -import com.ghgande.j2mod.modbus.msg.ModbusResponse; import com.ghgande.j2mod.modbus.msg.ReadMultipleRegistersRequest; import com.ghgande.j2mod.modbus.msg.ReadMultipleRegistersResponse; import com.ghgande.j2mod.modbus.procimg.InputRegister; import io.openems.common.exceptions.OpenemsException; -import io.openems.edge.bridge.modbus.api.element.AbstractModbusElement; +import io.openems.edge.bridge.modbus.api.ModbusUtils; +import io.openems.edge.bridge.modbus.api.element.ModbusElement; import io.openems.edge.common.taskmanager.Priority; /** * Implements a Read Holding Register Task, implementing Modbus function code 3 * (http://www.simplymodbus.ca/FC03.htm). */ -public class FC3ReadRegistersTask extends AbstractReadInputRegistersTask implements ReadTask { +public class FC3ReadRegistersTask + extends AbstractReadInputRegistersTask { - public FC3ReadRegistersTask(int startAddress, Priority priority, AbstractModbusElement... elements) { - super(startAddress, priority, elements); + public FC3ReadRegistersTask(int startAddress, Priority priority, ModbusElement... elements) { + super("FC3ReadHoldingRegisters", ReadMultipleRegistersResponse.class, startAddress, priority, elements); } @Override - protected ModbusRequest getRequest() { - return new ReadMultipleRegistersRequest(this.getStartAddress(), this.getLength()); + protected ReadMultipleRegistersRequest createModbusRequest() { + return new ReadMultipleRegistersRequest(this.startAddress, this.length); } @Override - protected InputRegister[] handleResponse(ModbusResponse response) throws OpenemsException { - if (response instanceof ReadMultipleRegistersResponse) { - var registersResponse = (ReadMultipleRegistersResponse) response; - return registersResponse.getRegisters(); - } - throw new OpenemsException("Unexpected Modbus response. Expected [ReadMultipleRegistersResponse], got [" - + response.getClass().getSimpleName() + "]"); + protected InputRegister[] parseResponse(ReadMultipleRegistersResponse response) throws OpenemsException { + return response.getRegisters(); } @Override - protected String getActiondescription() { - return "FC3ReadHoldingRegisters"; + protected String payloadToString(ReadMultipleRegistersResponse response) { + return ModbusUtils.registersToHexString(response.getRegisters()); } } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC4ReadInputRegistersTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC4ReadInputRegistersTask.java index f6f1b4838d3..6e7218c4e73 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC4ReadInputRegistersTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC4ReadInputRegistersTask.java @@ -1,42 +1,37 @@ package io.openems.edge.bridge.modbus.api.task; -import com.ghgande.j2mod.modbus.msg.ModbusRequest; -import com.ghgande.j2mod.modbus.msg.ModbusResponse; import com.ghgande.j2mod.modbus.msg.ReadInputRegistersRequest; import com.ghgande.j2mod.modbus.msg.ReadInputRegistersResponse; import com.ghgande.j2mod.modbus.procimg.InputRegister; import io.openems.common.exceptions.OpenemsException; -import io.openems.edge.bridge.modbus.api.element.AbstractModbusElement; +import io.openems.edge.bridge.modbus.api.ModbusUtils; +import io.openems.edge.bridge.modbus.api.element.ModbusElement; import io.openems.edge.common.taskmanager.Priority; /** * Implements a Read Input Register Task, implementing Modbus function code 4 * (http://www.simplymodbus.ca/FC04.htm). */ -public class FC4ReadInputRegistersTask extends AbstractReadInputRegistersTask implements ReadTask { +public class FC4ReadInputRegistersTask + extends AbstractReadInputRegistersTask { - public FC4ReadInputRegistersTask(int startAddress, Priority priority, AbstractModbusElement... elements) { - super(startAddress, priority, elements); + public FC4ReadInputRegistersTask(int startAddress, Priority priority, ModbusElement... elements) { + super("FC4ReadInputRegisters", ReadInputRegistersResponse.class, startAddress, priority, elements); } @Override - protected String getActiondescription() { - return "FC4ReadInputRegisters"; + protected ReadInputRegistersRequest createModbusRequest() { + return new ReadInputRegistersRequest(this.startAddress, this.length); } @Override - protected ModbusRequest getRequest() { - return new ReadInputRegistersRequest(this.getStartAddress(), this.getLength()); + protected InputRegister[] parseResponse(ReadInputRegistersResponse response) throws OpenemsException { + return response.getRegisters(); } @Override - protected InputRegister[] handleResponse(ModbusResponse response) throws OpenemsException { - if (response instanceof ReadInputRegistersResponse) { - var registersResponse = (ReadInputRegistersResponse) response; - return registersResponse.getRegisters(); - } - throw new OpenemsException("Unexpected Modbus response. Expected [ReadInputRegistersResponse], got [" - + response.getClass().getSimpleName() + "]"); + protected String payloadToString(ReadInputRegistersResponse response) { + return ModbusUtils.registersToHexString(response.getRegisters()); } } \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC5WriteCoilTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC5WriteCoilTask.java index 73beb49ff91..f30dc641d05 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC5WriteCoilTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC5WriteCoilTask.java @@ -1,88 +1,35 @@ package io.openems.edge.bridge.modbus.api.task; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.ghgande.j2mod.modbus.ModbusException; import com.ghgande.j2mod.modbus.msg.WriteCoilRequest; import com.ghgande.j2mod.modbus.msg.WriteCoilResponse; import io.openems.common.exceptions.OpenemsException; -import io.openems.edge.bridge.modbus.api.AbstractModbusBridge; import io.openems.edge.bridge.modbus.api.element.ModbusCoilElement; -import io.openems.edge.bridge.modbus.api.element.ModbusElement; /** * Implements a Write Single Coil Task, using Modbus function code 5 * (http://www.simplymodbus.ca/FC05.htm). */ -public class FC5WriteCoilTask extends AbstractTask implements WriteTask { - - private final Logger log = LoggerFactory.getLogger(FC5WriteCoilTask.class); +public class FC5WriteCoilTask extends AbstractWriteTask.Single { public FC5WriteCoilTask(int startAddress, ModbusCoilElement element) { - super(startAddress, element); + super("FC5WriteCoil", WriteCoilResponse.class, startAddress, element); } @Override - public int _execute(AbstractModbusBridge bridge) throws OpenemsException { - var noOfWrittenCoils = 0; - ModbusElement element = this.getElements()[0]; - if (element instanceof ModbusCoilElement) { - var valueOpt = ((ModbusCoilElement) element).getNextWriteValueAndReset(); - if (valueOpt.isPresent()) { - // found value -> write - boolean value = valueOpt.get(); - try { - /* - * First try - */ - this.writeCoil(bridge, this.getParent().getUnitId(), this.getStartAddress(), value); - noOfWrittenCoils = 1; - } catch (OpenemsException | ModbusException e) { - /* - * Second try: with new connection - */ - bridge.closeModbusConnection(); - try { - this.writeCoil(bridge, this.getParent().getUnitId(), this.getStartAddress(), value); - noOfWrittenCoils = 1; - } catch (ModbusException e2) { - throw new OpenemsException("Transaction failed: " + e.getMessage(), e2); - } - } - } - } else { - this.log.warn("Unable to execute Write for ModbusElement [" + element + "]: No ModbusCoilElement!"); - } - return noOfWrittenCoils; - } + protected WriteCoilRequest createModbusRequest() throws OpenemsException { + var valueOpt = this.element.getNextWriteValueAndReset(); + if (valueOpt.isPresent()) { + boolean value = valueOpt.get(); + return new WriteCoilRequest(startAddress, value); - private void writeCoil(AbstractModbusBridge bridge, int unitId, int startAddress, boolean value) - throws OpenemsException, ModbusException { - var request = new WriteCoilRequest(startAddress, value); - var response = Utils.getResponse(request, unitId, bridge); - - // debug output - switch (this.getLogVerbosity(bridge)) { - case READS_AND_WRITES: - bridge.logInfo(this.log, "FC5WriteCoil " // - + "[" + unitId + ":" + startAddress + "/0x" + Integer.toHexString(startAddress) + "]: " // - + value); - break; - case WRITES: - case NONE: - break; - } - - if (!(response instanceof WriteCoilResponse)) { - throw new OpenemsException("Unexpected Modbus response. Expected [WriteCoilResponse], got [" - + response.getClass().getSimpleName() + "]"); + } else { + return null; } } @Override - protected String getActiondescription() { - return "FC5 WriteCoil"; + protected String payloadToString(WriteCoilRequest request) { + return request.getCoil() ? "ON" : "OFF"; } } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC6WriteRegisterTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC6WriteRegisterTask.java index 28e0564c140..7aeffd4836b 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC6WriteRegisterTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC6WriteRegisterTask.java @@ -1,97 +1,41 @@ package io.openems.edge.bridge.modbus.api.task; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.ghgande.j2mod.modbus.ModbusException; import com.ghgande.j2mod.modbus.msg.WriteSingleRegisterRequest; import com.ghgande.j2mod.modbus.msg.WriteSingleRegisterResponse; -import com.ghgande.j2mod.modbus.procimg.Register; import io.openems.common.exceptions.OpenemsException; -import io.openems.edge.bridge.modbus.api.AbstractModbusBridge; -import io.openems.edge.bridge.modbus.api.element.AbstractModbusElement; +import io.openems.edge.bridge.modbus.api.ModbusUtils; import io.openems.edge.bridge.modbus.api.element.AbstractWordElement; -import io.openems.edge.bridge.modbus.api.element.ModbusElement; - -public class FC6WriteRegisterTask extends AbstractTask implements WriteTask { - private final Logger log = LoggerFactory.getLogger(FC6WriteRegisterTask.class); +public class FC6WriteRegisterTask extends + AbstractWriteTask.Single> { - public FC6WriteRegisterTask(int startAddress, AbstractModbusElement element) { - super(startAddress, element); + public FC6WriteRegisterTask(int startAddress, AbstractWordElement element) { + super("FC6WriteRegister", WriteSingleRegisterResponse.class, startAddress, element); } @Override - public int _execute(AbstractModbusBridge bridge) throws OpenemsException { - var noOfWrittenRegisters = 0; - ModbusElement element = this.getElements()[0]; - - if (element instanceof AbstractWordElement) { - - var valueOpt = ((AbstractWordElement) element).getNextWriteValueAndReset(); - if (valueOpt.isPresent()) { - var registers = valueOpt.get(); - - if (registers.length == 1 && registers[0] != null) { - // found value -> write - var register = registers[0]; - try { - /* - * First try - */ - - this.writeSingleRegister(bridge, this.getParent().getUnitId(), this.getStartAddress(), - register); - noOfWrittenRegisters = 1; - } catch (OpenemsException | ModbusException e) { - /* - * Second try: with new connection - */ - bridge.closeModbusConnection(); - try { - this.writeSingleRegister(bridge, this.getParent().getUnitId(), this.getStartAddress(), - register); - noOfWrittenRegisters = 1; - } catch (ModbusException e2) { - throw new OpenemsException("Transaction failed: " + e.getMessage(), e2); - } - } - } else { - this.log.warn("Expecting exactly one register. Got [" + registers.length + "]"); - } + protected WriteSingleRegisterRequest createModbusRequest() throws OpenemsException { + var valueOpt = this.element.getNextWriteValueAndReset(); + if (valueOpt.isPresent()) { + var registers = valueOpt.get(); + + if (registers.length == 1 && registers[0] != null) { + // found value -> write + var register = registers[0]; + return new WriteSingleRegisterRequest(this.startAddress, register); + + } else { + throw new OpenemsException("Expected exactly one register. Got [" + registers.length + "]"); } + } else { - this.log.warn("Unable to execute Write for ModbusElement [" + element + "]: No AbstractWordElement!"); + return null; } - return noOfWrittenRegisters; } @Override - protected String getActiondescription() { - return "FC6 WriteRegister"; - } - - private void writeSingleRegister(AbstractModbusBridge bridge, int unitId, int startAddress, Register register) - throws ModbusException, OpenemsException { - var request = new WriteSingleRegisterRequest(startAddress, register); - var response = Utils.getResponse(request, unitId, bridge); - - // debug output - switch (this.getLogVerbosity(bridge)) { - case READS_AND_WRITES: - case WRITES: - bridge.logInfo(this.log, "FC6WriteRegister " // - + "[" + unitId + ":" + startAddress + "/0x" + Integer.toHexString(startAddress) + "]: " // - + String.format("%4s", Integer.toHexString(register.getValue())).replace(' ', '0')); - break; - case NONE: - break; - } - - if (!(response instanceof WriteSingleRegisterResponse)) { - throw new OpenemsException("Unexpected Modbus response. Expected [WriteSingleRegisterResponse], got [" - + response.getClass().getSimpleName() + "]"); - } + protected String payloadToString(WriteSingleRegisterRequest request) { + return ModbusUtils.registersToHexString(request.getRegister()); } } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/ReadTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/ReadTask.java index e26d868f1e0..b77a6e87f6a 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/ReadTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/ReadTask.java @@ -1,7 +1,6 @@ package io.openems.edge.bridge.modbus.api.task; import io.openems.edge.bridge.modbus.api.element.AbstractModbusElement; -import io.openems.edge.common.taskmanager.ManagedTask; /** * A Modbus 'ReadTask' is holding references to one or more Modbus @@ -9,5 +8,5 @@ * range. The ReadTask handles the execution (query) on this range. @{link * WriteTask} inherits from ReadTask. */ -public interface ReadTask extends Task, ManagedTask { +public non-sealed interface ReadTask extends Task { } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/Task.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/Task.java index 3477c22e426..44baa414d72 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/Task.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/Task.java @@ -1,41 +1,41 @@ package io.openems.edge.bridge.modbus.api.task; -import io.openems.common.exceptions.OpenemsException; import io.openems.edge.bridge.modbus.api.AbstractModbusBridge; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.ModbusComponent; import io.openems.edge.bridge.modbus.api.element.ModbusElement; import io.openems.edge.common.taskmanager.ManagedTask; -public interface Task extends ManagedTask { +@SuppressWarnings("rawtypes") +public sealed interface Task extends ManagedTask permits AbstractTask, ReadTask, WriteTask, WaitTask { /** * Gets the ModbusElements. * * @return an array of ModbusElements */ - ModbusElement[] getElements(); + public ModbusElement[] getElements(); /** * Gets the start Modbus register address. * * @return the address */ - int getStartAddress(); + public int getStartAddress(); /** * Gets the length from first to last Modbus register address. * * @return the address */ - int getLength(); + public int getLength(); /** * Sets the parent. * * @param parent the parent {@link AbstractOpenemsModbusComponent}. */ - void setParent(AbstractOpenemsModbusComponent parent); + public void setParent(AbstractOpenemsModbusComponent parent); /** * Gets the parent. @@ -48,32 +48,23 @@ public interface Task extends ManagedTask { * This is called on deactivate of the Modbus-Bridge. It can be used to clear * any references like listeners. */ - void deactivate(); + public void deactivate(); /** * Executes the tasks - i.e. sends the query of a ReadTask or writes a * WriteTask. * * @param bridge the Modbus-Bridge - * @param the Modbus-Element - * @throws OpenemsException on error - * @return the number of executed Sub-Tasks + * @return {@link ExecuteState} */ - int execute(AbstractModbusBridge bridge) throws OpenemsException; - - /** - * Gets whether this ReadTask has been successfully executed before. - * - * @return true if this Task has been executed successfully at least once - */ - boolean hasBeenExecuted(); - - /** - * Gets the execution duration of the last execution (successful or not not - * successful) in [ms]. - * - * @return the duration in [ms] - */ - long getExecuteDuration(); + public ExecuteState execute(AbstractModbusBridge bridge); + public static enum ExecuteState { + /** Successfully executed request(s). */ + OK, + /** No available requests -> no operation. */ + NO_OP, + /** Executing request(s) failed. */ + ERROR; + } } \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/Utils.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/Utils.java deleted file mode 100644 index 18a7f5c52e1..00000000000 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/Utils.java +++ /dev/null @@ -1,96 +0,0 @@ -package io.openems.edge.bridge.modbus.api.task; - -import java.util.Arrays; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -import com.ghgande.j2mod.modbus.ModbusException; -import com.ghgande.j2mod.modbus.msg.ModbusRequest; -import com.ghgande.j2mod.modbus.msg.ModbusResponse; -import com.ghgande.j2mod.modbus.procimg.InputRegister; -import com.ghgande.j2mod.modbus.util.BitVector; - -import io.openems.common.exceptions.OpenemsException; -import io.openems.edge.bridge.modbus.api.AbstractModbusBridge; - -public class Utils { - - /** - * Convert a {@link BitVector} to a {@link Boolean} array. - * - * @param v the {@link BitVector} - * @return the {@link Boolean} array - */ - public static Boolean[] toBooleanArray(BitVector v) { - var bools = new Boolean[v.size()]; - for (var i = 0; i < v.size(); i++) { - bools[i] = v.getBit(i); - } - return bools; - } - - /** - * Convert a byte array to a {@link Boolean} array. - * - * @param bytes the byte array - * @return the {@link Boolean} array - */ - public static Boolean[] toBooleanArray(byte[] bytes) { - var bools = new Boolean[bytes.length * 8]; - for (var i = 0; i < bytes.length * 8; i++) { - var byteIndex = i / 8; - bools[i] = (bytes[byteIndex] & (byte) (128 / Math.pow(2, i % 8))) != 0; - } - return bools; - } - - /** - * Build a {@link ModbusResponse} from a {@link ModbusRequest}. - * - * @param request the {@link ModbusRequest} - * @param unitId the Modbus Unit-ID - * @param bridge the {@link AbstractModbusBridge} - * @return the {@link ModbusResponse} - */ - public static ModbusResponse getResponse(ModbusRequest request, int unitId, AbstractModbusBridge bridge) - throws OpenemsException, ModbusException { - request.setUnitID(unitId); - var transaction = bridge.getNewModbusTransaction(); - transaction.setRequest(request); - transaction.execute(); - return transaction.getResponse(); - } - - /** - * Converts an array of {@link InputRegister}s to a String. - * - * @param registers the {@link InputRegister} array - * @return the String - */ - public static String toBitString(InputRegister[] registers) { - return Arrays.stream(registers).map(register -> { - var bs = register.toBytes(); - - return String.format("%8s", // - Integer.toBinaryString(bs[0] & 0xFF)).replace(' ', '0') // - + " " // - + String.format("%8s", // - Integer.toBinaryString(bs[1] & 0xFF)).replace(' ', '0'); - }).collect(Collectors.joining(" ")); - } - - /** - * Converts an array of bytes to a String. - * - * @param bs the byte array - * @return the String - */ - public static String toBitString(byte[] bs) { - return IntStream // - .range(0, bs.length) // - .map(idx -> bs[idx]) // - .mapToObj(b -> String.format("%8s", // - Integer.toBinaryString((byte) b & 0xFF)).replace(' ', '0')) - .collect(Collectors.joining(" ")); - } -} diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/WaitTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/WaitTask.java index a1a8e5e087b..817da169482 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/WaitTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/WaitTask.java @@ -1,77 +1,121 @@ package io.openems.edge.bridge.modbus.api.task; +import java.time.Duration; +import java.time.Instant; +import java.util.concurrent.TimeUnit; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.openems.common.exceptions.OpenemsException; import io.openems.edge.bridge.modbus.api.AbstractModbusBridge; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.element.ModbusElement; import io.openems.edge.common.taskmanager.Priority; -public class WaitTask implements Task { +public abstract non-sealed class WaitTask implements Task { private final Logger log = LoggerFactory.getLogger(WaitTask.class); - private final long delay; - private AbstractOpenemsModbusComponent parent = null; - public WaitTask(long delay) { - this.delay = delay; - } - @Override - public Priority getPriority() { - return Priority.LOW; + public final void setParent(AbstractOpenemsModbusComponent parent) { + this.parent = parent; } @Override - public ModbusElement[] getElements() { - return new ModbusElement[0]; + public final AbstractOpenemsModbusComponent getParent() { + return this.parent; } @Override - public int getStartAddress() { - return 0; + public final Priority getPriority() { + return Priority.LOW; } @Override - public int getLength() { - return 0; + public final ModbusElement[] getElements() { + return new ModbusElement[0]; } @Override - public void setParent(AbstractOpenemsModbusComponent parent) { - this.parent = parent; + public final int getStartAddress() { + return 0; } @Override - public AbstractOpenemsModbusComponent getParent() { - return this.parent; + public final int getLength() { + return 0; } @Override - public void deactivate() { + public final void deactivate() { } @Override - public int execute(AbstractModbusBridge bridge) throws OpenemsException { + public final ExecuteState execute(AbstractModbusBridge bridge) { try { - Thread.sleep(this.delay); + this._execute(); } catch (InterruptedException e) { - this.log.warn(e.getMessage()); + this.log.info(this.toString() + " interrupted: " + e.getMessage()); } - return 0; + return ExecuteState.NO_OP; } - @Override - public boolean hasBeenExecuted() { - return true; - } + protected abstract void _execute() throws InterruptedException; - @Override - public long getExecuteDuration() { - return this.delay; + public static class Mutex extends WaitTask { + + private final io.openems.common.utils.Mutex mutex = new io.openems.common.utils.Mutex(false); + + /** Release the Mutex, i.e. interrupt waiting. */ + public void release() { + this.mutex.release(); + } + + @Override + protected void _execute() throws InterruptedException { + this.mutex.awaitOrTimeout(0, TimeUnit.MILLISECONDS); // throw away active release + this.mutex.await(); + } + + @Override + public String toString() { + return "WaitTask.Mutex []"; + } } -} \ No newline at end of file + public static class Delay extends WaitTask { + + public final long initialDelay; + + private final Runnable onFinished; + + private long delay; + + public Delay(long delay, Runnable onFinished) { + this.initialDelay = this.delay = delay; + this.onFinished = onFinished; + } + + @Override + protected void _execute() throws InterruptedException { + var start = Instant.now(); + try { + if (this.delay > 0) { + Thread.sleep(this.delay); + } + } finally { + this.delay -= Duration.between(start, Instant.now()).toMillis(); + + if (this.delay <= 0) { + this.onFinished.run(); + } + } + } + + @Override + public String toString() { + return "WaitDelayTask [delay=" + this.delay + "]"; + } + } +} diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/WriteTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/WriteTask.java index d62ef072f9c..1a52dff24bd 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/WriteTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/WriteTask.java @@ -1,17 +1,5 @@ package io.openems.edge.bridge.modbus.api.task; -import io.openems.edge.common.taskmanager.ManagedTask; -import io.openems.edge.common.taskmanager.Priority; +public non-sealed interface WriteTask extends Task { -public interface WriteTask extends Task, ManagedTask { - - /** - * Priority for WriteTasks is by default always HIGH. - * - * @return the Priority - */ - @Override - public default Priority getPriority() { - return Priority.HIGH; - } } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/ModbusWorker.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/ModbusWorker.java index 4d483fa9428..56def5a2b05 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/ModbusWorker.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/ModbusWorker.java @@ -1,31 +1,21 @@ package io.openems.edge.bridge.modbus.api.worker; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Deque; -import java.util.LinkedList; -import java.util.List; -import java.util.concurrent.LinkedBlockingDeque; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.Function; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.base.Stopwatch; -import com.google.common.collect.Multimap; - -import io.openems.common.exceptions.OpenemsException; import io.openems.common.worker.AbstractImmediateWorker; -import io.openems.edge.bridge.modbus.api.AbstractModbusBridge; +import io.openems.edge.bridge.modbus.api.BridgeModbus; +import io.openems.edge.bridge.modbus.api.LogVerbosity; +import io.openems.edge.bridge.modbus.api.ModbusComponent; import io.openems.edge.bridge.modbus.api.ModbusProtocol; import io.openems.edge.bridge.modbus.api.element.ModbusElement; -import io.openems.edge.bridge.modbus.api.task.ReadTask; import io.openems.edge.bridge.modbus.api.task.Task; -import io.openems.edge.bridge.modbus.api.task.WaitTask; -import io.openems.edge.bridge.modbus.api.task.WriteTask; -import io.openems.edge.common.component.OpenemsComponent; -import io.openems.edge.common.taskmanager.MetaTasksManager; -import io.openems.edge.common.taskmanager.Priority; +import io.openems.edge.bridge.modbus.api.task.Task.ExecuteState; +import io.openems.edge.bridge.modbus.api.worker.internal.CycleTasks; +import io.openems.edge.bridge.modbus.api.worker.internal.CycleTasksManager; +import io.openems.edge.bridge.modbus.api.worker.internal.DefectiveComponents; +import io.openems.edge.bridge.modbus.api.worker.internal.TasksSupplierImpl; /** * The ModbusWorker schedules the execution of all Modbus-Tasks, like reading @@ -34,273 +24,144 @@ *

* It tries to execute all Write-Tasks as early as possible (directly after the * TOPIC_CYCLE_EXECUTE_WRITE event) and all Read-Tasks as late as possible to - * have correct values available exactly when they are needed (i.e. at the - * TOPIC_CYCLE_BEFORE_PROCESS_IMAGE event). + * have values available exactly when they are needed (i.e. at the + * TOPIC_CYCLE_BEFORE_PROCESS_IMAGE event). For this it uses a + * {@link CycleTasksManager} that internally uses a {@link TasksSupplierImpl} + * that supplies the tasks for one Cycle ({@link CycleTasks}). */ public class ModbusWorker extends AbstractImmediateWorker { - private static final long TASK_DURATION_BUFFER = 50; - - private final Logger log = LoggerFactory.getLogger(ModbusWorker.class); - private final Stopwatch stopwatch = Stopwatch.createUnstarted(); - private final LinkedBlockingDeque tasksQueue = new LinkedBlockingDeque<>(); - private final MetaTasksManager readTasksManager = new MetaTasksManager<>(); - private final MetaTasksManager writeTasksManager = new MetaTasksManager<>(); - private final AbstractModbusBridge parent; + // Callbacks + private final Function execute; + private final Consumer[]> invalidate; - // The measured duration between BeforeProcessImage event and ExecuteWrite event - private long durationBetweenBeforeProcessImageTillExecuteWrite = 0; - - public ModbusWorker(AbstractModbusBridge parent) { - this.parent = parent; - } + private final DefectiveComponents defectiveComponents; + private final TasksSupplierImpl tasksSupplier; + private final CycleTasksManager cycleTasksManager; /** - * This is called on TOPIC_CYCLE_BEFORE_PROCESS_IMAGE cycle event. + * Constructor for {@link ModbusWorker}. + * + * @param execute executes a {@link Task}; returns number of + * actually executed subtasks + * @param invalidate invalidates the given + * {@link ModbusElement}s after read errors + * @param cycleTimeIsTooShortChannel sets the + * {@link BridgeModbus.ChannelId#CYCLE_TIME_IS_TOO_SHORT} + * channel + * @param cycleDelayChannel sets the + * {@link BridgeModbus.ChannelId#CYCLE_DELAY} + * channel + * @param logVerbosity the configured {@link LogVerbosity} */ - public synchronized void onBeforeProcessImage() { - this.stopwatch.reset(); - this.stopwatch.start(); + public ModbusWorker(Function execute, Consumer[]> invalidate, + Consumer cycleTimeIsTooShortChannel, Consumer cycleDelayChannel, + AtomicReference logVerbosity) { + this.execute = execute; + this.invalidate = invalidate; + + this.defectiveComponents = new DefectiveComponents(logVerbosity); + this.tasksSupplier = new TasksSupplierImpl(); + this.cycleTasksManager = new CycleTasksManager(this.tasksSupplier, this.defectiveComponents, + cycleTimeIsTooShortChannel, cycleDelayChannel, logVerbosity); + } - // If the current tasks queue spans multiple cycles and we are in-between -> - // stop here - if (!this.tasksQueue.isEmpty()) { - return; - } + @Override + protected void forever() throws InterruptedException { + var task = this.cycleTasksManager.getNextTask(); - // Collect the next read-tasks - List nextReadTasks = new ArrayList<>(); - var lowPriorityTask = this.getOneLowPriorityReadTask(); - if (lowPriorityTask != null) { - nextReadTasks.add(lowPriorityTask); - } - nextReadTasks.addAll(this.getAllHighPriorityReadTasks()); - var readTasksDuration = 0L; - for (ReadTask task : nextReadTasks) { - readTasksDuration += task.getExecuteDuration(); - } + // execute the task + var result = this.execute.apply(task); - // collect the next write-tasks - var writeTasksDuration = 0L; - var nextWriteTasks = this.getAllWriteTasks(); - for (WriteTask task : nextWriteTasks) { - writeTasksDuration += task.getExecuteDuration(); + switch (result) { + case OK -> { + // no exception & at least one sub-task executed + this.markComponentAsDefective(task.getParent(), false); } - // plan the execution for the next cycles - var totalDuration = readTasksDuration + writeTasksDuration; - var totalDurationWithBuffer = totalDuration + TASK_DURATION_BUFFER; - var cycleTime = this.parent.getCycle().getCycleTime(); - var noOfRequiredCycles = ceilDiv(totalDurationWithBuffer, cycleTime); - - // Set EXECUTION_DURATION channel - this.parent._setExecutionDuration(totalDuration); + case ERROR -> { + this.markComponentAsDefective(task.getParent(), true); - // Set CYCLE_TIME_IS_TOO_SHORT state-channel - if (noOfRequiredCycles > 1) { - this.parent._setCycleTimeIsTooShort(true); - } else { - this.parent._setCycleTimeIsTooShort(false); + // invalidate elements of this task + this.invalidate.accept(task.getElements()); } - var durationOfTasksBeforeExecuteWriteEvent = 0L; - var noOfTasksBeforeExecuteWriteEvent = 0; - for (ReadTask task : nextReadTasks) { - if (durationOfTasksBeforeExecuteWriteEvent > this.durationBetweenBeforeProcessImageTillExecuteWrite) { - break; - } - noOfTasksBeforeExecuteWriteEvent++; - durationOfTasksBeforeExecuteWriteEvent += task.getExecuteDuration(); + case NO_OP -> { } - - // Build Queue - Deque tasksQueue = new LinkedList<>(); - - // Add all write-tasks to the queue - tasksQueue.addAll(nextWriteTasks); - - // Add all read-tasks to the queue - for (var i = 0; i < nextReadTasks.size(); i++) { - var task = nextReadTasks.get(i); - if (i < noOfTasksBeforeExecuteWriteEvent) { - // this Task will be executed before ExecuteWrite event -> add it to the end of - // the queue - tasksQueue.addLast(task); - } else { - // this Task will be executed after ExecuteWrite event -> add it to the - // beginning of the queue - tasksQueue.addFirst(task); - } } - // Add a waiting-task to the end of the queue - var waitTillStart = noOfRequiredCycles * cycleTime - totalDurationWithBuffer; - tasksQueue.addLast(new WaitTask(waitTillStart)); - - // Copy all Tasks to the global tasks-queue - this.tasksQueue.clear(); - this.tasksQueue.addAll(tasksQueue); } /** - * This is called on TOPIC_CYCLE_EXECUTE_WRITE cycle event. + * Marks the given {@link ModbusComponent} as defective or non-defective. + * + *

+ * + * @param component the {@link ModbusComponent} + * @param isDefective mark as defective (true) or non-defective (false) */ - public synchronized void onExecuteWrite() { - // calculate the duration between BeforeProcessImage event and ExecuteWrite - // event. This duration is used for planning the queue in onBeforeProcessImage() - if (this.stopwatch.isRunning()) { - this.durationBetweenBeforeProcessImageTillExecuteWrite = this.stopwatch.elapsed(TimeUnit.MILLISECONDS); - } else { - this.durationBetweenBeforeProcessImageTillExecuteWrite = 0; - } - } - - @Override - protected void forever() throws InterruptedException { - var task = this.tasksQueue.takeLast(); - - // If there are no tasks in the bridge, there will always be only one - // 'WaitTask'. - if (task instanceof WaitTask && !this.hasTasks()) { - return; - } - - var modbusComponent = task.getParent(); - try { - // execute the task - var noOfExecutedSubTasks = task.execute(this.parent); + private void markComponentAsDefective(ModbusComponent component, boolean isDefective) { + if (component != null) { + if (isDefective) { + // Component is defective + this.defectiveComponents.add(component.id()); + component._setModbusCommunicationFailed(true); - if (noOfExecutedSubTasks > 0) { - // no exception & at least one sub-task executed -> remove this component from - // erroneous list and set the CommunicationFailedChannel to false - if (modbusComponent != null) { - modbusComponent._setModbusCommunicationFailed(false); - } - } - - } catch (OpenemsException e) { - OpenemsComponent.logWarn(this.parent, this.log, task.toString() + " execution failed: " + e.getMessage()); - - // mark this component as erroneous - if (modbusComponent != null) { - modbusComponent._setModbusCommunicationFailed(true); - } - - // invalidate elements of this task - for (ModbusElement element : task.getElements()) { - element.invalidate(this.parent); + } else { + // Read from/Write to Component was successful + this.defectiveComponents.remove(component.id()); + component._setModbusCommunicationFailed(false); } } } /** - * Gets one Read-Tasks with priority Low or Once. + * Adds the protocol. * - * @return a list of ReadTasks by Source-ID + * @param sourceId Component-ID of the source + * @param protocol the ModbusProtocol */ - private ReadTask getOneLowPriorityReadTask() { - var lowPriorityTask = this.readTasksManager.getOneTask(Priority.LOW); - return lowPriorityTask; + public void addProtocol(String sourceId, ModbusProtocol protocol) { + this.tasksSupplier.addProtocol(sourceId, protocol); + this.defectiveComponents.remove(sourceId); // Cleanup } /** - * Gets all the High-Priority Read-Tasks. - * - *

- * This checks if a device is listed as defective and - if it is - adds only one - * ReadTask of this Source-Component to the queue + * Removes the protocol. * - * @return a list of ReadTasks + * @param sourceId Component-ID of the source */ - private List getAllHighPriorityReadTasks() { - var tasks = this.readTasksManager.getAllTasksBySourceId(Priority.HIGH); - return this.filterDefectiveComponents(tasks); + public void removeProtocol(String sourceId) { + this.tasksSupplier.removeProtocol(sourceId); + this.defectiveComponents.remove(sourceId); // Cleanup } /** - * Gets the Write-Tasks by Source-ID. - * + * Retry Modbus communication to given Component-ID. + * *

- * This checks if a device is listed as defective and - if it is - adds only one - * WriteTask of this Source-Component to the queue - * - * @return a list of WriteTasks by Source-ID - */ - private List getAllWriteTasks() { - var tasks = this.writeTasksManager.getAllTasksBySourceId(); - return this.filterDefectiveComponents(tasks); - } - - /** - * Does this {@link ModbusWorker} have any Tasks?. - * - * @return true if there are Tasks - */ - private boolean hasTasks() { - return this.writeTasksManager.hasTasks() && this.readTasksManager.hasTasks(); - } - - /** - * Filters a Multimap with Tasks by Component-ID. For Components that are known - * to be defective, only one task is added; otherwise all tasks are added to the - * result. The idea is to not execute tasks that are known to fail. - * - * @param the Task type - * @param tasks Tasks by Component-ID - * @return a list of filtered tasks - */ - private List filterDefectiveComponents(Multimap tasks) { - List result = new ArrayList<>(); - for (Collection tasksOfComponent : tasks.asMap().values()) { - var iterator = tasksOfComponent.iterator(); - if (iterator.hasNext()) { - var task = iterator.next(); // get first task - var modbusComponent = task.getParent(); - if (modbusComponent.getModbusCommunicationFailed().get() == Boolean.TRUE) { - // Component is known to be erroneous -> add only one Task - result.add(task); - } else { - // Component is ok. All all tasks. - result.addAll(tasksOfComponent); - } - } - } - return result; - } - - /** - * Adds the protocol. - * + * See {@link BridgeModbus#retryModbusCommunication(String)} + * * @param sourceId Component-ID of the source - * @param protocol the ModbusProtocol */ - public void addProtocol(String sourceId, ModbusProtocol protocol) { - this.readTasksManager.addTasksManager(sourceId, protocol.getReadTasksManager()); - this.writeTasksManager.addTasksManager(sourceId, protocol.getWriteTasksManager()); + public void retryModbusCommunication(String sourceId) { + this.defectiveComponents.remove(sourceId); } /** - * Removes the protocol. - * - * @param sourceId Component-ID of the source + * Called on EXECUTE_WRITE event. */ - public void removeProtocol(String sourceId) { - this.readTasksManager.removeTasksManager(sourceId); - this.writeTasksManager.removeTasksManager(sourceId); + public void onExecuteWrite() { + this.cycleTasksManager.onExecuteWrite(); } /** - * This is a helper function. It calculates the opposite of Math.floorDiv(). - * - *

- * Source: - * https://stackoverflow.com/questions/27643616/ceil-conterpart-for-math-floordiv-in-java - * - * @param x the dividend - * @param y the divisor - * @return the result of the division, rounded up + * Called on BEFORE_PROCESS_IMAGE event. */ - private static long ceilDiv(long x, long y) { - return -Math.floorDiv(-x, y); + public void onBeforeProcessImage() { + this.cycleTasksManager.onBeforeProcessImage(); } } \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasks.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasks.java new file mode 100644 index 00000000000..633c114f8fa --- /dev/null +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasks.java @@ -0,0 +1,71 @@ +package io.openems.edge.bridge.modbus.api.worker.internal; + +import java.util.LinkedList; +import java.util.Objects; +import java.util.stream.Stream; + +import io.openems.edge.bridge.modbus.api.task.ReadTask; +import io.openems.edge.bridge.modbus.api.task.WriteTask; + +/** + * Holds the Read- and Write-Tasks for one Cycle. + */ +public record CycleTasks(LinkedList reads, LinkedList writes) { + + public static class Builder { + private final LinkedList reads = new LinkedList<>(); + private final LinkedList writes = new LinkedList<>(); + + private Builder() { + } + + /** + * Adds {@link ReadTask}s. + * + * @param tasks the tasks + * @return myself + */ + public Builder reads(ReadTask... tasks) { + Stream.of(tasks).forEach(this.reads::add); + return this; + } + + /** + * Adds {@link WriteTask}s. + * + * @param tasks the tasks + * @return myself + */ + public Builder writes(WriteTask... tasks) { + Stream.of(tasks).forEach(this.writes::add); + return this; + } + + public CycleTasks build() { + return new CycleTasks(this.reads, this.writes); + } + } + + /** + * Create a Config builder. + * + * @return a {@link Builder} + */ + public static Builder create() { + return new Builder(); + } + + /** + * Is any of the tasks belonging to a Component that is known to be defective?. + * + * @param defectiveComponents the {@link DefectiveComponents} + * @return true for defective; false otherwise + */ + public boolean containsDefectiveComponent(DefectiveComponents defectiveComponents) { + return Stream.concat(this.reads.stream(), this.writes.stream()) // + .map(t -> t.getParent()) // + .filter(Objects::nonNull) // + .map(p -> p.id()) // + .anyMatch(c -> defectiveComponents.isKnown(c)); + } +} diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManager.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManager.java new file mode 100644 index 00000000000..fbee14394ac --- /dev/null +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManager.java @@ -0,0 +1,222 @@ +package io.openems.edge.bridge.modbus.api.worker.internal; + +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.openems.edge.bridge.modbus.api.LogVerbosity; +import io.openems.edge.bridge.modbus.api.task.Task; +import io.openems.edge.bridge.modbus.api.task.WaitTask; +import io.openems.edge.bridge.modbus.api.worker.ModbusWorker; + +/** + * Manages the Read-, Write- and Wait-Tasks for one Cycle. + * + *

+ *

  • {@link #onBeforeProcessImage()} initialize the next Cycle if previous + * Cycle had finished + *
  • {@link #onExecuteWrite()} puts Write-Tasks as highest priority + */ +public class CycleTasksManager { + + private final Logger log = LoggerFactory.getLogger(CycleTasksManager.class); + + private final TasksSupplier tasksSupplier; + private final DefectiveComponents defectiveComponents; + private final Consumer cycleTimeIsTooShortChannel; + private final AtomicReference logVerbosity; + + private final WaitDelayHandler waitDelayHandler; + private final WaitTask.Mutex waitMutexTask = new WaitTask.Mutex(); + + private CycleTasks cycleTasks; + + public CycleTasksManager(TasksSupplier tasksSupplier, DefectiveComponents defectiveComponents, + Consumer cycleTimeIsTooShortChannel, Consumer cycleDelayChannel, + AtomicReference logVerbosity) { + this.tasksSupplier = tasksSupplier; + this.defectiveComponents = defectiveComponents; + this.cycleTimeIsTooShortChannel = cycleTimeIsTooShortChannel; + this.logVerbosity = logVerbosity; + + this.waitDelayHandler = new WaitDelayHandler(() -> this.onWaitDelayTaskFinished(), cycleDelayChannel); + } + + protected CycleTasksManager(TasksSupplier tasksSupplier, DefectiveComponents defectiveComponents, + Consumer cycleTimeIsTooShortChannel, Consumer cycleDelayChannel) { + this(tasksSupplier, defectiveComponents, cycleTimeIsTooShortChannel, cycleDelayChannel, + new AtomicReference<>(LogVerbosity.NONE)); + } + + private static enum StateMachine { + INITIAL_WAIT, // + READ_BEFORE_WRITE, // + WAIT_FOR_WRITE, // + WRITE, // + WAIT_BEFORE_READ, // + READ_AFTER_WRITE, // + FINISHED + } + + private StateMachine state = StateMachine.FINISHED; + + /** + * Called on BEFORE_PROCESS_IMAGE event. + */ + public synchronized void onBeforeProcessImage() { + // Calculate Delay + var waitDelayHandlerLog = this.waitDelayHandler.onBeforeProcessImage(this.isTraceLog()); + + // Evaluate Cycle-Time-Is-Too-Short, invalidate time measurement and stop early + var cycleTimeIsTooShort = this.state != StateMachine.FINISHED; + this.cycleTimeIsTooShortChannel.accept(cycleTimeIsTooShort); + if (cycleTimeIsTooShort) { + this.waitDelayHandler.timeIsInvalid(); + if (this.isTraceLog()) { + this.log.info("State: " + this.state + " unchanged" // + + " (in onBeforeProcessImage)" // + + " Delay [" + this.waitDelayHandler.getWaitDelayTask().initialDelay + "] " // + + waitDelayHandlerLog); + } + return; + } + + // Update WaitDelayHandler Queue size + this.waitDelayHandler.updateTotalNumberOfTasks(this.tasksSupplier.getTotalNumberOfTasks()); + + // Fill queues for this Cycle + this.cycleTasks = this.tasksSupplier.getCycleTasks(this.defectiveComponents); + + // On defectiveComponents invalidate time measurement + if (this.cycleTasks.containsDefectiveComponent(this.defectiveComponents)) { + this.waitDelayHandler.timeIsInvalid(); + waitDelayHandlerLog += " DEFECTIVE_COMPONENT"; + } + + // Initialize next Cycle + if (this.isTraceLog()) { + this.log.info("State: " + this.state + " -> " + StateMachine.INITIAL_WAIT // + + " (in onBeforeProcessImage)" // + + " Delay [" + this.waitDelayHandler.getWaitDelayTask().initialDelay + "] " // + + waitDelayHandlerLog); + } + this.state = StateMachine.INITIAL_WAIT; + + // Interrupt wait + this.waitMutexTask.release(); + } + + /** + * Called on EXECUTE_WRITE event. + */ + public synchronized void onExecuteWrite() { + if (this.isTraceLog()) { + this.log.info("State: " + this.state + " -> " + StateMachine.WRITE + " (onExecuteWrite)"); + } + + this.state = StateMachine.WRITE; + this.waitMutexTask.release(); + } + + /** + * Gets the next {@link Task}. This is called in a separate Thread by + * {@link ModbusWorker}. + * + * @return next {@link Task} + */ + public Task getNextTask() { + if (this.cycleTasks == null) { + return this.waitMutexTask; + } + + var previousState = this.state; // drop before release + + var nextTask = switch (this.state) { + + case INITIAL_WAIT -> + // Waiting for planned waiting time to pass + this.waitDelayHandler.getWaitDelayTask(); + + case READ_BEFORE_WRITE -> { + // Read-Task available? + var task = this.cycleTasks.reads().poll(); + if (task != null) { + yield task; + } + // Otherwise -> next state + recursive call + this.state = StateMachine.WAIT_FOR_WRITE; + yield this.getNextTask(); + } + + case WAIT_FOR_WRITE -> + // Waiting for EXECUTE_WRITE event + this.waitMutexTask; + + case WRITE -> { + // Write-Task available? + var task = this.cycleTasks.writes().poll(); + if (task != null) { + yield task; + } + // Otherwise -> next state + recursive call + this.state = StateMachine.WAIT_BEFORE_READ; + yield this.getNextTask(); + } + + case WAIT_BEFORE_READ -> + // Waiting for planned waiting time to pass + this.waitDelayHandler.getWaitDelayTask(); + + case READ_AFTER_WRITE -> { + // Read-Task available? + var task = this.cycleTasks.reads().poll(); + if (task != null) { + yield task; + } + // Otherwise -> next state + recursive call + this.state = StateMachine.FINISHED; + yield this.getNextTask(); + } + + case FINISHED -> { + this.waitDelayHandler.onFinished(); + // Waiting for BEFORE_PROCESS_IMAGE event + yield this.waitMutexTask; + } + }; + + if (this.state != previousState && this.isTraceLog()) { + this.log.info("State: " + previousState + " -> " + this.state + " (getNextTask)"); + } + return nextTask; + } + + /** + * Waiting in INITIAL_WAIT or WAIT_BEFORE_READ finished. + */ + private synchronized void onWaitDelayTaskFinished() { + var previousState = this.state; + this.state = switch (this.state) { + // Expected + case INITIAL_WAIT -> StateMachine.READ_BEFORE_WRITE; + case WAIT_BEFORE_READ -> StateMachine.READ_AFTER_WRITE; + // Unexpected (the State has been unexpectedly changed in-between) + default -> this.state; + }; + + if (this.state != previousState) { + if (this.isTraceLog()) { + this.log.info("State: " + previousState + " -> " + this.state + " (onWaitDelayTaskFinished)"); + } + } + } + + private boolean isTraceLog() { + return switch (this.logVerbosity.get()) { + case READS_AND_WRITES_DURATION_TRACE_EVENTS -> true; + case NONE, DEBUG_LOG, READS_AND_WRITES, READS_AND_WRITES_DURATION, READS_AND_WRITES_VERBOSE -> false; + }; + } +} \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponents.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponents.java new file mode 100644 index 00000000000..6fa218f805f --- /dev/null +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponents.java @@ -0,0 +1,116 @@ +package io.openems.edge.bridge.modbus.api.worker.internal; + +import java.time.Clock; +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.openems.edge.bridge.modbus.api.LogVerbosity; +import io.openems.edge.common.type.TypeUtils; + +public class DefectiveComponents { + + public static final int INCREASE_WAIT_SECONDS = 2; + public static final int MAX_WAIT_SECONDS = 5 * 60; // 5 minutes + + private final Logger log = LoggerFactory.getLogger(DefectiveComponents.class); + + private static record NextTry(Instant timestamp, int count) { + } + + private final Clock clock; + private final AtomicReference logVerbosity; + private final Map nextTries = new HashMap<>(); + + public DefectiveComponents(AtomicReference logVerbosity) { + this(Clock.systemDefaultZone(), logVerbosity); + } + + protected DefectiveComponents() { + this(Clock.systemDefaultZone(), new AtomicReference<>(LogVerbosity.READS_AND_WRITES_DURATION_TRACE_EVENTS)); + } + + protected DefectiveComponents(Clock clock) { + this(clock, new AtomicReference<>(LogVerbosity.READS_AND_WRITES_DURATION_TRACE_EVENTS)); + } + + protected DefectiveComponents(Clock clock, AtomicReference logVerbosity) { + this.clock = clock; + this.logVerbosity = logVerbosity; + } + + /** + * Adds a defective Component and sets retry time to now() + + * {@value #PAUSE_SECONDS}. + * + * @param componentId the Component-ID; not null + */ + public synchronized void add(String componentId) { + TypeUtils.assertNull("DefectiveComponents add() takes no null values", componentId); + this.nextTries.compute(componentId, (k, v) -> { + var count = (v == null) ? 1 : v.count + 1; + var wait = Math.min(INCREASE_WAIT_SECONDS * count, MAX_WAIT_SECONDS); + if (this.isTraceLog()) { + final String log; + if (count == 1) { + log = "Add [" + componentId + "] to defective Components."; + } else { + log = "Increase wait for defective Component [" + componentId + "]."; + } + this.log.info(log + " Wait [" + wait + "s]" + " Count [" + count + "]"); + } + return new NextTry(Instant.now(this.clock).plusSeconds(wait), count); + }); + } + + /** + * Removes a defective Component. + * + * @param componentId the Component-ID; not null + */ + public synchronized void remove(String componentId) { + TypeUtils.assertNull("DefectiveComponents remove() takes no null values", componentId); + if (this.nextTries.remove(componentId) != null && this.isTraceLog()) { + this.log.info("Remove [" + componentId + "] from defective Components."); + } + } + + /** + * Is the given Component known to be defective?. + * + * @param componentId the Component-ID + * @return true if listed as defective, false if not + */ + public synchronized boolean isKnown(String componentId) { + return this.nextTries.containsKey(componentId); + } + + /** + * Is the given Component due for next try?. + * + * @param componentId the Component-ID + * @return true if yes, false if no, null if component is not in list of + * defective components + */ + public synchronized Boolean isDueForNextTry(String componentId) { + var nextTry = this.nextTries.get(componentId); + if (nextTry == null) { + return null; + } + var now = Instant.now(this.clock); + return now.isAfter(nextTry.timestamp); + } + + private boolean isTraceLog() { + return switch (this.logVerbosity.get()) { + case READS_AND_WRITES, READS_AND_WRITES_DURATION, READS_AND_WRITES_VERBOSE, + READS_AND_WRITES_DURATION_TRACE_EVENTS -> + true; + case NONE, DEBUG_LOG -> false; + }; + } +} \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplier.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplier.java new file mode 100644 index 00000000000..ca70ff35fa9 --- /dev/null +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplier.java @@ -0,0 +1,20 @@ +package io.openems.edge.bridge.modbus.api.worker.internal; + +public interface TasksSupplier { + + /** + * Supplies the Tasks for one Cycle. + * + * @param defectiveComponents the {@link DefectiveComponents} handler + * @return a {@link CycleTasks} object + */ + public CycleTasks getCycleTasks(DefectiveComponents defectiveComponents); + + /** + * Gets the total number of tasks. + * + * @return total number of tasks + */ + public int getTotalNumberOfTasks(); + +} diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImpl.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImpl.java new file mode 100644 index 00000000000..bae816068df --- /dev/null +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImpl.java @@ -0,0 +1,130 @@ +package io.openems.edge.bridge.modbus.api.worker.internal; + +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.Queue; +import java.util.stream.Collectors; + +import io.openems.edge.bridge.modbus.api.ModbusProtocol; +import io.openems.edge.bridge.modbus.api.task.ReadTask; +import io.openems.edge.bridge.modbus.api.task.Task; +import io.openems.edge.bridge.modbus.api.task.WriteTask; +import io.openems.edge.common.taskmanager.Priority; +import io.openems.edge.common.taskmanager.TasksManager; +import io.openems.edge.common.type.Tuple; + +/** + * Supplies Tasks. + */ +public class TasksSupplierImpl implements TasksSupplier { + + /** + * Source-ID -> TasksManager for {@link Task}s. + */ + private final Map> taskManagers = new HashMap<>(); + + /** + * Queue of LOW priority {@link ReadTask}s. + */ + private final Queue> nextLowPriorityTasks = new LinkedList<>(); + + /** + * Adds the protocol. + * + * @param sourceId Component-ID of the source + * @param protocol the ModbusProtocol + */ + public void addProtocol(String sourceId, ModbusProtocol protocol) { + this.taskManagers.put(sourceId, protocol.getTaskManager()); + } + + /** + * Removes the protocol. + * + * @param sourceId Component-ID of the source + */ + public void removeProtocol(String sourceId) { + this.taskManagers.remove(sourceId); + } + + @Override + public CycleTasks getCycleTasks(DefectiveComponents defectiveComponents) { + Map> tasks = new HashMap<>(); + // One Low Priority ReadTask + { + var t = this.getOneLowPriorityReadTask(); + if (t != null) { + tasks.computeIfAbsent(t.a(), (ignore) -> new LinkedList<>()) // + .add(t.b()); + } + } + // All High Priority ReadTasks + all WriteTasks + this.taskManagers.forEach((id, taskManager) -> { + var list = tasks.computeIfAbsent(id, (ignore) -> new LinkedList<>()); + taskManager.getTasks().stream() // + .filter(t -> t instanceof WriteTask || t.getPriority() == Priority.HIGH) // + .forEach(list::add); + }); + // Filter out defective components + tasks.forEach((id, componentTasks) -> { + var isDue = defectiveComponents.isDueForNextTry(id); + if (isDue == null) { + // Component is not defective -> keep all tasks + } else if (isDue) { + // Component is due for next try -> keep only one random Task + Collections.shuffle(componentTasks); + while (componentTasks.size() > 1) { + componentTasks.pop(); + } + } else { + // Component is defective and not due -> drop all tasks + componentTasks.clear(); + } + }); + return new CycleTasks(// + tasks.values().stream().flatMap(LinkedList::stream) // + .filter(ReadTask.class::isInstance).map(ReadTask.class::cast) // + // Sort HIGH priority to the end + .sorted((a, b) -> b.getPriority().compareTo(a.getPriority())) // + .collect(Collectors.toCollection(LinkedList::new)), + tasks.values().stream().flatMap(LinkedList::stream) // + .filter(WriteTask.class::isInstance).map(WriteTask.class::cast) // + .collect(Collectors.toCollection(LinkedList::new))); + } + + /** + * Get one LOW priority task. + * + * @return the next task; null if there is no available task + */ + private synchronized Tuple getOneLowPriorityReadTask() { + var refilledBefore = false; + while (true) { + var task = this.nextLowPriorityTasks.poll(); + if (task != null) { + return task; + } + if (refilledBefore) { + // queue had been refilled before, but still cannot find a matching task -> quit + return null; + } + // refill the queue + this.taskManagers.forEach((id, taskManager) -> { + taskManager.getTasks(Priority.LOW).stream() // + .filter(ReadTask.class::isInstance).map(ReadTask.class::cast) // + .map(t -> new Tuple(id, t)) // + .forEach(this.nextLowPriorityTasks::add); + }); + refilledBefore = true; + } + } + + @Override + public int getTotalNumberOfTasks() { + return this.taskManagers.values().stream() // + .mapToInt(m -> m.countTasks()) // + .sum(); + } +} diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/WaitDelayHandler.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/WaitDelayHandler.java new file mode 100644 index 00000000000..d491e094104 --- /dev/null +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/WaitDelayHandler.java @@ -0,0 +1,209 @@ +package io.openems.edge.bridge.modbus.api.worker.internal; + +import java.util.Collection; +import java.util.function.Consumer; + +import com.google.common.base.Stopwatch; +import com.google.common.base.Ticker; +import com.google.common.collect.EvictingQueue; +import com.google.common.math.Quantiles; + +import io.openems.edge.bridge.modbus.api.task.WaitTask; + +public class WaitDelayHandler { + + private static final int BUFFER_MS = 20; + + private final Runnable onWaitDelayTaskFinished; + private final Consumer cycleDelayChannel; + private final Stopwatch stopwatch; + + /** + * Delays that would have been possible. Updated via + * {@link #updateTotalNumberOfTasks(int)}. + */ + private EvictingQueue possibleDelays = EvictingQueue.create(10 /* initial size */); + + /** + * Set only via + * {@link #setWaitDelayTask(io.openems.edge.bridge.modbus.api.task.WaitTask.Delay)}. + */ + private WaitTask.Delay waitDelayTask; + + /** + * Marker for invalid time, e.g. CycleTasks span multiple Cycles, Tasks + * contained defective Components, etc. + */ + private boolean timeIsInvalid = false; + + protected WaitDelayHandler(Ticker ticker, Runnable onWaitDelayTaskFinished, Consumer cycleDelayChannel) { + this.stopwatch = Stopwatch.createUnstarted(ticker); + this.onWaitDelayTaskFinished = onWaitDelayTaskFinished; + this.cycleDelayChannel = cycleDelayChannel; + this.setWaitDelayTask(generateZeroWaitDelayTask(onWaitDelayTaskFinished)); + } + + protected WaitDelayHandler(Runnable onWaitDelayTaskFinished, Consumer cycleDelayChannel) { + this(Ticker.systemTicker(), onWaitDelayTaskFinished, cycleDelayChannel); + } + + /** + * Updates the size of the internal {@link #possibleDelays} queue to the total + * number of tasks. + * + *

    + * 'possibleDelays' needs to 'learn' execution time of all tasks, so it is + * important to keep its size in sync with the total number of tasks. + * + * @param totalNumberOfTasks the total number of tasks + */ + public synchronized void updateTotalNumberOfTasks(int totalNumberOfTasks) { + var targetQueueSize = Math.max(// + totalNumberOfTasks * 5, // keeps 5 full Cycles + 10); // size at least 10 + + // Calculate current queue size; logic is the inverse of + // EvictingQueue#remainingCapacity() + var currentQueueSize = this.possibleDelays.remainingCapacity() + this.possibleDelays.size(); + if (targetQueueSize != currentQueueSize) { + // Size changed: create new queue and copy entries + var oldQueue = this.possibleDelays; + var newQueue = EvictingQueue.create(targetQueueSize); + newQueue.addAll(oldQueue); + this.possibleDelays = newQueue; + } + } + + /** + * Called on BEFORE_PROCESS_IMAGE event. + * + * @param traceLog activate logging + * @return if traceLog is active, return a detailed log info; empty string + * otherwise + */ + public synchronized String onBeforeProcessImage(boolean traceLog) { + String log = ""; + + if (this.timeIsInvalid) { + // Do not add possibleDelay if previous Cycle contained a defective component + log = "(time is invalid)"; + this.stopwatch.reset(); + + } else { + // Calculate possible delay + final long possibleDelay; + if (this.stopwatch.isRunning()) { + // Coming from FINISHED state -> it's possible to increase delay + this.stopwatch.stop(); + possibleDelay = this.waitDelayTask.initialDelay + this.stopwatch.elapsed().toMillis(); + if (traceLog) { + log = "PreviousDelay [" + this.waitDelayTask.initialDelay + "ms] " // + + "+ Wait [" + this.stopwatch.elapsed().toMillis() + "ms] " // + + "= PossibleDelay [" + possibleDelay + "ms]"; + } + + } else { + // FINISHED state has not happened -> reduce possible delay + var halfOfLastDelay = this.waitDelayTask.initialDelay / 2; + if (traceLog) { + if (this.waitDelayTask.initialDelay == 0) { + log = "CYCLE_TIME_TOO_SHORT"; // + } else { + log = "CYCLE_TIME_TOO_SHORT after " // + + "PreviousDelay [" + this.waitDelayTask.initialDelay + "ms] " // + + "-> reduce to [" + halfOfLastDelay + "ms]"; + } + } + possibleDelay = halfOfLastDelay; + } + + this.possibleDelays.add(possibleDelay); + } + + // Initialize a new WaitDelayTask. + this.setWaitDelayTask(generateWaitDelayTask(this.possibleDelays, this.onWaitDelayTaskFinished)); + + // Reset 'timeIsInvalid' + this.timeIsInvalid = false; + + return log; + } + + /** + * Announce, that the Cycle measurement time is invalid. + * + *

      + *
    • WaitDelayTask will be set to 'zero-wait' + *
    • Internal marker 'timeIsInvalid' is set. This causes the time measurement + * for this Cycle to be ignored + *
    + * + *

    + * This method is called shortly after 'onBeforeProcessImage()' + */ + public synchronized void timeIsInvalid() { + this.setWaitDelayTask(generateZeroWaitDelayTask(this.onWaitDelayTaskFinished)); + this.timeIsInvalid = true; + } + + /** + * Called when waiting finished. + */ + public synchronized void onFinished() { + // Measure duration between FINISHED and ON_BEFORE_PROCESS_IMAGE event + this.stopwatch.reset(); + this.stopwatch.start(); + } + + private synchronized void setWaitDelayTask(WaitTask.Delay waitDelayTask) { + this.waitDelayTask = waitDelayTask; + + // Set the CYCLE_DELAY Channel + this.cycleDelayChannel.accept(this.waitDelayTask.initialDelay); + } + + /** + * Gets the {@link WaitTask.Delay}. + * + * @return the task + */ + public synchronized WaitTask.Delay getWaitDelayTask() { + return this.waitDelayTask; + } + + /** + * Generates a {@link WaitDelayTask} with the 1st 4th-quantile of all possible + * waiting times in the queue, i.e. one of the shortest possible delays - minus + * {@link #BUFFER_MS}. + * + * @param possibleDelays the collected possible delays of the last + * Cycles + * @param onWaitDelayTaskFinished callback on wait-delay finished + * @return the {@link WaitDelayTask} + */ + protected static WaitTask.Delay generateWaitDelayTask(Collection possibleDelays, + Runnable onWaitDelayTaskFinished) { + final long shortestPossibleDelay; + if (possibleDelays.isEmpty()) { + shortestPossibleDelay = 0L; + } else { + shortestPossibleDelay = (long) Quantiles.scale(4).index(1).compute(possibleDelays); + } + + if (shortestPossibleDelay < BUFFER_MS) { + return generateZeroWaitDelayTask(onWaitDelayTaskFinished); + } + + return new WaitTask.Delay(shortestPossibleDelay - BUFFER_MS, onWaitDelayTaskFinished); + } + + /** + * Generates a {@link WaitDelayTask} with zero waiting time. + * + * @param onWaitDelayTaskFinished callback on wait-delay finished + * @return the {@link WaitDelayTask} + */ + private static WaitTask.Delay generateZeroWaitDelayTask(Runnable onWaitDelayTaskFinished) { + return new WaitTask.Delay(0, onWaitDelayTaskFinished); + } +} diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/AbstractOpenemsSunSpecComponent.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/AbstractOpenemsSunSpecComponent.java index fb1d961b15d..cb39232c26c 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/AbstractOpenemsSunSpecComponent.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/AbstractOpenemsSunSpecComponent.java @@ -23,6 +23,7 @@ import io.openems.edge.bridge.modbus.api.ModbusUtils; import io.openems.edge.bridge.modbus.api.element.AbstractModbusElement; import io.openems.edge.bridge.modbus.api.element.DummyRegisterElement; +import io.openems.edge.bridge.modbus.api.element.ModbusElement; import io.openems.edge.bridge.modbus.api.element.UnsignedDoublewordElement; import io.openems.edge.bridge.modbus.api.element.UnsignedWordElement; import io.openems.edge.bridge.modbus.api.task.FC16WriteRegistersTask; @@ -167,8 +168,6 @@ private CompletableFuture readNextBlock(int startAddress, Set rem } // Handle SunSpec Block - int length = values.get(1); - if (blockId == 1 /* SunSpecModel.S_1 */) { this.commonBlockCounter++; } @@ -199,8 +198,14 @@ private CompletableFuture readNextBlock(int startAddress, Set rem } } + // Stop reading if all expectedBlocks have been read + if (remainingBlocks.isEmpty()) { + finished.complete(null); + return; + } + // Read next block recursively - var nextBlockStartAddress = startAddress + 2 + length; + var nextBlockStartAddress = startAddress + 2 + values.get(1); try { final var readNextBlockFuture = this.readNextBlock(nextBlockStartAddress, remainingBlocks); @@ -278,15 +283,15 @@ public boolean isSunSpecInitializationCompleted() { protected void addBlock(int startAddress, SunSpecModel model, Priority priority) throws OpenemsException { this.logInfo(this.log, "Adding SunSpec-Model [" + model.getBlockId() + ":" + model.label() + "] starting at [" + startAddress + "]"); - AbstractModbusElement[] elements = new AbstractModbusElement[model.points().length]; + var elements = new ModbusElement[model.points().length]; startAddress += 2; for (var i = 0; i < model.points().length; i++) { var point = model.points()[i]; - AbstractModbusElement element = point.get().generateModbusElement(startAddress); + var element = point.get().generateModbusElement(startAddress); startAddress += element.getLength(); elements[i] = element; - SunSChannelId channelId = point.getChannelId(); + var channelId = point.getChannelId(); this.addChannel(channelId); if (point.get().scaleFactor.isPresent()) { @@ -294,7 +299,7 @@ protected void addBlock(int startAddress, SunSpecModel model, Priority priority) // - find the ScaleFactor-Point var scaleFactorName = SunSpecCodeGenerator.toUpperUnderscore(point.get().scaleFactor.get()); SunSpecPoint scaleFactorPoint = null; - for (SunSpecPoint sfPoint : model.points()) { + for (var sfPoint : model.points()) { if (sfPoint.name().equals(scaleFactorName)) { scaleFactorPoint = sfPoint; break; @@ -336,6 +341,7 @@ protected void addBlock(int startAddress, SunSpecModel model, Priority priority) case READ_WRITE: case WRITE_ONLY: // Add a Write-Task + // TODO create one FC16WriteRegistersTask for entire block final Task writeTask = new FC16WriteRegistersTask(element.getStartAddress(), element); this.modbusProtocol.addTask(writeTask); break; @@ -355,7 +361,7 @@ protected void addBlock(int startAddress, SunSpecModel model, Priority priority) * @throws OpenemsException on error */ @SafeVarargs - private final CompletableFuture> readElementsOnceTyped(AbstractModbusElement... elements) + private final CompletableFuture> readElementsOnceTyped(AbstractModbusElement... elements) throws OpenemsException { // Register listeners for elements @SuppressWarnings("unchecked") diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/SunSpecPoint.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/SunSpecPoint.java index 50917d892ee..6beb47fcf4e 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/SunSpecPoint.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/SunSpecPoint.java @@ -105,7 +105,7 @@ public PointImpl(String channelId, String label, String description, String note * @param startAddress the startAddress of the Point * @return a new Modbus Element */ - public final AbstractModbusElement generateModbusElement(Integer startAddress) { + public final AbstractModbusElement generateModbusElement(Integer startAddress) { switch (this.type) { case UINT16: case ACC16: diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/test/DummyModbusBridge.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/test/DummyModbusBridge.java index 001b8dcb40a..cd031cfb4cb 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/test/DummyModbusBridge.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/test/DummyModbusBridge.java @@ -1,6 +1,7 @@ package io.openems.edge.bridge.modbus.test; import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.HashMap; import java.util.Map; @@ -14,14 +15,12 @@ import io.openems.edge.bridge.modbus.api.ModbusProtocol; import io.openems.edge.common.channel.Channel; import io.openems.edge.common.component.OpenemsComponent; -import io.openems.edge.common.cycle.Cycle; -import io.openems.edge.common.test.DummyCycle; public class DummyModbusBridge extends AbstractModbusBridge implements BridgeModbusTcp, BridgeModbus, OpenemsComponent { private final Map protocols = new HashMap<>(); - private final Cycle cycle = new DummyCycle(1000); + private InetAddress ipAddress = null; public DummyModbusBridge(String id) { super(// @@ -35,6 +34,18 @@ public DummyModbusBridge(String id) { super.activate(null, id, "", true, LogVerbosity.NONE, 2); } + /** + * Sets the IP-Address. + * + * @param ipAddress an IP-Address. + * @return myself + * @throws UnknownHostException on parse error + */ + public DummyModbusBridge withIpAddress(String ipAddress) throws UnknownHostException { + this.ipAddress = InetAddress.getByName(ipAddress); + return this; + } + @Override public void addProtocol(String sourceId, ModbusProtocol protocol) { this.protocols.put(sourceId, protocol); @@ -47,24 +58,20 @@ public void removeProtocol(String sourceId) { @Override public InetAddress getIpAddress() { - return null; - } - - @Override - public Cycle getCycle() { - return this.cycle; + if (this.ipAddress != null) { + return this.ipAddress; + } + throw new UnsupportedOperationException("Unsupported by Dummy Class"); } @Override public ModbusTransaction getNewModbusTransaction() throws OpenemsException { - // TODO Auto-generated method stub - return null; + throw new UnsupportedOperationException("Unsupported by Dummy Class"); } @Override public void closeModbusConnection() { - // TODO Auto-generated method stub - + throw new UnsupportedOperationException("Unsupported by Dummy Class"); } } diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/ModbusTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/ModbusTest.java index 2edf8ddf3fd..5f6c7132757 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/ModbusTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/ModbusTest.java @@ -6,12 +6,12 @@ import io.openems.common.exceptions.OpenemsException; import io.openems.common.types.OpenemsType; import io.openems.edge.bridge.modbus.DummyModbusComponent; -import io.openems.edge.bridge.modbus.api.task.AbstractTask; import io.openems.edge.bridge.modbus.api.task.FC16WriteRegistersTask; import io.openems.edge.bridge.modbus.api.task.FC1ReadCoilsTask; import io.openems.edge.bridge.modbus.api.task.FC3ReadRegistersTask; import io.openems.edge.bridge.modbus.api.task.FC5WriteCoilTask; import io.openems.edge.bridge.modbus.api.task.FC6WriteRegisterTask; +import io.openems.edge.bridge.modbus.api.task.Task; import io.openems.edge.common.channel.Channel; import io.openems.edge.common.channel.ChannelId.ChannelIdImpl; import io.openems.edge.common.channel.Doc; @@ -19,7 +19,7 @@ import io.openems.edge.common.channel.internal.AbstractReadChannel; import io.openems.edge.common.taskmanager.Priority; -public class ModbusTest, CHANNEL extends Channel> +public class ModbusTest, CHANNEL extends Channel> extends DummyModbusComponent { private static final String CHANNEL_ID = "CHANNEL"; @@ -39,16 +39,16 @@ private ModbusTest(ELEMENT element, BiFunction taskFact this.getModbusProtocol().addTask(this.task); } - public static class FC1ReadCoils, CHANNEL extends AbstractReadChannel> - extends ModbusTest { - public FC1ReadCoils(ELEMENT element, OpenemsType openemsType) throws OpenemsException { + public static class FC1ReadCoils> + extends ModbusTest { + public FC1ReadCoils(CoilElement element, OpenemsType openemsType) throws OpenemsException { super(element, // (startAddress, priority) -> new FC1ReadCoilsTask(startAddress, priority, element), // AccessMode.READ_ONLY, openemsType); } } - public static class FC5WriteCoil, CHANNEL extends WriteChannel> + public static class FC5WriteCoil, CHANNEL extends WriteChannel> extends ModbusTest { @SuppressWarnings("unchecked") public FC5WriteCoil(ModbusCoilElement element, OpenemsType openemsType) throws OpenemsException { @@ -58,7 +58,7 @@ public FC5WriteCoil(ModbusCoilElement element, OpenemsType openemsType) throws O } } - public static class FC3ReadRegisters, CHANNEL extends AbstractReadChannel> + public static class FC3ReadRegisters, CHANNEL extends AbstractReadChannel> extends ModbusTest { public FC3ReadRegisters(ELEMENT element, OpenemsType openemsType) throws OpenemsException { super(element, // @@ -67,7 +67,7 @@ public FC3ReadRegisters(ELEMENT element, OpenemsType openemsType) throws Openems } } - public static class FC6WriteRegister, CHANNEL extends WriteChannel> + public static class FC6WriteRegister, CHANNEL extends WriteChannel> extends ModbusTest { public FC6WriteRegister(ELEMENT element, OpenemsType openemsType) throws OpenemsException { super(element, // @@ -76,7 +76,7 @@ public FC6WriteRegister(ELEMENT element, OpenemsType openemsType) throws Openems } } - public static class FC16WriteRegisters, CHANNEL extends WriteChannel> + public static class FC16WriteRegisters, CHANNEL extends WriteChannel> extends ModbusTest { public FC16WriteRegisters(ELEMENT element, OpenemsType openemsType) throws OpenemsException { super(element, // diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/AbstractReadDigitalInputsTaskTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/AbstractReadDigitalInputsTaskTest.java new file mode 100644 index 00000000000..d0f0c5910a4 --- /dev/null +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/AbstractReadDigitalInputsTaskTest.java @@ -0,0 +1,27 @@ +package io.openems.edge.bridge.modbus.api.task; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import com.ghgande.j2mod.modbus.util.BitVector; + +public class AbstractReadDigitalInputsTaskTest { + + @Test + public void testToBooleanArray() { + var bv = new BitVector(5); + bv.setBit(0, false); + bv.setBit(1, true); + bv.setBit(2, false); + bv.setBit(3, true); + bv.setBit(4, false); + + var bools = AbstractReadDigitalInputsTask.toBooleanArray(bv); + assertEquals(false, bools[0]); + assertEquals(true, bools[1]); + assertEquals(false, bools[2]); + assertEquals(true, bools[3]); + assertEquals(false, bools[4]); + } +} diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC16WriteRegistersTaskTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC16WriteRegistersTaskTest.java new file mode 100644 index 00000000000..c7961ba2c3b --- /dev/null +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC16WriteRegistersTaskTest.java @@ -0,0 +1,73 @@ +package io.openems.edge.bridge.modbus.api.task; + +import static org.junit.Assert.assertEquals; + +import java.util.Optional; + +import org.junit.Test; + +import com.ghgande.j2mod.modbus.msg.WriteMultipleRegistersRequest; +import com.ghgande.j2mod.modbus.msg.WriteMultipleRegistersResponse; +import com.ghgande.j2mod.modbus.procimg.Register; +import com.ghgande.j2mod.modbus.procimg.SimpleRegister; + +import io.openems.common.exceptions.OpenemsException; +import io.openems.edge.bridge.modbus.DummyModbusComponent; +import io.openems.edge.bridge.modbus.api.LogVerbosity; +import io.openems.edge.bridge.modbus.api.element.ModbusElement; +import io.openems.edge.bridge.modbus.api.element.UnsignedDoublewordElement; +import io.openems.edge.bridge.modbus.api.element.UnsignedWordElement; + +public class FC16WriteRegistersTaskTest { + + @Test + public void testMergeWriteRegisters() throws OpenemsException, IllegalArgumentException { + final var element0 = new UnsignedWordElement(0); + final var element1 = new UnsignedWordElement(1); + final var element2 = new UnsignedWordElement(2); + final var element3 = new UnsignedWordElement(3); + var elements = new ModbusElement[] { element0, element1, element2, element3 }; + + // Has Hole (no value for element2) + element0.setNextWriteValue(Optional.empty()); + element1.setNextWriteValue(Optional.of(100)); + element2.setNextWriteValue(Optional.empty()); + element3.setNextWriteValue(Optional.of(300)); + + { + var result = FC16WriteRegistersTask.mergeWriteRegisters(elements, (message) -> System.out.println(message)); + + assertEquals(2, result.size()); // Two individual requests + assertEquals(1, result.get(0).startAddress()); + assertEquals(1, result.get(0).getRegisters().length); + assertEquals(3, result.get(1).startAddress()); + assertEquals(1, result.get(1).getRegisters().length); + } + + // Has NO Hole (all values set) + element0.setNextWriteValue(Optional.of(100)); + element1.setNextWriteValue(Optional.of(200)); + element2.setNextWriteValue(Optional.of(300)); + element3.setNextWriteValue(Optional.of(400)); + { + var result = FC16WriteRegistersTask.mergeWriteRegisters(elements, (message) -> System.out.println(message)); + + assertEquals(1, result.size()); // One combined request + assertEquals(0, result.get(0).startAddress()); + assertEquals(4, result.get(0).getRegisters().length); + } + } + + @Test + public void testToLogMessage() throws OpenemsException { + var component = new DummyModbusComponent(); + var task = new FC16WriteRegistersTask(30, new UnsignedDoublewordElement(30)); + task.setParent(component); + var request = new WriteMultipleRegistersRequest(30, + new Register[] { new SimpleRegister(123), new SimpleRegister(456) }); + var response = (WriteMultipleRegistersResponse) request.getResponse(); + + assertEquals("FC16WriteRegisters [device0;unitid=1;ref=30/0x1e;length=2;request=007b 01c8]", + task.toLogMessage(LogVerbosity.READS_AND_WRITES_VERBOSE, request, response)); + } +} diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC1ReadCoilsTaskTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC1ReadCoilsTaskTest.java new file mode 100644 index 00000000000..6dde298f3cf --- /dev/null +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC1ReadCoilsTaskTest.java @@ -0,0 +1,30 @@ +package io.openems.edge.bridge.modbus.api.task; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import com.ghgande.j2mod.modbus.msg.ReadCoilsResponse; + +import io.openems.common.exceptions.OpenemsException; +import io.openems.edge.bridge.modbus.DummyModbusComponent; +import io.openems.edge.bridge.modbus.api.LogVerbosity; +import io.openems.edge.bridge.modbus.api.element.DummyCoilElement; +import io.openems.edge.common.taskmanager.Priority; + +public class FC1ReadCoilsTaskTest { + + @Test + public void testToLogMessage() throws OpenemsException { + var component = new DummyModbusComponent(); + var task = new FC1ReadCoilsTask(10, Priority.HIGH, new DummyCoilElement(10), new DummyCoilElement(11)); + task.setParent(component); + var request = task.createModbusRequest(); + var response = (ReadCoilsResponse) request.getResponse(); + response.setCoilStatus(0, true); + response.setCoilStatus(1, false); + + assertEquals("FC1ReadCoils [device0;unitid=1;priority=HIGH;ref=10/0xa;length=2;response=10]", + task.toLogMessage(LogVerbosity.READS_AND_WRITES_VERBOSE, request, response)); + } +} diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC2ReadInputsTaskTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC2ReadInputsTaskTest.java new file mode 100644 index 00000000000..6f6599a512a --- /dev/null +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC2ReadInputsTaskTest.java @@ -0,0 +1,30 @@ +package io.openems.edge.bridge.modbus.api.task; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import com.ghgande.j2mod.modbus.msg.ReadInputDiscretesResponse; + +import io.openems.common.exceptions.OpenemsException; +import io.openems.edge.bridge.modbus.DummyModbusComponent; +import io.openems.edge.bridge.modbus.api.LogVerbosity; +import io.openems.edge.bridge.modbus.api.element.DummyCoilElement; +import io.openems.edge.common.taskmanager.Priority; + +public class FC2ReadInputsTaskTest { + + @Test + public void testToLogMessage() throws OpenemsException { + var component = new DummyModbusComponent(); + var task = new FC2ReadInputsTask(10, Priority.HIGH, new DummyCoilElement(10), new DummyCoilElement(11)); + task.setParent(component); + var request = task.createModbusRequest(); + var response = (ReadInputDiscretesResponse) request.getResponse(); + response.setDiscreteStatus(0, true); + response.setDiscreteStatus(1, false); + + assertEquals("FC2ReadCoils [device0;unitid=1;priority=HIGH;ref=10/0xa;length=2;response=10]", + task.toLogMessage(LogVerbosity.READS_AND_WRITES_VERBOSE, request, response)); + } +} diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC3ReadRegistersTaskTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC3ReadRegistersTaskTest.java new file mode 100644 index 00000000000..061a2590d59 --- /dev/null +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC3ReadRegistersTaskTest.java @@ -0,0 +1,31 @@ +package io.openems.edge.bridge.modbus.api.task; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import com.ghgande.j2mod.modbus.msg.ReadMultipleRegistersResponse; +import com.ghgande.j2mod.modbus.procimg.Register; +import com.ghgande.j2mod.modbus.procimg.SimpleRegister; + +import io.openems.common.exceptions.OpenemsException; +import io.openems.edge.bridge.modbus.DummyModbusComponent; +import io.openems.edge.bridge.modbus.api.LogVerbosity; +import io.openems.edge.bridge.modbus.api.element.UnsignedDoublewordElement; +import io.openems.edge.common.taskmanager.Priority; + +public class FC3ReadRegistersTaskTest { + + @Test + public void testToLogMessage() throws OpenemsException { + var component = new DummyModbusComponent(); + var task = new FC3ReadRegistersTask(20, Priority.LOW, new UnsignedDoublewordElement(20)); + task.setParent(component); + var request = task.createModbusRequest(); + var response = (ReadMultipleRegistersResponse) request.getResponse(); + response.setRegisters(new Register[] { new SimpleRegister(100), new SimpleRegister(200) }); + + assertEquals("FC3ReadHoldingRegisters [device0;unitid=1;priority=LOW;ref=20/0x14;length=2;response=0064 00c8]", + task.toLogMessage(LogVerbosity.READS_AND_WRITES_VERBOSE, request, response)); + } +} diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC4ReadInputRegistersTaskTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC4ReadInputRegistersTaskTest.java new file mode 100644 index 00000000000..1bc27a12d6e --- /dev/null +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC4ReadInputRegistersTaskTest.java @@ -0,0 +1,30 @@ +package io.openems.edge.bridge.modbus.api.task; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import com.ghgande.j2mod.modbus.procimg.Register; +import com.ghgande.j2mod.modbus.procimg.SimpleRegister; + +import io.openems.common.exceptions.OpenemsException; +import io.openems.edge.bridge.modbus.DummyModbusComponent; +import io.openems.edge.bridge.modbus.api.LogVerbosity; +import io.openems.edge.bridge.modbus.api.element.UnsignedDoublewordElement; +import io.openems.edge.common.taskmanager.Priority; + +public class FC4ReadInputRegistersTaskTest { + + @Test + public void testToLogMessage() throws OpenemsException { + var component = new DummyModbusComponent(); + var task = new FC4ReadInputRegistersTask(20, Priority.LOW, new UnsignedDoublewordElement(20)); + task.setParent(component); + var request = task.createModbusRequest(); + var response = request.getResponse(); + response.setRegisters(new Register[] { new SimpleRegister(987), new SimpleRegister(654) }); + + assertEquals("FC4ReadInputRegisters [device0;unitid=1;priority=LOW;ref=20/0x14;length=2;response=03db 028e]", + task.toLogMessage(LogVerbosity.READS_AND_WRITES_VERBOSE, request, response)); + } +} diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC5WriteCoilTaskTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC5WriteCoilTaskTest.java new file mode 100644 index 00000000000..f1522cbb49b --- /dev/null +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC5WriteCoilTaskTest.java @@ -0,0 +1,29 @@ +package io.openems.edge.bridge.modbus.api.task; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import com.ghgande.j2mod.modbus.msg.WriteCoilRequest; +import com.ghgande.j2mod.modbus.msg.WriteCoilResponse; + +import io.openems.common.exceptions.OpenemsException; +import io.openems.edge.bridge.modbus.DummyModbusComponent; +import io.openems.edge.bridge.modbus.api.LogVerbosity; +import io.openems.edge.bridge.modbus.api.element.DummyCoilElement; + +public class FC5WriteCoilTaskTest { + + @Test + public void testToLogMessage() throws OpenemsException { + var component = new DummyModbusComponent(); + var task = new FC5WriteCoilTask(20, new DummyCoilElement(20)); + task.setParent(component); + var request = new WriteCoilRequest(20, true); + var response = (WriteCoilResponse) request.getResponse(); + response.setCoil(true); + + assertEquals("FC5WriteCoil [device0;unitid=1;ref=20/0x14;length=1;request=ON]", + task.toLogMessage(LogVerbosity.READS_AND_WRITES_VERBOSE, request, response)); + } +} diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC6WriteRegisterTaskTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC6WriteRegisterTaskTest.java new file mode 100644 index 00000000000..3eae8483467 --- /dev/null +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC6WriteRegisterTaskTest.java @@ -0,0 +1,29 @@ +package io.openems.edge.bridge.modbus.api.task; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import com.ghgande.j2mod.modbus.msg.WriteSingleRegisterRequest; +import com.ghgande.j2mod.modbus.msg.WriteSingleRegisterResponse; +import com.ghgande.j2mod.modbus.procimg.SimpleRegister; + +import io.openems.common.exceptions.OpenemsException; +import io.openems.edge.bridge.modbus.DummyModbusComponent; +import io.openems.edge.bridge.modbus.api.LogVerbosity; +import io.openems.edge.bridge.modbus.api.element.UnsignedWordElement; + +public class FC6WriteRegisterTaskTest { + + @Test + public void testToLogMessage() throws OpenemsException { + var component = new DummyModbusComponent(); + var task = new FC6WriteRegisterTask(20, new UnsignedWordElement(20)); + task.setParent(component); + var request = new WriteSingleRegisterRequest(20, new SimpleRegister(315)); + var response = new WriteSingleRegisterResponse(20, 315); + + assertEquals("FC6WriteRegister [device0;unitid=1;ref=20/0x14;length=1;request=013b]", + task.toLogMessage(LogVerbosity.READS_AND_WRITES_VERBOSE, request, response)); + } +} diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/UtilsTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/UtilsTest.java deleted file mode 100644 index 76b19006f4d..00000000000 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/UtilsTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package io.openems.edge.bridge.modbus.api.task; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; - -public class UtilsTest { - - @Test - public void testToBooleanArray() { - - byte[] bs = { (byte) 0xAA, (byte) 0xAA }; - - var bools = Utils.toBooleanArray(bs); - - assertEquals(true, bools[0]); - assertEquals(false, bools[1]); - assertEquals(true, bools[2]); - assertEquals(false, bools[3]); - assertEquals(true, bools[4]); - assertEquals(false, bools[5]); - assertEquals(true, bools[6]); - assertEquals(false, bools[7]); - assertEquals(true, bools[8]); - assertEquals(false, bools[9]); - assertEquals(true, bools[10]); - assertEquals(false, bools[11]); - assertEquals(true, bools[12]); - assertEquals(false, bools[13]); - assertEquals(true, bools[14]); - assertEquals(false, bools[15]); - } - -} diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/AbstractDummyTask.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/AbstractDummyTask.java new file mode 100644 index 00000000000..b834c62cb6c --- /dev/null +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/AbstractDummyTask.java @@ -0,0 +1,70 @@ +package io.openems.edge.bridge.modbus.api.worker; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.ghgande.j2mod.modbus.msg.ModbusRequest; +import com.ghgande.j2mod.modbus.msg.ModbusResponse; + +import io.openems.edge.bridge.modbus.api.AbstractModbusBridge; +import io.openems.edge.bridge.modbus.api.task.AbstractTask; + +public abstract class AbstractDummyTask extends AbstractTask { + + protected final String name; + + private final Logger log = LoggerFactory.getLogger(AbstractDummyTask.class); + + protected long delay; + + private Runnable onExecuteCallback; + private boolean isDefective = false; + + public AbstractDummyTask(String name, long delay) { + super(name, ModbusResponse.class, 0); + this.name = name; + this.delay = delay; + } + + public void setDefective(boolean isDefective, long delay) { + this.isDefective = isDefective; + this.delay = delay; + } + + @Override + protected String payloadToString(ModbusRequest request) { + return ""; + } + + @Override + protected String payloadToString(ModbusResponse response) { + return ""; + } + + /** + * Callback on Execute. + * + * @param onExecuteCallback the callback {@link Runnable} + */ + public void onExecute(Runnable onExecuteCallback) { + this.onExecuteCallback = onExecuteCallback; + } + + @Override + public ExecuteState execute(AbstractModbusBridge bridge) { + if (this.onExecuteCallback != null) { + this.onExecuteCallback.run(); + } + + try { + Thread.sleep(this.delay); + } catch (InterruptedException e) { + this.log.warn(e.getMessage()); + } + + if (this.isDefective) { + return ExecuteState.ERROR; + } + return ExecuteState.OK; + } +} \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/DummyReadTask.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/DummyReadTask.java new file mode 100644 index 00000000000..63357905cfb --- /dev/null +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/DummyReadTask.java @@ -0,0 +1,24 @@ +package io.openems.edge.bridge.modbus.api.worker; + +import io.openems.edge.bridge.modbus.api.task.ReadTask; +import io.openems.edge.common.taskmanager.Priority; + +public class DummyReadTask extends AbstractDummyTask implements ReadTask { + + private final Priority priority; + + public DummyReadTask(String name, long delay, Priority priority) { + super(name, delay); + this.priority = priority; + } + + @Override + public String toString() { + return "DummyReadTask [name=" + this.name + ", delay=" + this.delay + ", priority=" + this.priority + "]"; + } + + @Override + public Priority getPriority() { + return this.priority; + } +} \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/DummyWriteTask.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/DummyWriteTask.java new file mode 100644 index 00000000000..c219e0a6295 --- /dev/null +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/DummyWriteTask.java @@ -0,0 +1,22 @@ +package io.openems.edge.bridge.modbus.api.worker; + +import io.openems.edge.bridge.modbus.api.task.WriteTask; +import io.openems.edge.common.taskmanager.Priority; + +public class DummyWriteTask extends AbstractDummyTask implements WriteTask { + + public DummyWriteTask(String name, long delay) { + super(name, delay); + } + + @Override + public String toString() { + return "DummyWriteTask [name=" + this.name + ", delay=" + this.delay + "]"; + } + + @Override + public Priority getPriority() { + return Priority.HIGH; + } + +} \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManagerTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManagerTest.java new file mode 100644 index 00000000000..0d764fdecef --- /dev/null +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManagerTest.java @@ -0,0 +1,180 @@ +package io.openems.edge.bridge.modbus.api.worker.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.function.Consumer; + +import org.junit.Before; +import org.junit.Test; + +import io.openems.common.exceptions.OpenemsException; +import io.openems.edge.bridge.modbus.DummyModbusComponent; +import io.openems.edge.bridge.modbus.api.task.WaitTask; +import io.openems.edge.bridge.modbus.api.worker.DummyReadTask; +import io.openems.edge.bridge.modbus.api.worker.DummyWriteTask; +import io.openems.edge.common.taskmanager.Priority; + +public class CycleTasksManagerTest { + + public static final Consumer CYCLE_TIME_IS_TOO_SHORT = (cycleTimeIsTooShort) -> { + }; + public static final Consumer CYCLE_DELAY = (cycleDelay) -> { + }; + + private static DummyReadTask RT_H_1; + private static DummyReadTask RT_H_2; + private static DummyReadTask RT_L_1; + private static DummyReadTask RT_L_2; + private static DummyWriteTask WT_1; + + @Before + public void before() { + RT_H_1 = new DummyReadTask("RT_H_1", 49, Priority.HIGH); + RT_H_2 = new DummyReadTask("RT_H_2", 70, Priority.HIGH); + RT_L_1 = new DummyReadTask("RT_L_1", 20, Priority.LOW); + RT_L_2 = new DummyReadTask("RT_L_2", 30, Priority.LOW); + WT_1 = new DummyWriteTask("WT_1", 90); + } + + @Test + public void testIdealConditions() throws OpenemsException, InterruptedException { + var cycle1 = CycleTasks.create() // + .reads(RT_L_1, RT_H_1, RT_H_2) // + .writes(WT_1) // + .build(); + var cycle2 = CycleTasks.create() // + .reads(RT_L_2, RT_H_1, RT_H_2) // + .writes(WT_1) // + .build(); + var tasksSupplier = new DummyTasksSupplier(cycle1, cycle2); + var defectiveComponents = new DefectiveComponents(); + + var sut = new CycleTasksManager(tasksSupplier, defectiveComponents, CYCLE_TIME_IS_TOO_SHORT, CYCLE_DELAY); + + // Cycle 1 + sut.onBeforeProcessImage(); + var task = sut.getNextTask(); + assertTrue(task instanceof WaitTask.Delay); + task.execute(null); + + task = sut.getNextTask(); + assertEquals(RT_L_1, task); + task.execute(null); + + sut.onExecuteWrite(); + task = sut.getNextTask(); + assertEquals(WT_1, task); + task.execute(null); + + task = sut.getNextTask(); + assertTrue(task instanceof WaitTask.Delay); + task.execute(null); + + task = sut.getNextTask(); + assertEquals(RT_H_1, task); + task.execute(null); + + task = sut.getNextTask(); + assertEquals(RT_H_2, task); + task.execute(null); + + task = sut.getNextTask(); + assertTrue(task instanceof WaitTask.Mutex); + // task.execute(null); -> this would block in single-threaded JUnit test + + // Cycle 2 + sut.onBeforeProcessImage(); + task = sut.getNextTask(); + assertTrue(task instanceof WaitTask.Delay); + task.execute(null); + + task = sut.getNextTask(); + assertEquals(RT_L_2, task); + task.execute(null); + + sut.onExecuteWrite(); + task = sut.getNextTask(); + assertEquals(WT_1, task); + task.execute(null); + + task = sut.getNextTask(); + assertTrue(task instanceof WaitTask.Delay); + task.execute(null); + + task = sut.getNextTask(); + assertEquals(RT_H_1, task); + task.execute(null); + + task = sut.getNextTask(); + assertEquals(RT_H_2, task); + task.execute(null); + + // Cycle 3 + sut.onBeforeProcessImage(); + task = sut.getNextTask(); + assertTrue(task instanceof WaitTask.Mutex); + // task.execute(null); -> this would block in single-threaded JUnit test + } + + @Test + public void testDefective() throws OpenemsException, InterruptedException { + var component = new DummyModbusComponent(); + RT_L_1.setParent(component); + + var cycle1 = CycleTasks.create() // + .reads(RT_L_1, RT_H_1, RT_H_2) // + .writes(WT_1) // + .build(); + var cycle2 = CycleTasks.create() // + .reads(RT_L_2, RT_H_1, RT_H_2) // + .writes(WT_1) // + .build(); + var tasksSupplier = new DummyTasksSupplier(cycle1, cycle2); + var defectiveComponents = new DefectiveComponents(); + defectiveComponents.add(component.id()); + + var sut = new CycleTasksManager(tasksSupplier, defectiveComponents, CYCLE_TIME_IS_TOO_SHORT, CYCLE_DELAY); + + // Cycle 1 + sut.onBeforeProcessImage(); + var task = sut.getNextTask(); + assertTrue(task instanceof WaitTask.Delay); + task.execute(null); + + task = sut.getNextTask(); + assertEquals(RT_L_1, task); + task.execute(null); + + sut.getNextTask().execute(null); + sut.getNextTask().execute(null); + sut.onExecuteWrite(); + sut.getNextTask().execute(null); + sut.getNextTask().execute(null); + sut.getNextTask(); + + // Cycle 2 + sut.onBeforeProcessImage(); + } + + @Test + public void testNoTasks() throws OpenemsException, InterruptedException { + var cycle1 = CycleTasks.create() // + .build(); + var tasksSupplier = new DummyTasksSupplier(cycle1); + var defectiveComponents = new DefectiveComponents(); + + var sut = new CycleTasksManager(tasksSupplier, defectiveComponents, CYCLE_TIME_IS_TOO_SHORT, CYCLE_DELAY); + + // Cycle 1 + sut.onBeforeProcessImage(); + var task = sut.getNextTask(); + assertTrue(task instanceof WaitTask.Delay); + task.execute(null); + + sut.onBeforeProcessImage(); + + task = sut.getNextTask(); + assertTrue(task instanceof WaitTask.Mutex); + } +} diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponentsTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponentsTest.java new file mode 100644 index 00000000000..6074b868e7c --- /dev/null +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponentsTest.java @@ -0,0 +1,51 @@ +package io.openems.edge.bridge.modbus.api.worker.internal; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.time.temporal.ChronoUnit; + +import org.junit.Test; + +import io.openems.edge.common.test.TimeLeapClock; + +public class DefectiveComponentsTest { + + private static final String CMP = "foo"; + + @Test + public void testIsDueForNextTry() { + var clock = new TimeLeapClock(); + var sut = new DefectiveComponents(clock); + + assertNull(sut.isDueForNextTry(CMP)); + sut.add(CMP); + assertFalse(sut.isDueForNextTry(CMP)); + clock.leap(30_001, ChronoUnit.MILLIS); + assertTrue(sut.isDueForNextTry(CMP)); + } + + @Test + public void testAddRemove() { + var clock = new TimeLeapClock(); + var sut = new DefectiveComponents(clock); + + sut.add(CMP); + clock.leap(30_001, ChronoUnit.MILLIS); + assertTrue(sut.isDueForNextTry(CMP)); + sut.remove(CMP); + assertNull(sut.isDueForNextTry(CMP)); + } + + @Test + public void testIsKnownw() { + var sut = new DefectiveComponents(); + + sut.add(CMP); + assertTrue(sut.isKnown(CMP)); + sut.remove(CMP); + assertFalse(sut.isKnown(CMP)); + } + +} diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/DummyTasksSupplier.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/DummyTasksSupplier.java new file mode 100644 index 00000000000..8d739880f6c --- /dev/null +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/DummyTasksSupplier.java @@ -0,0 +1,35 @@ +package io.openems.edge.bridge.modbus.api.worker.internal; + +import java.util.LinkedList; +import java.util.Queue; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.google.common.collect.Streams; + +public class DummyTasksSupplier implements TasksSupplier { + + private final Queue records; + private final int totalNumberOfTasks; + + public DummyTasksSupplier(CycleTasks... records) { + this.records = Stream.of(records) // + .collect(Collectors.toCollection(LinkedList::new)); + this.totalNumberOfTasks = (int) Streams.concat(// + Stream.of(records).flatMap(t -> t.reads().stream()), + Stream.of(records).flatMap(t -> t.writes().stream())) // + .distinct() // + .count(); + } + + @Override + public CycleTasks getCycleTasks(DefectiveComponents defectiveComponents) { + return this.records.poll(); + } + + @Override + public int getTotalNumberOfTasks() { + return this.totalNumberOfTasks; + } + +} diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/FakeTicker.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/FakeTicker.java new file mode 100644 index 00000000000..af5569ecbdf --- /dev/null +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/FakeTicker.java @@ -0,0 +1,97 @@ +// Source: https://raw.githubusercontent.com/google/guava/master/guava-testlib/src/com/google/common/testing/FakeTicker.java + +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// CHECKSTYLE:OFF +package io.openems.edge.bridge.modbus.api.worker.internal; + +import static com.google.common.base.Preconditions.checkArgument; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +import com.google.common.base.Ticker; + +/** + * A Ticker whose value can be advanced programmatically in test. + * + *

    + * The ticker can be configured so that the time is incremented whenever + * {@link #read} is called: see {@link #setAutoIncrementStep}. + * + *

    + * This class is thread-safe. + * + * @author Jige Yu + * @since 10.0 + */ +public class FakeTicker extends Ticker { + + private final AtomicLong nanos = new AtomicLong(); + private volatile long autoIncrementStepNanos; + + /** Advances the ticker value by {@code time} in {@code timeUnit}. */ + public FakeTicker advance(long time, TimeUnit timeUnit) { + return advance(timeUnit.toNanos(time)); + } + + /** Advances the ticker value by {@code nanoseconds}. */ + public FakeTicker advance(long nanoseconds) { + nanos.addAndGet(nanoseconds); + return this; + } + + /** + * Advances the ticker value by {@code duration}. + * + * @since 28.0 + */ + public FakeTicker advance(java.time.Duration duration) { + return advance(duration.toNanos()); + } + + /** + * Sets the increment applied to the ticker whenever it is queried. + * + *

    + * The default behavior is to auto increment by zero. i.e: The ticker is left + * unchanged when queried. + */ + public FakeTicker setAutoIncrementStep(long autoIncrementStep, TimeUnit timeUnit) { + checkArgument(autoIncrementStep >= 0, "May not auto-increment by a negative amount"); + this.autoIncrementStepNanos = timeUnit.toNanos(autoIncrementStep); + return this; + } + + /** + * Sets the increment applied to the ticker whenever it is queried. + * + *

    + * The default behavior is to auto increment by zero. i.e: The ticker is left + * unchanged when queried. + * + * @since 28.0 + */ + public FakeTicker setAutoIncrementStep(java.time.Duration autoIncrementStep) { + return setAutoIncrementStep(autoIncrementStep.toNanos(), TimeUnit.NANOSECONDS); + } + + @Override + public long read() { + return nanos.getAndAdd(autoIncrementStepNanos); + } +} +//CHECKSTYLE:ON \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImplTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImplTest.java new file mode 100644 index 00000000000..4b8f1091639 --- /dev/null +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImplTest.java @@ -0,0 +1,106 @@ +package io.openems.edge.bridge.modbus.api.worker.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.time.temporal.ChronoUnit; + +import org.junit.Before; +import org.junit.Test; + +import io.openems.common.exceptions.OpenemsException; +import io.openems.edge.bridge.modbus.DummyModbusComponent; +import io.openems.edge.bridge.modbus.api.worker.DummyReadTask; +import io.openems.edge.bridge.modbus.api.worker.DummyWriteTask; +import io.openems.edge.common.taskmanager.Priority; +import io.openems.edge.common.test.TimeLeapClock; + +public class TasksSupplierImplTest { + + private static DummyReadTask RT_H_1; + private static DummyReadTask RT_H_2; + private static DummyReadTask RT_L_1; + private static DummyReadTask RT_L_2; + private static DummyWriteTask WT_1; + + @Before + public void before() { + RT_H_1 = new DummyReadTask("RT_H_1", 49, Priority.HIGH); + RT_H_2 = new DummyReadTask("RT_H_2", 70, Priority.HIGH); + RT_L_1 = new DummyReadTask("RT_L_1", 20, Priority.LOW); + RT_L_2 = new DummyReadTask("RT_L_2", 30, Priority.LOW); + WT_1 = new DummyWriteTask("WT_1", 90); + } + + @Test + public void testFull() throws OpenemsException { + var clock = new TimeLeapClock(); + var defectiveComponents = new DefectiveComponents(clock); + var sut = new TasksSupplierImpl(); + + var component = new DummyModbusComponent(); + var protocol = component.getModbusProtocol(); + protocol.addTasks(RT_H_1, RT_H_2, RT_L_1, RT_L_2, WT_1); + sut.addProtocol(component.id(), protocol); + + // 1st Cycle + var tasks = sut.getCycleTasks(defectiveComponents); + assertEquals(4, tasks.reads().size() + tasks.writes().size()); + assertEquals(RT_L_1, tasks.reads().get(0)); + assertEquals(RT_H_1, tasks.reads().get(1)); + assertEquals(RT_H_2, tasks.reads().get(2)); + assertEquals(WT_1, tasks.writes().get(0)); + assertFalse(tasks.reads().contains(RT_L_2)); // -> not + + // 2nd Cycle + tasks = sut.getCycleTasks(defectiveComponents); + assertEquals(4, tasks.reads().size() + tasks.writes().size()); + assertEquals(RT_L_2, tasks.reads().get(0)); + assertEquals(RT_H_1, tasks.reads().get(1)); + assertEquals(RT_H_2, tasks.reads().get(2)); + assertEquals(WT_1, tasks.writes().get(0)); + assertFalse(tasks.reads().contains(RT_L_1)); // -> not + + // Add to defective + defectiveComponents.add(component.id()); + + // 3rd Cycle -> not yet due + tasks = sut.getCycleTasks(defectiveComponents); + assertEquals(0, tasks.reads().size() + tasks.writes().size()); + + // 4th Cycle -> due: total one task + clock.leap(30_001, ChronoUnit.MILLIS); + tasks = sut.getCycleTasks(defectiveComponents); + assertEquals(1, tasks.reads().size() + tasks.writes().size()); + + // Remove from defective + defectiveComponents.remove(component.id()); + + // 5th Cycle -> back to normal + tasks = sut.getCycleTasks(defectiveComponents); + assertEquals(4, tasks.reads().size() + tasks.writes().size()); + + // Finish + sut.removeProtocol(component.id()); + } + + @Test + public void testHighOnly() throws OpenemsException { + var clock = new TimeLeapClock(); + var defectiveComponents = new DefectiveComponents(clock); + var sut = new TasksSupplierImpl(); + + var component = new DummyModbusComponent(); + var protocol = component.getModbusProtocol(); + protocol.addTasks(RT_H_1, RT_H_2, WT_1); + sut.addProtocol(component.id(), protocol); + + var tasks = sut.getCycleTasks(defectiveComponents); + assertEquals(3, tasks.reads().size() + tasks.writes().size()); + assertTrue(tasks.reads().contains(RT_H_1)); + assertTrue(tasks.reads().contains(RT_H_2)); + assertTrue(tasks.writes().contains(WT_1)); + } + +} diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/WaitDelayHandlerTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/WaitDelayHandlerTest.java new file mode 100644 index 00000000000..16eec68bc09 --- /dev/null +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/WaitDelayHandlerTest.java @@ -0,0 +1,47 @@ +package io.openems.edge.bridge.modbus.api.worker.internal; + +import static org.junit.Assert.assertEquals; + +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import org.junit.Test; + +import com.google.common.collect.Lists; + +public class WaitDelayHandlerTest { + + private static Runnable NO_OP = () -> { + }; + private static Consumer CYCLE_DELAY = (value) -> { + }; + + @Test + public void test() { + var ticker = new FakeTicker(); + var sut = new WaitDelayHandler(ticker, NO_OP, CYCLE_DELAY); + + sut.onFinished(); + ticker.advance(100, TimeUnit.MILLISECONDS); + sut.onBeforeProcessImage(false); + + sut.onFinished(); + ticker.advance(100, TimeUnit.MILLISECONDS); + sut.onBeforeProcessImage(false); + } + + @Test + public void testGenerateWaitDelayTask() { + // 12 - BUFFER_MS -> 0 + var possibleDelays = Lists.newArrayList(5L, 20L, 100L); + assertEquals(0, WaitDelayHandler.generateWaitDelayTask(possibleDelays, NO_OP).initialDelay); + + // 40 - BUFFER_MS -> 20 + possibleDelays = Lists.newArrayList(30L, 50L, 50L); + assertEquals(20, WaitDelayHandler.generateWaitDelayTask(possibleDelays, NO_OP).initialDelay); + + // Empty + possibleDelays = Lists.newArrayList(); + assertEquals(0, WaitDelayHandler.generateWaitDelayTask(possibleDelays, NO_OP).initialDelay); + } +} diff --git a/io.openems.edge.common/src/io/openems/edge/common/taskmanager/MetaTasksManager.java b/io.openems.edge.common/src/io/openems/edge/common/taskmanager/MetaTasksManager.java deleted file mode 100644 index 7e5786fa51d..00000000000 --- a/io.openems.edge.common/src/io/openems/edge/common/taskmanager/MetaTasksManager.java +++ /dev/null @@ -1,121 +0,0 @@ -package io.openems.edge.common.taskmanager; - -import java.util.EnumMap; -import java.util.LinkedList; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Queue; - -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Multimap; -import com.google.common.collect.Multimaps; - -/** - * Manages a number of {@link TasksManager}s. - * - *

    - * A useful application for MetaTasksManager is to provide a list of Tasks that - * need to be handled on an OpenEMS Cycle run. - * - * @param the type of the actual {@link ManagedTask} - */ -public class MetaTasksManager { - - private final Multimap> tasksManagers = Multimaps - .synchronizedListMultimap(ArrayListMultimap.create()); - private final Map> nextTasks; - - public MetaTasksManager() { - // initialize Queues for next tasks - var nextTasks = new EnumMap>(Priority.class); - for (Priority priority : Priority.values()) { - nextTasks.put(priority, new LinkedList<>()); - } - this.nextTasks = nextTasks; - } - - /** - * Adds a TasksManager. - * - * @param sourceId a source identifier - * @param tasksManager the TasksManager - */ - public synchronized void addTasksManager(String sourceId, TasksManager tasksManager) { - this.tasksManagers.put(sourceId, tasksManager); - } - - /** - * Removes a TasksManager. - * - * @param sourceId a source identifier - * @param tasksManager the TasksManager - */ - public synchronized void removeTasksManager(String sourceId, TasksManager tasksManager) { - this.tasksManagers.remove(sourceId, tasksManager); - } - - /** - * Removes all TasksManagers with the given Source-ID. - * - * @param sourceId a source identifier - */ - public synchronized void removeTasksManager(String sourceId) { - this.tasksManagers.removeAll(sourceId); - } - - /** - * Gets the next task with the given Priority sequentially. - * - * @param priority the {@link Priority} - * @return the next task; null if there are no tasks with the given Priority - */ - public synchronized T getOneTask(Priority priority) { - var tasks = this.nextTasks.get(priority); - if (tasks.isEmpty()) { - // refill the queue - for (TasksManager tasksManager : this.tasksManagers.values()) { - tasks.addAll(tasksManager.getTasks(priority)); - } - } - - // returns the head or 'null' if the queue is still empty after refilling it - return tasks.poll(); - } - - /** - * Gets all Tasks with the given Priority by their Source-ID. - * - * @param priority the priority - * @return a list of tasks - */ - public Multimap getAllTasksBySourceId(Priority priority) { - Multimap result = ArrayListMultimap.create(); - for (Entry> entry : this.tasksManagers.entries()) { - result.putAll(entry.getKey(), entry.getValue().getTasks(priority)); - } - return result; - } - - /** - * Gets all Tasks with by their Source-ID. - * - * @return a list of tasks - */ - public Multimap getAllTasksBySourceId() { - Multimap result = ArrayListMultimap.create(); - for (Entry> entry : this.tasksManagers.entries()) { - result.putAll(entry.getKey(), entry.getValue().getTasks()); - } - return result; - } - - /** - * Does this {@link TasksManager} have any Tasks?. - * - * @return true if there are Tasks - */ - public boolean hasTasks() { - return !this.tasksManagers.isEmpty(); - } - -} \ No newline at end of file diff --git a/io.openems.edge.common/src/io/openems/edge/common/type/Tuple.java b/io.openems.edge.common/src/io/openems/edge/common/type/Tuple.java new file mode 100644 index 00000000000..5621c1a6509 --- /dev/null +++ b/io.openems.edge.common/src/io/openems/edge/common/type/Tuple.java @@ -0,0 +1,4 @@ +package io.openems.edge.common.type; + +public record Tuple(A a, B b) { +} diff --git a/io.openems.edge.edge2edge/bnd.bnd b/io.openems.edge.edge2edge/bnd.bnd index 0d566c38035..f0d5bb62d42 100644 --- a/io.openems.edge.edge2edge/bnd.bnd +++ b/io.openems.edge.edge2edge/bnd.bnd @@ -5,11 +5,11 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ io.openems.edge.ess.api,\ -testpath: \ - ${testpath},\ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.edge2edge/src/io/openems/edge/edge2edge/common/AbstractEdge2Edge.java b/io.openems.edge.edge2edge/src/io/openems/edge/edge2edge/common/AbstractEdge2Edge.java index 70ae7a78710..c02f596b8ef 100644 --- a/io.openems.edge.edge2edge/src/io/openems/edge/edge2edge/common/AbstractEdge2Edge.java +++ b/io.openems.edge.edge2edge/src/io/openems/edge/edge2edge/common/AbstractEdge2Edge.java @@ -228,8 +228,8 @@ private void mapRemoteChannels(TreeMap natureStartAddresses) thr .map(method -> method.apply(this.remoteAccessMode)) // .collect(Collectors.toUnmodifiableList()); - Deque> readElements = new ArrayDeque<>(); - Deque> writeElements = new ArrayDeque<>(); + var readElements = new ArrayDeque>(); + var writeElements = new ArrayDeque>(); for (var entry : natureStartAddresses.entrySet()) { var natureStartAddress = entry.getKey(); var hash = entry.getValue(); @@ -261,8 +261,7 @@ private void mapRemoteChannels(TreeMap natureStartAddresses) thr } } - if (record instanceof ModbusRecordChannel) { - var r = (ModbusRecordChannel) record; + if (record instanceof ModbusRecordChannel r) { m(r.getChannelId(), element); } else { @@ -295,7 +294,7 @@ private void mapRemoteChannels(TreeMap natureStartAddresses) thr */ { var length = 0; - var taskElements = new ArrayDeque>(); + var taskElements = new ArrayDeque>(); var element = readElements.pollFirst(); while (element != null) { if (length + element.getLength() > 126 /* limit of j2mod */) { @@ -314,7 +313,7 @@ private void mapRemoteChannels(TreeMap natureStartAddresses) thr * Add the Write-Task(s) */ { - var taskElements = new ArrayDeque>(); + var taskElements = new ArrayDeque>(); var element = writeElements.pollFirst(); while (element != null) { var lastElement = taskElements.peekLast(); @@ -362,7 +361,7 @@ protected abstract io.openems.edge.common.channel.ChannelId getWriteChannelId( * @param address the address of the {@link ModbusElement} * @return the {@link ModbusElement} */ - private static AbstractModbusElement generateModbusElement(ModbusType type, int address) { + private static AbstractModbusElement generateModbusElement(ModbusType type, int address) { switch (type) { case ENUM16: case UINT16: @@ -391,7 +390,7 @@ private static AbstractModbusElement generateModbusElement(ModbusType type, i * @param elements the {@link ModbusElement}s * @throws OpenemsException on error */ - private void addReadTask(Deque> elements) throws OpenemsException { + private void addReadTask(Deque> elements) throws OpenemsException { if (elements.isEmpty()) { return; } @@ -404,7 +403,7 @@ private void addReadTask(Deque> elements) throws Openem this.modbusProtocol.addTask(// new FC3ReadRegistersTask(// elements.peekFirst().getStartAddress(), Priority.HIGH, - elements.toArray(new AbstractModbusElement[elements.size()]))); + elements.toArray(new ModbusElement[elements.size()]))); } /** @@ -413,14 +412,13 @@ private void addReadTask(Deque> elements) throws Openem * @param elements the {@link ModbusElement}s * @throws OpenemsException on error */ - private void addWriteTask(Deque> elements) throws OpenemsException { + private void addWriteTask(Deque> elements) throws OpenemsException { if (elements.isEmpty()) { return; } this.modbusProtocol.addTask(// new FC16WriteRegistersTask(// - elements.peekFirst().getStartAddress(), - elements.toArray(new AbstractModbusElement[elements.size()]))); + elements.peekFirst().getStartAddress(), elements.toArray(new ModbusElement[elements.size()]))); } /** diff --git a/io.openems.edge.ess.adstec.storaxe/bnd.bnd b/io.openems.edge.ess.adstec.storaxe/bnd.bnd index 7692aa89946..8da0bff98b5 100644 --- a/io.openems.edge.ess.adstec.storaxe/bnd.bnd +++ b/io.openems.edge.ess.adstec.storaxe/bnd.bnd @@ -5,6 +5,7 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ @@ -12,5 +13,4 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.meter.api -testpath: \ - ${testpath},\ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.ess.byd.container/bnd.bnd b/io.openems.edge.ess.byd.container/bnd.bnd index e9ba8b4f105..c9189ef0245 100644 --- a/io.openems.edge.ess.byd.container/bnd.bnd +++ b/io.openems.edge.ess.byd.container/bnd.bnd @@ -5,6 +5,7 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ @@ -12,5 +13,4 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.ess.api -testpath: \ - ${testpath},\ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.ess.fenecon.commercial40/bnd.bnd b/io.openems.edge.ess.fenecon.commercial40/bnd.bnd index bf2688ac234..1adc7937361 100644 --- a/io.openems.edge.ess.fenecon.commercial40/bnd.bnd +++ b/io.openems.edge.ess.fenecon.commercial40/bnd.bnd @@ -5,6 +5,7 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ @@ -13,5 +14,4 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.timedata.api -testpath: \ - ${testpath},\ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.ess.mr.gridcon/bnd.bnd b/io.openems.edge.ess.mr.gridcon/bnd.bnd index 0dff92fa002..4ebae0cb9b6 100644 --- a/io.openems.edge.ess.mr.gridcon/bnd.bnd +++ b/io.openems.edge.ess.mr.gridcon/bnd.bnd @@ -5,6 +5,7 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.battery.api,\ io.openems.edge.bridge.modbus,\ @@ -15,4 +16,4 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.meter.api -testpath: \ - ${testpath} + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.ess.mr.gridcon/src/io/openems/edge/ess/mr/gridcon/GridconPcsImpl.java b/io.openems.edge.ess.mr.gridcon/src/io/openems/edge/ess/mr/gridcon/GridconPcsImpl.java index 89ff4d80216..e70b13d4ba9 100644 --- a/io.openems.edge.ess.mr.gridcon/src/io/openems/edge/ess/mr/gridcon/GridconPcsImpl.java +++ b/io.openems.edge.ess.mr.gridcon/src/io/openems/edge/ess/mr/gridcon/GridconPcsImpl.java @@ -1149,7 +1149,7 @@ public float getDcLinkPositiveVoltage() { @Override public boolean isCommunicationBroken() { - return this.getModbusCommunicationFailed().get() == Boolean.TRUE; + return this.getModbusCommunicationFailed(); } @Override diff --git a/io.openems.edge.ess.mr.gridcon/src/io/openems/edge/ess/mr/gridcon/state/onoffgrid/DecisionTableConditionImpl.java b/io.openems.edge.ess.mr.gridcon/src/io/openems/edge/ess/mr/gridcon/state/onoffgrid/DecisionTableConditionImpl.java index 90a81f9fab9..ff0945deba8 100644 --- a/io.openems.edge.ess.mr.gridcon/src/io/openems/edge/ess/mr/gridcon/state/onoffgrid/DecisionTableConditionImpl.java +++ b/io.openems.edge.ess.mr.gridcon/src/io/openems/edge/ess/mr/gridcon/state/onoffgrid/DecisionTableConditionImpl.java @@ -97,12 +97,10 @@ public MeterCommunicationFailed isMeterCommunicationFailed() { try { ModbusComponent meter = this.manager.getComponent(this.meterId); - if (meter.getModbusCommunicationFailed().get() == Boolean.TRUE) { + if (meter.getModbusCommunicationFailed()) { return MeterCommunicationFailed.TRUE; - } else if (meter.getModbusCommunicationFailed().get() == Boolean.FALSE) { - return MeterCommunicationFailed.FALSE; } else { - return MeterCommunicationFailed.UNSET; + return MeterCommunicationFailed.FALSE; } } catch (Exception e) { return MeterCommunicationFailed.UNSET; diff --git a/io.openems.edge.ess.sma/bnd.bnd b/io.openems.edge.ess.sma/bnd.bnd index 358ca92864a..efd982af916 100644 --- a/io.openems.edge.ess.sma/bnd.bnd +++ b/io.openems.edge.ess.sma/bnd.bnd @@ -5,11 +5,11 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ io.openems.edge.ess.api -testpath: \ - ${testpath},\ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.evcs.webasto.next/bnd.bnd b/io.openems.edge.evcs.webasto.next/bnd.bnd index 5138da876f7..d8f6c880de2 100644 --- a/io.openems.edge.evcs.webasto.next/bnd.bnd +++ b/io.openems.edge.evcs.webasto.next/bnd.bnd @@ -5,11 +5,11 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ io.openems.edge.evcs.api,\ -testpath: \ - ${testpath},\ - com.ghgande.j2mod,\ + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.evcs.webasto.unite/bnd.bnd b/io.openems.edge.evcs.webasto.unite/bnd.bnd index 948eb642dda..2f4e7fe8a62 100644 --- a/io.openems.edge.evcs.webasto.unite/bnd.bnd +++ b/io.openems.edge.evcs.webasto.unite/bnd.bnd @@ -5,11 +5,11 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ io.openems.edge.evcs.api,\ -testpath: \ - ${testpath},\ - com.ghgande.j2mod,\ + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.fenecon.dess/bnd.bnd b/io.openems.edge.fenecon.dess/bnd.bnd index e3fbaec9dc2..aa10b109979 100644 --- a/io.openems.edge.fenecon.dess/bnd.bnd +++ b/io.openems.edge.fenecon.dess/bnd.bnd @@ -5,6 +5,7 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ @@ -13,5 +14,4 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.timedata.api,\ -testpath: \ - ${testpath},\ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.fenecon.mini/bnd.bnd b/io.openems.edge.fenecon.mini/bnd.bnd index f8c97486164..af769b710fd 100644 --- a/io.openems.edge.fenecon.mini/bnd.bnd +++ b/io.openems.edge.fenecon.mini/bnd.bnd @@ -5,6 +5,7 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ @@ -13,5 +14,4 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.timedata.api -testpath: \ - ${testpath},\ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.fenecon.pro/bnd.bnd b/io.openems.edge.fenecon.pro/bnd.bnd index 0df0314ca3e..2182145def6 100644 --- a/io.openems.edge.fenecon.pro/bnd.bnd +++ b/io.openems.edge.fenecon.pro/bnd.bnd @@ -5,6 +5,7 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ @@ -12,5 +13,4 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.meter.api -testpath: \ - ${testpath},\ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.goodwe/bnd.bnd b/io.openems.edge.goodwe/bnd.bnd index 90b076f1995..2ec2f9a5464 100644 --- a/io.openems.edge.goodwe/bnd.bnd +++ b/io.openems.edge.goodwe/bnd.bnd @@ -5,6 +5,7 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.battery.api,\ io.openems.edge.batteryinverter.api,\ @@ -16,5 +17,4 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.timedata.api,\ -testpath: \ - ${testpath},\ - com.ghgande.j2mod + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/AbstractGoodWe.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/AbstractGoodWe.java index 24140dd8cc8..56bdc7bec47 100644 --- a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/AbstractGoodWe.java +++ b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/AbstractGoodWe.java @@ -1688,7 +1688,7 @@ private void handleDspVersion5(ModbusProtocol protocol) throws OpenemsException ); } - protected AbstractModbusElement getSocModbusElement(int address) throws NotImplementedException { + protected AbstractModbusElement getSocModbusElement(int address) throws NotImplementedException { if (this instanceof HybridEss) { return m(SymmetricEss.ChannelId.SOC, new UnsignedWordElement(address), new ElementToChannelConverter( // element -> channel diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/gridmeter/GoodWeGridMeterImpl.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/gridmeter/GoodWeGridMeterImpl.java index 95a9f7df5d2..b132f30c5c5 100644 --- a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/gridmeter/GoodWeGridMeterImpl.java +++ b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/gridmeter/GoodWeGridMeterImpl.java @@ -139,8 +139,6 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException { m(GoodWeGridMeter.ChannelId.P_GRID_T, new SignedDoublewordElement(35134), SCALE_FACTOR_MINUS_2)), // - // Active and reactive power, Power factor and frequency - // Voltage, current and Grid Frequency of each phase new FC3ReadRegistersTask(36005, Priority.HIGH, // m(ElectricityMeter.ChannelId.ACTIVE_POWER_L1, new SignedWordElement(36005), INVERT), // m(ElectricityMeter.ChannelId.ACTIVE_POWER_L2, new SignedWordElement(36006), INVERT), // diff --git a/io.openems.edge.io.kmtronic/bnd.bnd b/io.openems.edge.io.kmtronic/bnd.bnd index 1e6dea47cc5..fe92c4735d7 100644 --- a/io.openems.edge.io.kmtronic/bnd.bnd +++ b/io.openems.edge.io.kmtronic/bnd.bnd @@ -5,11 +5,11 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ io.openems.edge.io.api -testpath: \ - ${testpath},\ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.io.wago/bnd.bnd b/io.openems.edge.io.wago/bnd.bnd index e1f56190dfa..d45d3c5a362 100644 --- a/io.openems.edge.io.wago/bnd.bnd +++ b/io.openems.edge.io.wago/bnd.bnd @@ -5,6 +5,7 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ @@ -13,5 +14,4 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.meter.api -testpath: \ - ${testpath},\ - com.ghgande.j2mod + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.io.wago/src/io/openems/edge/wago/IoWagoImpl.java b/io.openems.edge.io.wago/src/io/openems/edge/wago/IoWagoImpl.java index 2a8fb8331d5..957b36cb7e6 100644 --- a/io.openems.edge.io.wago/src/io/openems/edge/wago/IoWagoImpl.java +++ b/io.openems.edge.io.wago/src/io/openems/edge/wago/IoWagoImpl.java @@ -45,7 +45,6 @@ import io.openems.edge.bridge.modbus.api.BridgeModbusTcp; import io.openems.edge.bridge.modbus.api.ModbusComponent; import io.openems.edge.bridge.modbus.api.ModbusProtocol; -import io.openems.edge.bridge.modbus.api.element.AbstractModbusElement; import io.openems.edge.bridge.modbus.api.element.CoilElement; import io.openems.edge.bridge.modbus.api.element.ModbusCoilElement; import io.openems.edge.bridge.modbus.api.task.FC1ReadCoilsTask; @@ -251,11 +250,11 @@ protected void createProtocolFromModules(List modules) throws Op } if (!readCoilElements0.isEmpty()) { this.protocol.addTask(new FC1ReadCoilsTask(0, Priority.LOW, - readCoilElements0.toArray(new AbstractModbusElement[readCoilElements0.size()]))); + readCoilElements0.stream().toArray(ModbusCoilElement[]::new))); } if (!readCoilElements512.isEmpty()) { this.protocol.addTask(new FC1ReadCoilsTask(512, Priority.LOW, - readCoilElements512.toArray(new AbstractModbusElement[readCoilElements512.size()]))); + readCoilElements512.stream().toArray(ModbusCoilElement[]::new))); } } diff --git a/io.openems.edge.io.wago/test/io/openems/edge/wago/IoWagoImplTest.java b/io.openems.edge.io.wago/test/io/openems/edge/wago/IoWagoImplTest.java index 3170b7a2f6f..012eac53fd3 100644 --- a/io.openems.edge.io.wago/test/io/openems/edge/wago/IoWagoImplTest.java +++ b/io.openems.edge.io.wago/test/io/openems/edge/wago/IoWagoImplTest.java @@ -1,6 +1,7 @@ package io.openems.edge.wago; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import java.io.ByteArrayInputStream; import java.io.InputStream; @@ -9,8 +10,6 @@ import io.openems.edge.bridge.modbus.api.task.FC1ReadCoilsTask; import io.openems.edge.bridge.modbus.api.task.FC5WriteCoilTask; -import io.openems.edge.bridge.modbus.api.task.ReadTask; -import io.openems.edge.bridge.modbus.api.task.WriteTask; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; @@ -57,7 +56,7 @@ public void test() throws Exception { var sut = new IoWagoImpl(); new ComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge(MODBUS_ID).withIpAddress("127.0.0.1")) // .activate(MyConfig.create() // .setId(IO_ID) // .setModbusId(MODBUS_ID) // @@ -74,49 +73,20 @@ public void test() throws Exception { assertEquals(Fieldbus523RO1Ch.class, modules.get(1).getClass()); sut.createProtocolFromModules(modules); + var tasks = sut.protocol.getTaskManager().getTasks(); - { - var readTasks = sut.protocol.getReadTasksManager().getTasks(); - ReadTask t; - { - t = readTasks.get(0); - assertEquals(FC1ReadCoilsTask.class, t.getClass()); - assertEquals(0, t.getStartAddress()); - assertEquals(2, t.getLength()); - } - { - t = readTasks.get(1); - assertEquals(FC1ReadCoilsTask.class, t.getClass()); - assertEquals(512, t.getStartAddress()); - assertEquals(4, t.getLength()); - } - } - - { - var writeTasks = sut.protocol.getWriteTasksManager().getTasks(); - System.out.println(writeTasks); - WriteTask t; - { - t = writeTasks.get(0); - assertEquals(FC5WriteCoilTask.class, t.getClass()); - assertEquals(512, t.getStartAddress()); - } - { - t = writeTasks.get(1); - assertEquals(FC5WriteCoilTask.class, t.getClass()); - assertEquals(513, t.getStartAddress()); - } - { - t = writeTasks.get(2); - assertEquals(FC5WriteCoilTask.class, t.getClass()); - assertEquals(514, t.getStartAddress()); - } - { - t = writeTasks.get(3); - assertEquals(FC5WriteCoilTask.class, t.getClass()); - assertEquals(515, t.getStartAddress()); - } - } - + assertEquals(6, tasks.size()); + assertTrue(tasks.stream() // + .anyMatch(t -> t instanceof FC1ReadCoilsTask && t.getStartAddress() == 0 && t.getLength() == 2)); + assertTrue(tasks.stream() // + .anyMatch(t -> t instanceof FC1ReadCoilsTask && t.getStartAddress() == 512 && t.getLength() == 4)); + assertTrue(tasks.stream() // + .anyMatch(t -> t instanceof FC5WriteCoilTask && t.getStartAddress() == 512 && t.getLength() == 1)); + assertTrue(tasks.stream() // + .anyMatch(t -> t instanceof FC5WriteCoilTask && t.getStartAddress() == 513 && t.getLength() == 1)); + assertTrue(tasks.stream() // + .anyMatch(t -> t instanceof FC5WriteCoilTask && t.getStartAddress() == 514 && t.getLength() == 1)); + assertTrue(tasks.stream() // + .anyMatch(t -> t instanceof FC5WriteCoilTask && t.getStartAddress() == 515 && t.getLength() == 1)); } } diff --git a/io.openems.edge.io.weidmueller/bnd.bnd b/io.openems.edge.io.weidmueller/bnd.bnd index 44e751f5386..88f43d56564 100644 --- a/io.openems.edge.io.weidmueller/bnd.bnd +++ b/io.openems.edge.io.weidmueller/bnd.bnd @@ -5,11 +5,11 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ io.openems.edge.io.api,\ -testpath: \ - ${testpath},\ - com.ghgande.j2mod + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.meter.artemes.am2/bnd.bnd b/io.openems.edge.meter.artemes.am2/bnd.bnd index de132778cbd..29a9ef47e54 100644 --- a/io.openems.edge.meter.artemes.am2/bnd.bnd +++ b/io.openems.edge.meter.artemes.am2/bnd.bnd @@ -5,11 +5,11 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ io.openems.edge.meter.api -testpath: \ - ${testpath},\ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.meter.bcontrol.em300/bnd.bnd b/io.openems.edge.meter.bcontrol.em300/bnd.bnd index b5f6c305d25..e43e9de2bbc 100644 --- a/io.openems.edge.meter.bcontrol.em300/bnd.bnd +++ b/io.openems.edge.meter.bcontrol.em300/bnd.bnd @@ -5,11 +5,11 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ io.openems.edge.meter.api -testpath: \ - ${testpath},\ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.meter.bgetech/bnd.bnd b/io.openems.edge.meter.bgetech/bnd.bnd index 4e3fc102eef..83667922034 100644 --- a/io.openems.edge.meter.bgetech/bnd.bnd +++ b/io.openems.edge.meter.bgetech/bnd.bnd @@ -5,11 +5,11 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ io.openems.edge.meter.api -testpath: \ - ${testpath},\ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.meter.carlo.gavazzi.em300/bnd.bnd b/io.openems.edge.meter.carlo.gavazzi.em300/bnd.bnd index 9c4008951bf..f7e2d928736 100644 --- a/io.openems.edge.meter.carlo.gavazzi.em300/bnd.bnd +++ b/io.openems.edge.meter.carlo.gavazzi.em300/bnd.bnd @@ -5,11 +5,11 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ io.openems.edge.meter.api -testpath: \ - ${testpath},\ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.meter.janitza/bnd.bnd b/io.openems.edge.meter.janitza/bnd.bnd index 6191fbc9656..c2c6b880eb6 100644 --- a/io.openems.edge.meter.janitza/bnd.bnd +++ b/io.openems.edge.meter.janitza/bnd.bnd @@ -6,11 +6,11 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ io.openems.edge.meter.api -testpath: \ - ${testpath},\ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.meter.kdk/bnd.bnd b/io.openems.edge.meter.kdk/bnd.bnd index d20bf396c0d..fa67fa68cb3 100644 --- a/io.openems.edge.meter.kdk/bnd.bnd +++ b/io.openems.edge.meter.kdk/bnd.bnd @@ -5,11 +5,11 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ io.openems.edge.meter.api -testpath: \ - ${testpath},\ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.meter.microcare.sdm630/bnd.bnd b/io.openems.edge.meter.microcare.sdm630/bnd.bnd index 65999913749..4842bdc1c67 100644 --- a/io.openems.edge.meter.microcare.sdm630/bnd.bnd +++ b/io.openems.edge.meter.microcare.sdm630/bnd.bnd @@ -5,11 +5,11 @@ Bundle-License: Proprietary (for now) -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ io.openems.edge.meter.api -testpath: \ - ${testpath},\ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.meter.phoenixcontact/bnd.bnd b/io.openems.edge.meter.phoenixcontact/bnd.bnd index 3b8e0716615..30df595e153 100644 --- a/io.openems.edge.meter.phoenixcontact/bnd.bnd +++ b/io.openems.edge.meter.phoenixcontact/bnd.bnd @@ -6,11 +6,11 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ io.openems.edge.meter.api -testpath: \ - ${testpath},\ - com.ghgande.j2mod,\ + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.meter.plexlog/bnd.bnd b/io.openems.edge.meter.plexlog/bnd.bnd index 5f22904c09b..715037cab5e 100644 --- a/io.openems.edge.meter.plexlog/bnd.bnd +++ b/io.openems.edge.meter.plexlog/bnd.bnd @@ -6,11 +6,11 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ io.openems.edge.meter.api -testpath: \ - ${testpath},\ - com.ghgande.j2mod + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.meter.pqplus/bnd.bnd b/io.openems.edge.meter.pqplus/bnd.bnd index 38050940f32..c41e016c603 100644 --- a/io.openems.edge.meter.pqplus/bnd.bnd +++ b/io.openems.edge.meter.pqplus/bnd.bnd @@ -5,11 +5,11 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ io.openems.edge.meter.api -testpath: \ - ${testpath},\ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.meter.schneider.acti9.smartlink/bnd.bnd b/io.openems.edge.meter.schneider.acti9.smartlink/bnd.bnd index bb2b2688b3b..6ad1e94c94b 100644 --- a/io.openems.edge.meter.schneider.acti9.smartlink/bnd.bnd +++ b/io.openems.edge.meter.schneider.acti9.smartlink/bnd.bnd @@ -5,11 +5,11 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ io.openems.edge.meter.api -testpath: \ - ${testpath},\ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.meter.siemens/bnd.bnd b/io.openems.edge.meter.siemens/bnd.bnd index 83c52182b6b..f74625c0918 100644 --- a/io.openems.edge.meter.siemens/bnd.bnd +++ b/io.openems.edge.meter.siemens/bnd.bnd @@ -6,11 +6,11 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ io.openems.edge.meter.api,\ -testpath: \ - ${testpath}, \ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.meter.sma.shm20/bnd.bnd b/io.openems.edge.meter.sma.shm20/bnd.bnd index b5fcc6b88a7..97347455884 100644 --- a/io.openems.edge.meter.sma.shm20/bnd.bnd +++ b/io.openems.edge.meter.sma.shm20/bnd.bnd @@ -5,11 +5,11 @@ Bundle-License: https://opensource.org/licenses/EPL-2.0 -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ io.openems.edge.meter.api -testpath: \ - ${testpath},\ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.meter.socomec/bnd.bnd b/io.openems.edge.meter.socomec/bnd.bnd index 654e5dbe3c9..d899f9ff3a8 100644 --- a/io.openems.edge.meter.socomec/bnd.bnd +++ b/io.openems.edge.meter.socomec/bnd.bnd @@ -5,11 +5,11 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ io.openems.edge.meter.api -testpath: \ - ${testpath},\ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.meter.weidmueller/bnd.bnd b/io.openems.edge.meter.weidmueller/bnd.bnd index efc5693eb96..337557bd54c 100644 --- a/io.openems.edge.meter.weidmueller/bnd.bnd +++ b/io.openems.edge.meter.weidmueller/bnd.bnd @@ -5,11 +5,11 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ io.openems.edge.meter.api -testpath: \ - ${testpath},\ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.meter.ziehl/bnd.bnd b/io.openems.edge.meter.ziehl/bnd.bnd index d0517490c1f..3949ec2f6ae 100644 --- a/io.openems.edge.meter.ziehl/bnd.bnd +++ b/io.openems.edge.meter.ziehl/bnd.bnd @@ -5,11 +5,11 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ io.openems.edge.meter.api -testpath: \ - ${testpath},\ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.pvinverter.solarlog/bnd.bnd b/io.openems.edge.pvinverter.solarlog/bnd.bnd index 9803677988b..9e5dcb1873e 100644 --- a/io.openems.edge.pvinverter.solarlog/bnd.bnd +++ b/io.openems.edge.pvinverter.solarlog/bnd.bnd @@ -5,6 +5,7 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ @@ -13,5 +14,4 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.timedata.api,\ -testpath: \ - ${testpath},\ - com.ghgande.j2mod \ No newline at end of file + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.simulator/src/io/openems/edge/simulator/modbus/SimulatorModbusImpl.java b/io.openems.edge.simulator/src/io/openems/edge/simulator/modbus/SimulatorModbusImpl.java index 74c9c32b61f..5587b7eb084 100644 --- a/io.openems.edge.simulator/src/io/openems/edge/simulator/modbus/SimulatorModbusImpl.java +++ b/io.openems.edge.simulator/src/io/openems/edge/simulator/modbus/SimulatorModbusImpl.java @@ -67,4 +67,9 @@ public void removeProtocol(String sourceId) { // ignore } + @Override + public void retryModbusCommunication(String sourceId) { + // ignore + } + } From b0dff314066aa58e32a4d5202aebae60a3b451f9 Mon Sep 17 00:00:00 2001 From: Christian Lehne <51822163+clehne@users.noreply.github.com> Date: Thu, 3 Aug 2023 07:51:25 +0200 Subject: [PATCH 03/32] split TimestampedDatanotification into AggregatedDataNotification, TimestampedDataNotification and ResendDataNotification. Added/enhanced some utilities classes. (#2297) Thank you for providing us your code and for review. --- .../common/channel/PersistencePriority.java | 75 ++++++++++ .../common/function/ThrowingTriConsumer.java | 26 ++++ .../AbstractDataNotification.java | 95 +++++++++++++ .../AggregatedDataNotification.java | 48 +++++++ .../notification/ResendDataNotification.java | 41 ++++++ .../TimestampedDataNotification.java | 75 ++-------- .../openems/common/timedata/DurationUnit.java | 128 ++++++++++++++++++ .../openems/common/utils/CollectorUtils.java | 35 +++++ .../io/openems/common/utils/JsonUtils.java | 23 ++++ 9 files changed, 482 insertions(+), 64 deletions(-) create mode 100644 io.openems.common/src/io/openems/common/function/ThrowingTriConsumer.java create mode 100644 io.openems.common/src/io/openems/common/jsonrpc/notification/AbstractDataNotification.java create mode 100644 io.openems.common/src/io/openems/common/jsonrpc/notification/AggregatedDataNotification.java create mode 100644 io.openems.common/src/io/openems/common/jsonrpc/notification/ResendDataNotification.java create mode 100644 io.openems.common/src/io/openems/common/timedata/DurationUnit.java create mode 100644 io.openems.common/src/io/openems/common/utils/CollectorUtils.java diff --git a/io.openems.common/src/io/openems/common/channel/PersistencePriority.java b/io.openems.common/src/io/openems/common/channel/PersistencePriority.java index d59b1a2fb62..8be1fe47281 100644 --- a/io.openems.common/src/io/openems/common/channel/PersistencePriority.java +++ b/io.openems.common/src/io/openems/common/channel/PersistencePriority.java @@ -1,11 +1,86 @@ package io.openems.common.channel; +import io.openems.common.jsonrpc.notification.AggregatedDataNotification; +import io.openems.common.jsonrpc.notification.TimestampedDataNotification; + +/** + * The {@link PersistencePriority} is used by... + * + *

      + *
    • Timedata.Rrd4j: persist Channel values locally + *
    • Controller.Api.MQTT: transmit Channel values via MQTT + *
    • Controller.Api.Backend: transmit Channel values to OpenEMS Backend + *
    + * + *

    + * These services use the {@link PersistencePriority} to distinguish whether a + * value of a Channel should be: + *

      + *
    • persisted/transmitted in high resolution (e.g. once per second) + *
    • persisted/transmitted in low resolution (e.g. aggregated to 5 minutes + * values) + *
    • not persisted + *
    + * + *

    + * The {@link PersistencePriority} of a Channel may be set via + * AbstractDoc::persistencePriority(). Defaults are: + *

      + *
    • {@link #HIGH} for StateChannels + *
    • {@link #LOW} for everything else + *
    + */ public enum PersistencePriority { + /** + * Channels with at least this priority are by default (if not configured + * differently)... + * + *
      + *
    • Timedata.Rrd4j: not persisted + *
    • Controller.Api.MQTT: transmitted in high resolution + *
    • Controller.Api.Backend: not transmitted + *
    + */ VERY_LOW(0), // + + /** + * Channels with at least this priority are by default (if not configured + * differently)... + * + *
      + *
    • Controller.Api.Backend: transmitted as aggregated values (via + * {@link AggregatedDataNotification}) + *
    + */ LOW(1), // + + /** + * Channels with at least this priority are by default (if not configured + * differently)... + * + *
      + *
    • Timedata.Rrd4j: persisted as aggregated values + *
    + */ MEDIUM(2), // + + /** + * Channels with at least this priority are by default (if not configured + * differently)... + * + *
      + *
    • Controller.Api.Backend: transmitted in high resolution (via + * {@link TimestampedDataNotification}), i.e. on every change or at least once + * every 5 minutes + *
    + */ HIGH(3), // + + /** + * {@link PersistencePriority#VERY_HIGH} is reserved for Channels of the + * `Core.Sum` Component. + */ VERY_HIGH(4), // ; diff --git a/io.openems.common/src/io/openems/common/function/ThrowingTriConsumer.java b/io.openems.common/src/io/openems/common/function/ThrowingTriConsumer.java new file mode 100644 index 00000000000..1ace02da5a5 --- /dev/null +++ b/io.openems.common/src/io/openems/common/function/ThrowingTriConsumer.java @@ -0,0 +1,26 @@ +package io.openems.common.function; + +/** + * This interface is similar to the java.util interface + * {@link ThrowingBiConsumer}. Difference is, that it allows to pass to the + * apply() method one more parameter. + * + * @param the apply methods first argument type + * @param the apply methods second argument type + * @param the apply methods third argument type + * @param the exception type + */ +@FunctionalInterface +public interface ThrowingTriConsumer { + + /** + * Applies this function to the given arguments. + * + * @param t the first function argument + * @param u the second function argument + * @param s the third function argument + * @throws E on error + */ + public void accept(T t, U u, S s) throws E; + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/notification/AbstractDataNotification.java b/io.openems.common/src/io/openems/common/jsonrpc/notification/AbstractDataNotification.java new file mode 100644 index 00000000000..506ed5037e6 --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/notification/AbstractDataNotification.java @@ -0,0 +1,95 @@ +package io.openems.common.jsonrpc.notification; + +import java.util.Map; + +import com.google.common.collect.TreeBasedTable; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.jsonrpc.base.JsonrpcNotification; +import io.openems.common.utils.JsonUtils; + +/** + * Represents a JSON-RPC Notification for timestamped or aggregated data sent + * from Edge to Backend. + * + *
    + * {
    + *   "jsonrpc": "2.0",
    + *   "method": "timestampedData | aggregatedData",
    + *   "params": {
    + *     [timestamp: epoch in milliseconds]: {
    + *       [channelAddress]: T
    + *     }
    + *   }
    + * }
    + * 
    + */ +// TODO change to sealed class +public abstract class AbstractDataNotification extends JsonrpcNotification { + + private final TreeBasedTable data; + + protected static TreeBasedTable parseParams(// + final JsonObject params // + ) throws OpenemsNamedException { + var data = TreeBasedTable.create(); + for (var e1 : params.entrySet()) { + var timestamp = Long.parseLong(e1.getKey()); + var jTime = JsonUtils.getAsJsonObject(e1.getValue()); + for (var e2 : jTime.entrySet()) { + data.put(timestamp, e2.getKey(), e2.getValue()); + } + } + return data; + } + + protected AbstractDataNotification(String method, TreeBasedTable data) { + super(method); + this.data = data; + } + + /** + * Add timestamped data. + * + * @param timestamp the timestamp epoch in milliseconds + * @param data a map of Channel-Address to {@link JsonElement} value + */ + public void add(long timestamp, Map data) { + for (var entry : data.entrySet()) { + this.add(timestamp, entry.getKey(), entry.getValue()); + } + } + + /** + * Add a timestamped value. + * + * @param timestamp the timestamp epoch in milliseconds + * @param address the Channel-Address + * @param value the {@link JsonElement} value + */ + public void add(long timestamp, String address, JsonElement value) { + this.data.put(timestamp, address, value); + } + + @Override + public JsonObject getParams() { + var p = new JsonObject(); + for (var e1 : this.data.rowMap().entrySet()) { + var jTime = new JsonObject(); + for (var e2 : e1.getValue().entrySet()) { + var address = e2.getKey(); + var value = e2.getValue(); + jTime.add(address, value); + } + p.add(e1.getKey().toString(), jTime); + } + return p; + } + + public TreeBasedTable getData() { + return this.data; + } + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/notification/AggregatedDataNotification.java b/io.openems.common/src/io/openems/common/jsonrpc/notification/AggregatedDataNotification.java new file mode 100644 index 00000000000..a31f44d65b5 --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/notification/AggregatedDataNotification.java @@ -0,0 +1,48 @@ +package io.openems.common.jsonrpc.notification; + +import com.google.common.collect.TreeBasedTable; +import com.google.gson.JsonElement; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.jsonrpc.base.JsonrpcNotification; + +/** + * Represents a JSON-RPC Notification for aggregatedData data sent from Edge to + * Backend. + * + *
    + * {
    + *   "jsonrpc": "2.0",
    + *   "method": "aggregatedData",
    + *   "params": {
    + *     [timestamp: epoch in milliseconds]: {
    + *       [channelAddress]: {@link JsonElement}
    + *     }
    + *   }
    + * }
    + * 
    + */ +public class AggregatedDataNotification extends AbstractDataNotification { + + public static final String METHOD = "aggregatedData"; + + /** + * Parses a {@link JsonrpcNotification} to a {@link AggregatedDataNotification}. + * + * @param notification the {@link JsonrpcNotification} + * @return the {@link AggregatedDataNotification} + * @throws OpenemsNamedException on error + */ + public static AggregatedDataNotification from(JsonrpcNotification notification) throws OpenemsNamedException { + return new AggregatedDataNotification(parseParams(notification.getParams())); + } + + public AggregatedDataNotification(TreeBasedTable data) { + super(AggregatedDataNotification.METHOD, data); + } + + public AggregatedDataNotification() { + super(AggregatedDataNotification.METHOD, TreeBasedTable.create()); + } + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/notification/ResendDataNotification.java b/io.openems.common/src/io/openems/common/jsonrpc/notification/ResendDataNotification.java new file mode 100644 index 00000000000..55369473296 --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/notification/ResendDataNotification.java @@ -0,0 +1,41 @@ +package io.openems.common.jsonrpc.notification; + +import com.google.common.collect.TreeBasedTable; +import com.google.gson.JsonElement; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.jsonrpc.base.JsonrpcNotification; + +/** + * Represents a JSON-RPC Notification for resending aggregated data. + * + *
    + * {
    + *   "jsonrpc": "2.0",
    + *   "method": "resendData",
    + *   "params": {
    + *     [channelAddress]: string | number
    + *   }
    + * }
    + * 
    + */ +public class ResendDataNotification extends AbstractDataNotification { + + public static final String METHOD = "resendData"; + + /** + * Parses a {@link JsonrpcNotification} to a {@link ResendDataNotification}. + * + * @param notification the {@link JsonrpcNotification} + * @return the {@link ResendDataNotification} + * @throws OpenemsNamedException on error + */ + public static ResendDataNotification from(JsonrpcNotification notification) throws OpenemsNamedException { + return new ResendDataNotification(parseParams(notification.getParams())); + } + + public ResendDataNotification(TreeBasedTable data) { + super(ResendDataNotification.METHOD, data); + } + +} \ No newline at end of file diff --git a/io.openems.common/src/io/openems/common/jsonrpc/notification/TimestampedDataNotification.java b/io.openems.common/src/io/openems/common/jsonrpc/notification/TimestampedDataNotification.java index 615c2890082..be703c6cf15 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/notification/TimestampedDataNotification.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/notification/TimestampedDataNotification.java @@ -1,15 +1,10 @@ package io.openems.common.jsonrpc.notification; -import java.util.Map; -import java.util.Map.Entry; - import com.google.common.collect.TreeBasedTable; import com.google.gson.JsonElement; -import com.google.gson.JsonObject; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.jsonrpc.base.JsonrpcNotification; -import io.openems.common.utils.JsonUtils; /** * Represents a JSON-RPC Notification for timestamped data sent from Edge to @@ -21,82 +16,34 @@ * "method": "timestampedData", * "params": { * [timestamp: epoch in milliseconds]: { - * [channelAddress]: String | Number + * [channelAddress]: {@link JsonElement} * } * } * } * */ -public class TimestampedDataNotification extends JsonrpcNotification { +public class TimestampedDataNotification extends AbstractDataNotification { + + public static final String METHOD = "timestampedData"; /** * Parses a {@link JsonrpcNotification} to a * {@link TimestampedDataNotification}. * - * @param n the {@link JsonrpcNotification} + * @param notification the {@link JsonrpcNotification} * @return the {@link TimestampedDataNotification} * @throws OpenemsNamedException on error */ - public static TimestampedDataNotification from(JsonrpcNotification n) throws OpenemsNamedException { - var result = new TimestampedDataNotification(); - var j = n.getParams(); - for (Entry e1 : j.entrySet()) { - var timestamp = Long.parseLong(e1.getKey()); - var jTime = JsonUtils.getAsJsonObject(e1.getValue()); - for (Entry e2 : jTime.entrySet()) { - result.add(timestamp, e2.getKey(), e2.getValue()); - } - } - return result; + public static TimestampedDataNotification from(JsonrpcNotification notification) throws OpenemsNamedException { + return new TimestampedDataNotification(parseParams(notification.getParams())); } - public static final String METHOD = "timestampedData"; - - private final TreeBasedTable data = TreeBasedTable.create(); - - public TimestampedDataNotification() { - super(TimestampedDataNotification.METHOD); + public TimestampedDataNotification(TreeBasedTable data) { + super(TimestampedDataNotification.METHOD, data); } - /** - * Add timestamped data. - * - * @param timestamp the timestamp epoch in milliseconds - * @param data a map of Channel-Address to {@link JsonElement} value - */ - public void add(long timestamp, Map data) { - for (Entry entry : data.entrySet()) { - this.add(timestamp, entry.getKey(), entry.getValue()); - } - } - - /** - * Add a timestamped value. - * - * @param timestamp the timestamp epoch in milliseconds - * @param address the Channel-Address - * @param value the {@link JsonElement} value - */ - public void add(long timestamp, String address, JsonElement value) { - this.data.put(timestamp, address, value); - } - - @Override - public JsonObject getParams() { - var p = new JsonObject(); - for (Entry> e1 : this.data.rowMap().entrySet()) { - var jTime = new JsonObject(); - for (Entry e2 : e1.getValue().entrySet()) { - var address = e2.getKey(); - var value = e2.getValue(); - jTime.add(address, value); - } - p.add(e1.getKey().toString(), jTime); - } - return p; + public TimestampedDataNotification() { + super(TimestampedDataNotification.METHOD, TreeBasedTable.create()); } - public TreeBasedTable getData() { - return this.data; - } } diff --git a/io.openems.common/src/io/openems/common/timedata/DurationUnit.java b/io.openems.common/src/io/openems/common/timedata/DurationUnit.java new file mode 100644 index 00000000000..181b7755156 --- /dev/null +++ b/io.openems.common/src/io/openems/common/timedata/DurationUnit.java @@ -0,0 +1,128 @@ +package io.openems.common.timedata; + +import java.time.Duration; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalUnit; + +public final class DurationUnit implements TemporalUnit { + + private static final int SECONDS_PER_DAY = 86400; + private static final long NANOS_PER_SECOND = 1000_000_000L; + private static final long NANOS_PER_DAY = NANOS_PER_SECOND * SECONDS_PER_DAY; + + private final Duration duration; + + /** + * Get the {@link DurationUnit} for {@link Duration}. + * + * @param duration the {@link Duration} + * @return the {@link DurationUnit} + */ + public static DurationUnit of(Duration duration) { + return new DurationUnit(duration); + } + + /** + * Get the {@link DurationUnit} of days. + * + * @param days the amount days + * @return the {@link DurationUnit} + */ + public static DurationUnit ofDays(long days) { + return new DurationUnit(Duration.ofDays(days)); + } + + /** + * Get the {@link DurationUnit} of hours. + * + * @param hours the amount hours + * @return the {@link DurationUnit} + */ + public static DurationUnit ofHours(long hours) { + return new DurationUnit(Duration.ofHours(hours)); + } + + /** + * Get the {@link DurationUnit} of minutes. + * + * @param minutes the amount minutes + * @return the {@link DurationUnit} + */ + public static DurationUnit ofMinutes(long minutes) { + return new DurationUnit(Duration.ofMinutes(minutes)); + } + + /** + * Get the {@link DurationUnit} of seconds. + * + * @param seconds the amount seconds + * @return the {@link DurationUnit} + */ + public static DurationUnit ofSeconds(long seconds) { + return new DurationUnit(Duration.ofSeconds(seconds)); + } + + /** + * Get the {@link DurationUnit} of millis. + * + * @param millis the amount millis + * @return the {@link DurationUnit} + */ + public static DurationUnit ofMillis(long millis) { + return new DurationUnit(Duration.ofMillis(millis)); + } + + /** + * Get the {@link DurationUnit} of nanos. + * + * @param nanos the amount nanos + * @return the {@link DurationUnit} + */ + public static DurationUnit ofNanos(long nanos) { + return new DurationUnit(Duration.ofNanos(nanos)); + } + + private DurationUnit(Duration duration) { + if (duration.isZero() || duration.isNegative()) { + throw new IllegalArgumentException("Duration may not be zero or negative"); + } + this.duration = duration; + } + + @Override + public Duration getDuration() { + return this.duration; + } + + @Override + public boolean isDurationEstimated() { + return this.duration.getSeconds() >= SECONDS_PER_DAY; + } + + @Override + public boolean isDateBased() { + return this.duration.getNano() == 0 && this.duration.getSeconds() % SECONDS_PER_DAY == 0; + } + + @Override + public boolean isTimeBased() { + return this.duration.getSeconds() < SECONDS_PER_DAY && NANOS_PER_DAY % this.duration.toNanos() == 0; + } + + @Override + @SuppressWarnings("unchecked") + public R addTo(R temporal, long amount) { + return (R) this.duration.multipliedBy(amount).addTo(temporal); + } + + @Override + public long between(Temporal temporal1Inclusive, Temporal temporal2Exclusive) { + return Duration.between(temporal1Inclusive, temporal2Exclusive).dividedBy(this.duration); + } + + @Override + public String toString() { + return this.duration.toString(); + } + +} \ No newline at end of file diff --git a/io.openems.common/src/io/openems/common/utils/CollectorUtils.java b/io.openems.common/src/io/openems/common/utils/CollectorUtils.java new file mode 100644 index 00000000000..667a75b7f76 --- /dev/null +++ b/io.openems.common/src/io/openems/common/utils/CollectorUtils.java @@ -0,0 +1,35 @@ +package io.openems.common.utils; + +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Collectors; + +public final class CollectorUtils { + + private CollectorUtils() { + } + + /** + * Creates a {@link Collector} which collects the given input to a {@link Map} + * where the value is also a {@link Map}. + * + * @param the input of the collection + * @param the key of the first map + * @param the key of the second map + * @param the value of the second map + * @param firstKeyMapper the mapper-function of the first key + * @param secondKeyMapper the mapper-function of the second key + * @param valueMapper the mapper-function of the value + * @return the {@link Collector} + */ + public static final // + Collector>> toDoubleMap(// + Function firstKeyMapper, // + Function secondKeyMapper, // + Function valueMapper // + ) { + return Collectors.groupingBy(firstKeyMapper, Collectors.toMap(secondKeyMapper, valueMapper)); + } + +} diff --git a/io.openems.common/src/io/openems/common/utils/JsonUtils.java b/io.openems.common/src/io/openems/common/utils/JsonUtils.java index 756b36d35dd..f8bca713e73 100644 --- a/io.openems.common/src/io/openems/common/utils/JsonUtils.java +++ b/io.openems.common/src/io/openems/common/utils/JsonUtils.java @@ -508,6 +508,19 @@ public static JsonPrimitive getAsPrimitive(JsonElement jElement, String memberNa throw OpenemsError.JSON_NO_PRIMITIVE_MEMBER.exception(memberName, jElement.toString().replace("%", "%%")); } + /** + * Gets the member of the {@link JsonElement} as {@link Optional} + * {@link JsonPrimitive}. + * + * @param jElement the {@link JsonElement} + * @param memberName the name of the member + * @return the {@link Optional} {@link JsonPrimitive} value + * @throws OpenemsNamedException on error + */ + public static Optional getAsOptionalPrimitive(JsonElement jElement, String memberName) { + return Optional.ofNullable(toPrimitive(toSubElement(jElement, memberName))); + } + /** * Gets the member of the {@link JsonElement} as {@link JsonElement}. * @@ -1716,6 +1729,16 @@ public static boolean isEmptyJsonArray(JsonElement j) { return false; } + /** + * Check if the given {@link JsonElement} is a {@link Number}. + * + * @param j the {@link JsonElement} to check + * @return true if the element is a {@link Number}, otherwise false + */ + public static boolean isNumber(JsonElement j) { + return j.isJsonPrimitive() && j.getAsJsonPrimitive().isNumber(); + } + /** * Returns a sequential stream of the {@link JsonElement JsonElements} in the * {@link JsonArray}. From 25a681b836907f181ad51946c1ea0680a046e077 Mon Sep 17 00:00:00 2001 From: Stefan Feilmeier Date: Thu, 3 Aug 2023 09:29:23 +0200 Subject: [PATCH 04/32] Modbus Bridge: refactor & cleanup Elements (#2273) This is a follow-up PR to #1976. The initial intention for this PR was a bug that existed previously: When the Modbus Bridge detects a 'defective device', i.e. it is not possible to read from an external device connected via modbus, e.g. because it is turned off, somebody unplugged the cable, etc., all Channels are 'invalidated'. This invalidation did not work properly for individual Bits in `BitsWordElements`. When looking at the previous code it becomes obvious how such a bug could happen. The structure and inheritance was complicated and @Overrides were often unclear. Also there were hardly any JUnit tests to validate the function. Because of this I had added JUnit tests in #2283, then refactored all Modbus Element classes and revalidated that they behave exactly the same way as before with the JUnit tests. This implementation is tested on a couple of production systems, including my own private system. --- .../fenecon/home/BatteryFeneconHomeImpl.java | 4 +- .../BatterySoltaroClusterVersionBImpl.java | 9 +- .../soltaro/cluster/versionb/SingleRack.java | 8 +- .../BatterySoltaroClusterVersionCImpl.java | 8 +- .../BatterySoltaroSingleRackVersionBImpl.java | 4 +- .../BatterySoltaroSingleRackVersionCImpl.java | 8 +- .../api/AbstractOpenemsModbusComponent.java | 59 ++--- .../edge/bridge/modbus/api/ModbusUtils.java | 15 +- .../element/AbstractDoubleWordElement.java | 94 +------ .../api/element/AbstractModbusElement.java | 156 ++++++++---- .../AbstractModbusRegisterElement.java | 88 ------- .../element/AbstractMultipleWordsElement.java | 48 ++++ .../element/AbstractQuadrupleWordElement.java | 105 +------- .../element/AbstractSingleWordElement.java | 31 +++ .../api/element/AbstractWordElement.java | 79 ------ .../modbus/api/element/BitsWordElement.java | 240 +++++++----------- .../modbus/api/element/CoilElement.java | 47 +--- .../modbus/api/element/DummyCoilElement.java | 44 ---- .../modbus/api/element/DummyElement.java | 8 - .../api/element/DummyRegisterElement.java | 40 +-- .../api/element/FloatDoublewordElement.java | 8 +- .../element/FloatQuadruplewordElement.java | 9 +- .../modbus/api/element/ModbusCoilElement.java | 67 ----- .../modbus/api/element/ModbusElement.java | 84 +++--- .../api/element/ModbusRegisterElement.java | 146 +++++++---- .../api/element/ModbusWriteElement.java | 5 - .../api/element/SignedDoublewordElement.java | 6 +- .../element/SignedQuadruplewordElement.java | 8 +- .../modbus/api/element/SignedWordElement.java | 16 +- .../modbus/api/element/StringWordElement.java | 99 +++----- .../element/UnsignedDoublewordElement.java | 9 +- .../element/UnsignedQuadruplewordElement.java | 10 +- .../api/element/UnsignedWordElement.java | 14 +- .../task/AbstractReadDigitalInputsTask.java | 14 +- .../task/AbstractReadInputRegistersTask.java | 9 +- .../modbus/api/task/AbstractReadTask.java | 9 +- .../bridge/modbus/api/task/AbstractTask.java | 20 +- .../modbus/api/task/AbstractWriteTask.java | 5 +- .../api/task/FC16WriteRegistersTask.java | 17 +- .../modbus/api/task/FC1ReadCoilsTask.java | 4 +- .../modbus/api/task/FC2ReadInputsTask.java | 4 +- .../modbus/api/task/FC3ReadRegistersTask.java | 2 +- .../api/task/FC4ReadInputRegistersTask.java | 2 +- .../modbus/api/task/FC5WriteCoilTask.java | 12 +- .../modbus/api/task/FC6WriteRegisterTask.java | 27 +- .../edge/bridge/modbus/api/task/ReadTask.java | 8 +- .../edge/bridge/modbus/api/task/Task.java | 2 +- .../edge/bridge/modbus/api/task/WaitTask.java | 2 +- .../modbus/api/worker/ModbusWorker.java | 4 +- .../AbstractOpenemsSunSpecComponent.java | 18 +- .../bridge/modbus/sunspec/SunSpecPoint.java | 6 +- .../api/element/BitsWordElementTest.java | 50 ++-- .../modbus/api/element/CoilElementTest.java | 13 +- .../api/element/DummyCoilElementTest.java | 12 - .../element/FloatDoublewordElementTest.java | 17 +- .../FloatQuadruplewordElementTest.java | 10 +- .../bridge/modbus/api/element/ModbusTest.java | 22 +- .../element/SignedDoublewordElementTest.java | 10 +- .../SignedQuadruplewordElementTest.java | 12 +- .../api/element/SignedWordElementTest.java | 8 +- .../api/element/StringWordElementTest.java | 58 ++++- .../UnsignedDoublewordElementTest.java | 16 +- .../UnsignedQuadruplewordElementTest.java | 12 +- .../api/element/UnsignedWordElementTest.java | 25 +- .../api/task/FC16WriteRegistersTaskTest.java | 20 +- .../modbus/api/task/FC1ReadCoilsTaskTest.java | 4 +- .../api/task/FC2ReadInputsTaskTest.java | 4 +- .../modbus/api/task/FC5WriteCoilTaskTest.java | 4 +- .../edge2edge/common/AbstractEdge2Edge.java | 43 ++-- .../edge/goodwe/common/AbstractGoodWe.java | 4 +- .../io/openems/edge/wago/Fieldbus4xxDI.java | 16 +- .../openems/edge/wago/Fieldbus523RO1Ch.java | 27 +- .../io/openems/edge/wago/Fieldbus5xxDO.java | 18 +- .../io/openems/edge/wago/FieldbusModule.java | 8 +- .../src/io/openems/edge/wago/IoWagoImpl.java | 17 +- .../io/weidmueller/IoWeidmuellerUr20Impl.java | 12 +- 76 files changed, 876 insertions(+), 1307 deletions(-) delete mode 100644 io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractModbusRegisterElement.java create mode 100644 io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractMultipleWordsElement.java create mode 100644 io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractSingleWordElement.java delete mode 100644 io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractWordElement.java delete mode 100644 io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/DummyCoilElement.java delete mode 100644 io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/DummyElement.java delete mode 100644 io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/ModbusCoilElement.java delete mode 100644 io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/ModbusWriteElement.java delete mode 100644 io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/DummyCoilElementTest.java diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImpl.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImpl.java index 7507cf6feef..07290107234 100644 --- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImpl.java +++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImpl.java @@ -738,8 +738,8 @@ private synchronized void initializeTowerModulesChannels(int numberOfTowers, int * for Cell-Voltages.Channel-IDs are like "TOWER_0_OFFSET_2_TEMPERATURE_003". * Channel-IDs are like "TOWER_0_OFFSET_2_VOLTAGE_003". */ - var ameVolt = new ModbusElement[SENSORS_PER_MODULE]; - var ameTemp = new ModbusElement[SENSORS_PER_MODULE]; + var ameVolt = new ModbusElement[SENSORS_PER_MODULE]; + var ameTemp = new ModbusElement[SENSORS_PER_MODULE]; for (var j = 0; j < SENSORS_PER_MODULE; j++) { { // Create Voltage Channel diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionBImpl.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionBImpl.java index be9e350ac41..102b6e8481a 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionBImpl.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionBImpl.java @@ -43,9 +43,9 @@ import io.openems.edge.bridge.modbus.api.ElementToChannelConverter; import io.openems.edge.bridge.modbus.api.ModbusComponent; import io.openems.edge.bridge.modbus.api.ModbusProtocol; -import io.openems.edge.bridge.modbus.api.element.AbstractModbusElement; import io.openems.edge.bridge.modbus.api.element.BitsWordElement; import io.openems.edge.bridge.modbus.api.element.DummyRegisterElement; +import io.openems.edge.bridge.modbus.api.element.ModbusElement; import io.openems.edge.bridge.modbus.api.element.UnsignedWordElement; import io.openems.edge.bridge.modbus.api.task.FC16WriteRegistersTask; import io.openems.edge.bridge.modbus.api.task.FC3ReadRegistersTask; @@ -682,13 +682,12 @@ private int getAddressContactorControl(int addressOffsetRack) { return addressOffsetRack + OFFSET_CONTACTOR_CONTROL; } - protected final > T map(io.openems.edge.common.channel.ChannelId channelId, - T element) { + protected final T map(io.openems.edge.common.channel.ChannelId channelId, T element) { return this.m(channelId, element); } - protected final > T map(io.openems.edge.common.channel.ChannelId channelId, - T element, ElementToChannelConverter converter) { + protected final T map(io.openems.edge.common.channel.ChannelId channelId, T element, + ElementToChannelConverter converter) { return this.m(channelId, element, converter); } diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/SingleRack.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/SingleRack.java index 3c31e64c69a..45d65c88cfb 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/SingleRack.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/SingleRack.java @@ -252,7 +252,7 @@ protected Collection getTasks() { // Cell voltages for (var i = 0; i < this.numberOfSlaves; i++) { - var elements = new ArrayList>(); + var elements = new ArrayList(); for (var j = i * VOLTAGE_SENSORS_PER_MODULE; j < (i + 1) * VOLTAGE_SENSORS_PER_MODULE; j++) { var key = this.getSingleCellPrefix(j) + "_" + VOLTAGE; var uwe = this.getUnsignedWordElement(VOLTAGE_ADDRESS_OFFSET + j); @@ -268,14 +268,14 @@ protected Collection getTasks() { .subList(x * maxElementsPerTask, Math.min((x + 1) * maxElementsPerTask, elements.size())) // .stream() // .toArray(ModbusElement[]::new); - tasks.add(new FC3ReadRegistersTask(taskElements[0].getStartAddress(), Priority.LOW, taskElements)); + tasks.add(new FC3ReadRegistersTask(taskElements[0].startAddress, Priority.LOW, taskElements)); } } // Cell temperatures for (var i = 0; i < this.numberOfSlaves; i++) { - var elements = new ArrayList>(); + var elements = new ArrayList(); for (var j = i * TEMPERATURE_SENSORS_PER_MODULE; j < (i + 1) * TEMPERATURE_SENSORS_PER_MODULE; j++) { var key = this.getSingleCellPrefix(j) + "_" + TEMPERATURE; @@ -292,7 +292,7 @@ protected Collection getTasks() { .subList(x * maxElementsPerTask, Math.min((x + 1) * maxElementsPerTask, elements.size())) // .stream() // .toArray(ModbusElement[]::new); - tasks.add(new FC3ReadRegistersTask(taskElements[0].getStartAddress(), Priority.LOW, taskElements)); + tasks.add(new FC3ReadRegistersTask(taskElements[0].startAddress, Priority.LOW, taskElements)); } } diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImpl.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImpl.java index ab31dc4ed41..2e2fd7956cb 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImpl.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImpl.java @@ -386,7 +386,7 @@ private void updateRackChannels(Integer numberOfModules, TreeSet racks) th } // Consumer addCellChannels = type -> { for (var i = 0; i < numberOfModules; i++) { - var elements = new ModbusElement[type.getSensorsPerModule()]; + var elements = new ModbusElement[type.getSensorsPerModule()]; for (var j = 0; j < type.getSensorsPerModule(); j++) { var sensorIndex = i * type.getSensorsPerModule() + j; var channelId = CellChannelFactory.create(r, type, sensorIndex); @@ -411,7 +411,7 @@ private void updateRackChannels(Integer numberOfModules, TreeSet racks) th // WARN_LEVEL_Pre Alarm (Pre Alarm configuration registers RW) { - ModbusElement[] elements = { + ModbusElement[] elements = { m(this.createChannelId(r, RackChannel.PRE_ALARM_CELL_OVER_VOLTAGE_ALARM), new UnsignedWordElement(r.offset + 0x080)), // m(this.createChannelId(r, RackChannel.PRE_ALARM_CELL_OVER_VOLTAGE_RECOVER), @@ -486,7 +486,7 @@ private void updateRackChannels(Integer numberOfModules, TreeSet racks) th // WARN_LEVEL1 (Level1 warning registers RW) { - ModbusElement[] elements = { + ModbusElement[] elements = { m(this.createChannelId(r, RackChannel.LEVEL1_CELL_OVER_VOLTAGE_PROTECTION), new UnsignedWordElement(r.offset + 0x040)), // m(this.createChannelId(r, RackChannel.LEVEL1_CELL_OVER_VOLTAGE_RECOVER), @@ -561,7 +561,7 @@ private void updateRackChannels(Integer numberOfModules, TreeSet racks) th // WARN_LEVEL2 (Level2 Protection registers RW) { - ModbusElement[] elements = { + ModbusElement[] elements = { m(this.createChannelId(r, RackChannel.LEVEL2_CELL_OVER_VOLTAGE_PROTECTION), new UnsignedWordElement(r.offset + 0x400)), // m(this.createChannelId(r, RackChannel.LEVEL2_CELL_OVER_VOLTAGE_RECOVER), diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionBImpl.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionBImpl.java index e45f61768fe..293e9b0dd86 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionBImpl.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionBImpl.java @@ -966,8 +966,8 @@ private void calculateCapacity(Integer numberOfModules) { private void createDynamicChannels(int numberOfModules) { try { for (var i = 0; i < numberOfModules; i++) { - var ameVolt = new ModbusElement[SENSORS_PER_MODULE]; - var ameTemp = new ModbusElement[SENSORS_PER_MODULE]; + var ameVolt = new ModbusElement[SENSORS_PER_MODULE]; + var ameTemp = new ModbusElement[SENSORS_PER_MODULE]; for (var j = 0; j < SENSORS_PER_MODULE; j++) { var sensor = i * SENSORS_PER_MODULE + j; { diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImpl.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImpl.java index fdfc489c9ba..3e17ae11b05 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImpl.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImpl.java @@ -492,7 +492,7 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException { .bit(12, BatterySoltaroSingleRackVersionC.ChannelId.SLAVE_BMS_INIT)// ))); // { - ModbusElement[] elements = { + ModbusElement[] elements = { m(BatterySoltaroSingleRackVersionC.ChannelId.PRE_ALARM_CELL_OVER_VOLTAGE_ALARM, new UnsignedWordElement(0x2080)), // m(BatterySoltaroSingleRackVersionC.ChannelId.PRE_ALARM_CELL_OVER_VOLTAGE_RECOVER, @@ -565,7 +565,7 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException { // WARN_LEVEL1 (Level1 warning registers RW) { - ModbusElement[] elements = { + ModbusElement[] elements = { m(BatterySoltaroSingleRackVersionC.ChannelId.LEVEL1_CELL_OVER_VOLTAGE_PROTECTION, new UnsignedWordElement(0x2040)), // m(BatterySoltaroSingleRackVersionC.ChannelId.LEVEL1_CELL_OVER_VOLTAGE_RECOVER, @@ -638,7 +638,7 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException { // WARN_LEVEL2 (Level2 Protection registers RW) { - ModbusElement[] elements = { + ModbusElement[] elements = { m(BatterySoltaroSingleRackVersionC.ChannelId.LEVEL2_CELL_OVER_VOLTAGE_PROTECTION, new UnsignedWordElement(0x2400)), // m(BatterySoltaroSingleRackVersionC.ChannelId.LEVEL2_CELL_OVER_VOLTAGE_RECOVER, @@ -725,7 +725,7 @@ void createCellVoltageAndTemperatureChannels(int numberOfModules) { */ Consumer addCellChannels = type -> { for (var i = 0; i < numberOfModules; i++) { - var elements = new ModbusElement[type.getSensorsPerModule()]; + var elements = new ModbusElement[type.getSensorsPerModule()]; for (var j = 0; j < type.getSensorsPerModule(); j++) { var sensorIndex = i * type.getSensorsPerModule() + j; var channelId = CellChannelFactory.create(type, sensorIndex); diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractOpenemsModbusComponent.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractOpenemsModbusComponent.java index 088eb694c73..f1c41563a13 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractOpenemsModbusComponent.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractOpenemsModbusComponent.java @@ -4,7 +4,6 @@ import java.util.HashMap; import java.util.Map; -import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; @@ -17,7 +16,8 @@ import io.openems.common.types.OpenemsType; import io.openems.edge.bridge.modbus.api.element.AbstractModbusElement; import io.openems.edge.bridge.modbus.api.element.BitsWordElement; -import io.openems.edge.bridge.modbus.api.element.ModbusCoilElement; +import io.openems.edge.bridge.modbus.api.element.CoilElement; +import io.openems.edge.bridge.modbus.api.element.ModbusElement; import io.openems.edge.bridge.modbus.api.element.ModbusRegisterElement; import io.openems.edge.bridge.modbus.api.task.ReadTask; import io.openems.edge.bridge.modbus.api.task.WriteTask; @@ -260,7 +260,7 @@ public void retryModbusCommunication() { * Maps an Element to one or more ModbusChannels using converters, that convert * the value forward and backwards. */ - public class ChannelMapper> { + public class ChannelMapper { private final ELEMENT element; private final Map, ElementToChannelConverter> channelMaps = new HashMap<>(); @@ -278,7 +278,7 @@ public ChannelMapper(ELEMENT element) { */ public ChannelMapper m(io.openems.edge.common.channel.ChannelId channelId, ElementToChannelConverter converter) { - return this.m(channelId, converter, new ChannelMetaInfo(this.element.getStartAddress())); + return this.m(channelId, converter, new ChannelMetaInfo(this.element.startAddress)); } /** @@ -324,7 +324,8 @@ public ELEMENT build() { /* * Forward Element Read-Value to Channel */ - this.element.onUpdateCallback(value -> { // + // This is guaranteed to work because of sealed abstract classes + ((AbstractModbusElement) this.element).onUpdateCallback(value -> { // /* * Applies the updated value on every Channel in ChannelMaps using the given * Converter. If the converter returns an Optional.empty, the value is ignored. @@ -349,13 +350,13 @@ public ELEMENT build() { // dynamically get the Converter; this allows the converter to be changed var converter = this.channelMaps.get(channel); var convertedValue = converter.channelToElement(value); - if (this.element instanceof ModbusRegisterElement registerElement) { + if (this.element instanceof ModbusRegisterElement registerElement) { try { - registerElement.setNextWriteValue(Optional.ofNullable(convertedValue)); - } catch (OpenemsException | IllegalArgumentException e) { + registerElement.setNextWriteValueFromObject(convertedValue); + } catch (IllegalArgumentException e) { AbstractOpenemsModbusComponent.this.logWarn(AbstractOpenemsModbusComponent.this.log, "Unable to write to ModbusRegisterElement. " // - + "Address [" + this.element.getStartAddress() + "] " // + + "Address [" + this.element.startAddress + "] " // + "Channel [" + channel.address() + "]. " // + "Exception [" + e.getClass().getSimpleName() + "] " // + ": " + e.getMessage()); @@ -364,19 +365,20 @@ public ELEMENT build() { e.printStackTrace(); } } - } else if (this.element instanceof ModbusCoilElement coilElement) { + + } else if (this.element instanceof CoilElement coilElement) { try { - coilElement.setNextWriteValue( - Optional.ofNullable(TypeUtils.getAsType(OpenemsType.BOOLEAN, convertedValue))); - } catch (OpenemsException e) { + coilElement.setNextWriteValue(TypeUtils.getAsType(OpenemsType.BOOLEAN, convertedValue)); + } catch (IllegalArgumentException e) { AbstractOpenemsModbusComponent.this.logWarn(AbstractOpenemsModbusComponent.this.log, "Unable to write to ModbusCoilElement " // - + "[" + this.element.getStartAddress() + "]: " + e.getMessage()); + + "[" + this.element.startAddress + "]: " + e.getMessage()); } + } else { AbstractOpenemsModbusComponent.this.logWarn(AbstractOpenemsModbusComponent.this.log, "Unable to write to Element " // - + "[" + this.element.getStartAddress() + "]: it is not a ModbusElement"); + + "[" + this.element.startAddress + "]: it is not a ModbusElement"); } }); } @@ -390,11 +392,11 @@ public ELEMENT build() { * Creates a ChannelMapper that can be used with builder pattern inside the * protocol definition. * - * @param the type of the {@link AbstractModbusElement}d + * @param the type of the {@link ModbusElement} * @param element the ModbusElement * @return a {@link ChannelMapper} */ - protected final > ChannelMapper m(T element) { + protected final ChannelMapper m(T element) { return new ChannelMapper<>(element); } @@ -411,28 +413,27 @@ protected final BitsWordElement m(BitsWordElement bitsWordElement) { /** * Maps the given element 1-to-1 to the Channel identified by channelId. * - * @param the type of the {@link AbstractModbusElement}d + * @param the type of the {@link ModbusElement} * @param channelId the Channel-ID * @param element the ModbusElement * @return the element parameter */ - protected final > T m(io.openems.edge.common.channel.ChannelId channelId, - T element) { + protected final T m(io.openems.edge.common.channel.ChannelId channelId, T element) { return this.m(channelId, element, DIRECT_1_TO_1); } /** * Maps the given element 1-to-1 to the Channel identified by channelId. * - * @param the type of the {@link AbstractModbusElement}d + * @param the type of the {@link ModbusElement} * @param channelId the Channel-ID * @param element the ModbusElement * @param channelMetaInfo an object that holds meta information about the * Channel * @return the element parameter */ - protected final > T m(io.openems.edge.common.channel.ChannelId channelId, - T element, ChannelMetaInfo channelMetaInfo) { + protected final T m(io.openems.edge.common.channel.ChannelId channelId, T element, + ChannelMetaInfo channelMetaInfo) { return this.m(channelId, element, DIRECT_1_TO_1, channelMetaInfo); } @@ -440,14 +441,14 @@ protected final BitsWordElement m(BitsWordElement bitsWordElement) { * Maps the given element to the Channel identified by channelId, applying the * given @link{ElementToChannelConverter}. * - * @param the type of the {@link AbstractModbusElement}d + * @param the type of the {@link ModbusElement} * @param channelId the Channel-ID * @param element the ModbusElement * @param converter the ElementToChannelConverter * @return the element parameter */ - protected final > T m(io.openems.edge.common.channel.ChannelId channelId, - T element, ElementToChannelConverter converter) { + protected final T m(io.openems.edge.common.channel.ChannelId channelId, T element, + ElementToChannelConverter converter) { return new ChannelMapper<>(element) // .m(channelId, converter) // .build(); @@ -457,7 +458,7 @@ protected final BitsWordElement m(BitsWordElement bitsWordElement) { * Maps the given element to the Channel identified by channelId, applying the * given @link{ElementToChannelConverter}. * - * @param the type of the {@link AbstractModbusElement}d + * @param the type of the {@link ModbusElement} * @param channelId the Channel-ID * @param element the ModbusElement * @param converter the ElementToChannelConverter @@ -465,8 +466,8 @@ protected final BitsWordElement m(BitsWordElement bitsWordElement) { * Channel * @return the element parameter */ - protected final > T m(io.openems.edge.common.channel.ChannelId channelId, - T element, ElementToChannelConverter converter, ChannelMetaInfo channelMetaInfo) { + protected final T m(io.openems.edge.common.channel.ChannelId channelId, T element, + ElementToChannelConverter converter, ChannelMetaInfo channelMetaInfo) { return new ChannelMapper<>(element) // .m(channelId, converter, channelMetaInfo) // .build(); diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusUtils.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusUtils.java index ac0672a6204..e2ebb682479 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusUtils.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusUtils.java @@ -15,8 +15,7 @@ import com.ghgande.j2mod.modbus.procimg.Register; import io.openems.common.exceptions.OpenemsException; -import io.openems.edge.bridge.modbus.api.element.AbstractModbusElement; -import io.openems.edge.bridge.modbus.api.element.AbstractModbusRegisterElement; +import io.openems.edge.bridge.modbus.api.element.ModbusRegisterElement; import io.openems.edge.bridge.modbus.api.task.FC3ReadRegistersTask; import io.openems.edge.bridge.modbus.api.task.Task; import io.openems.edge.common.taskmanager.Priority; @@ -29,19 +28,19 @@ public class ModbusUtils { * @param the Type of the element * @param modbusProtocol the {@link ModbusProtocol}, that is linked with a * {@link BridgeModbus} - * @param element the {@link AbstractModbusElement} + * @param element the {@link ModbusRegisterElement} * @param tryAgainOnError if true, tries to read till it receives a value; if * false, stops after first try and possibly return null * @return a future value, e.g. a Integer or null (if tryAgainOnError is false) * @throws OpenemsException on error with the {@link ModbusProtocol} object */ public static CompletableFuture readELementOnce(ModbusProtocol modbusProtocol, - AbstractModbusRegisterElement element, boolean tryAgainOnError) throws OpenemsException { + ModbusRegisterElement element, boolean tryAgainOnError) throws OpenemsException { // Prepare result final var result = new CompletableFuture(); // Activate task - final Task task = new FC3ReadRegistersTask(element.getStartAddress(), Priority.HIGH, element); + final Task task = new FC3ReadRegistersTask(element.startAddress, Priority.HIGH, element); modbusProtocol.addTask(task); // Register listener for element @@ -66,7 +65,7 @@ public static CompletableFuture readELementOnce(ModbusProtocol modbusProt * @param the Type of the elements * @param modbusProtocol the {@link ModbusProtocol}, that is linked with a * {@link BridgeModbus} - * @param elements the {@link AbstractModbusElement}s + * @param elements the {@link ModbusRegisterElement}s * @param tryAgainOnError if true, tries to read till it receives a value on * first register; if false, stops after first try and * possibly return null @@ -76,7 +75,7 @@ public static CompletableFuture readELementOnce(ModbusProtocol modbusProt * @throws OpenemsException on error with the {@link ModbusProtocol} object */ public static CompletableFuture> readELementsOnce(ModbusProtocol modbusProtocol, - AbstractModbusRegisterElement[] elements, boolean tryAgainOnError) throws OpenemsException { + ModbusRegisterElement[] elements, boolean tryAgainOnError) throws OpenemsException { if (elements.length == 0) { return CompletableFuture.completedFuture(Collections.emptyList()); } @@ -85,7 +84,7 @@ public static CompletableFuture> readELementsOnce(ModbusProtocol mod final var result = new CompletableFuture>(); // Activate task - final Task task = new FC3ReadRegistersTask(elements[0].getStartAddress(), Priority.HIGH, elements); + final Task task = new FC3ReadRegistersTask(elements[0].startAddress, Priority.HIGH, elements); modbusProtocol.addTask(task); // Register listener for each element diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractDoubleWordElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractDoubleWordElement.java index 48f4c1e5da3..57e2313e555 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractDoubleWordElement.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractDoubleWordElement.java @@ -1,106 +1,20 @@ package io.openems.edge.bridge.modbus.api.element; -import java.nio.ByteBuffer; -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.common.exceptions.OpenemsException; import io.openems.common.types.OpenemsType; /** * A DoubleWordElement has a size of two Modbus Registers or 32 bit. * * @param the subclass of myself - * @param the target type + * @param the OpenEMS type */ -public abstract class AbstractDoubleWordElement extends AbstractModbusRegisterElement { - - private final Logger log = LoggerFactory.getLogger(AbstractDoubleWordElement.class); +public abstract class AbstractDoubleWordElement, T> + extends AbstractMultipleWordsElement { public AbstractDoubleWordElement(OpenemsType type, int startAddress) { - super(type, startAddress); - } - - @Override - public final int getLength() { - return 2; - } - - @Override - protected final void _setInputRegisters(InputRegister... registers) { - // fill buffer - var buff = ByteBuffer.allocate(4).order(this.getByteOrder()); - if (this.wordOrder == WordOrder.MSWLSW) { - buff.put(registers[0].toBytes()); - buff.put(registers[1].toBytes()); - } else { - buff.put(registers[1].toBytes()); - buff.put(registers[0].toBytes()); - } - buff.rewind(); - // convert registers to Long - var value = this.fromByteBuffer(buff); - // set value - super.setValue(value); + super(type, startAddress, 2); } - /** - * Converts a 4-byte ByteBuffer to the the current OpenemsType. - * - * @param buff the ByteBuffer - * @return an instance of the given OpenemsType - */ - protected abstract T fromByteBuffer(ByteBuffer buff); - - @Override - public final void _setNextWriteValue(Optional valueOpt) throws OpenemsException { - if (valueOpt.isPresent()) { - if (this.isDebug()) { - this.log.info("Element [" + this + "] set next write value to [" + valueOpt.orElse(null) + "]."); - } - var buff = ByteBuffer.allocate(4).order(this.getByteOrder()); - buff = this.toByteBuffer(buff, valueOpt.get()); - var b = buff.array(); - if (this.wordOrder == WordOrder.MSWLSW) { - this.setNextWriteValueRegisters(Optional.of(new Register[] { // - new SimpleRegister(b[0], b[1]), new SimpleRegister(b[2], b[3]) })); - } else { - this.setNextWriteValueRegisters(Optional.of(new Register[] { // - new SimpleRegister(b[2], b[3]), new SimpleRegister(b[0], b[1]) })); - } - } else { - this.setNextWriteValueRegisters(Optional.empty()); - } - this.onSetNextWriteCallbacks.forEach(callback -> callback.accept(valueOpt)); - } - - /** - * Converts the current OpenemsType to a 4-byte ByteBuffer. - * - * @param buff the target ByteBuffer - * @param value an instance of the given OpenemsType - * @return the ByteBuffer - */ - protected abstract ByteBuffer toByteBuffer(ByteBuffer buff, T value); - - /** - * Sets the Word-Order. Default is "MSWLSW" - "Most Significant Word; Least - * Significant Word". See http://www.simplymodbus.ca/FAQ.htm#Order. - * - * @param wordOrder the new Word-Order - * @return myself - */ - public final SELF wordOrder(WordOrder wordOrder) { - this.wordOrder = wordOrder; - return this.self(); - } - - private WordOrder wordOrder = WordOrder.MSWLSW; - } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractModbusElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractModbusElement.java index 62002c5adea..1e6e41d73e7 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractModbusElement.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractModbusElement.java @@ -11,33 +11,46 @@ import io.openems.common.types.OpenemsType; import io.openems.edge.bridge.modbus.api.AbstractModbusBridge; -import io.openems.edge.bridge.modbus.api.task.Task; +import io.openems.edge.common.type.TypeUtils; /** * A ModbusElement represents one row of a Modbus definition table. * * @param the subclass of myself - * @param the target type + * @param the raw value type + * @param the value type */ -public abstract class AbstractModbusElement implements ModbusElement { +public abstract non-sealed class AbstractModbusElement, RAW, T> + extends ModbusElement { - protected final List>> onSetNextWriteCallbacks = new ArrayList<>(); + /** The type of the read and write value. */ + public final OpenemsType type; private final Logger log = LoggerFactory.getLogger(AbstractModbusElement.class); + private final List> onUpdateCallbacks = new CopyOnWriteArrayList<>(); + private final List>> onSetNextWriteCallbacks = new ArrayList<>(); - private final OpenemsType type; - private final int startAddress; + /** The next Write-Value. */ + protected T nextWriteValue = null; - // Counts for how many cycles no valid value was + /** Counts for how many cycles no valid value was read. */ private int invalidValueCounter = 0; - protected Task task = null; - - public AbstractModbusElement(OpenemsType type, int startAddress) { + protected AbstractModbusElement(OpenemsType type, int startAddress, int length) { + super(startAddress, length); this.type = type; - this.startAddress = startAddress; } + protected abstract RAW valueToRaw(T value); + + /** + * Converts the RAW value from j2mod to the expected type. + * + * @param value the raw value + * @return the typed/converted value + */ + protected abstract T rawToValue(RAW value); + /** * Gets an instance of the correct subclass of myself. * @@ -45,17 +58,79 @@ public AbstractModbusElement(OpenemsType type, int startAddress) { */ protected abstract SELF self(); - @Override - public final void onSetNextWrite(Consumer> callback) { - this.onSetNextWriteCallbacks.add(callback); + /** + * Set the input/read value. + * + * @param raw a value in raw format + */ + public final void setInputValue(RAW raw) { + // Convert to type + final T value; + if (raw != null) { + value = this.rawToValue(raw); + } else { + value = null; + } + // Log debug message + if (this.isDebug) { + this.log.info("Element [" + this + "] set value to [" + value + "]."); + } + // Reset invalidValueCounter + if (value != null) { + this.invalidValueCounter = 0; + } + // Call Callbacks + for (Consumer callback : this.onUpdateCallbacks) { + callback.accept(value); + } } - @Override - public OpenemsType getType() { - return this.type; + /** + * Sets a value that should be written to the Modbus device. + * + * @param value the value; possibly null + */ + public final void setNextWriteValue(T value) { + // Log debug message + if (this.isDebug()) { + this.log.info("Element [" + this + "] set next write value to [" + value + "]."); + } + this.nextWriteValue = value; } - private final List> onUpdateCallbacks = new CopyOnWriteArrayList<>(); + /** + * Sets a value that should be written to the Modbus device. + * + * @param value the value; possibly null + */ + public final void setNextWriteValueFromObject(Object value) { + this.setNextWriteValue(TypeUtils.getAsType(this.type, value)); + } + + /** + * Gets the next write value and resets it. + * + *

    + * This method should be called once in every cycle on the + * TOPIC_CYCLE_EXECUTE_WRITE event. It makes sure, that the nextWriteValue gets + * initialized in every Cycle. If registers need to be written again in every + * cycle, next setNextWriteValue()-method needs to be called on every Cycle. + * + * @return the next write value + */ + public final RAW getNextWriteValueAndReset() { + var value = this.nextWriteValue; + if (value == null) { + return null; + } + var result = this.valueToRaw(value); + this.nextWriteValue = null; + this.onNextWriteValueReset(); + return result; + } + + protected void onNextWriteValueReset() { + } /** * The onUpdateCallback is called on reception of a new value. @@ -67,42 +142,28 @@ public OpenemsType getType() { * @param onUpdateCallback the Callback * @return myself */ - public SELF onUpdateCallback(Consumer onUpdateCallback) { + public final SELF onUpdateCallback(Consumer onUpdateCallback) { this.onUpdateCallbacks.add(onUpdateCallback); return this.self(); } - @Override - public int getStartAddress() { - return this.startAddress; - } - - @Override - public void setModbusTask(Task task) { - this.task = task; - } - - public Task getModbusTask() { - return this.task; - } - - protected void setValue(T value) { - if (this.isDebug) { - this.log.info("Element [" + this + "] set value to [" + value + "]."); - } - if (value != null) { - this.invalidValueCounter = 0; - } - for (Consumer callback : this.onUpdateCallbacks) { - callback.accept(value); - } + /** + * Add an onSetNextWrite callback. It is called when a 'next write value' was + * set. + * + * @param callback the callback + * @return myself + */ + public final SELF onSetNextWrite(Consumer> callback) { + this.onSetNextWriteCallbacks.add(callback); + return this.self(); } @Override - public void invalidate(AbstractModbusBridge bridge) { + public final void invalidate(AbstractModbusBridge bridge) { this.invalidValueCounter++; if (bridge.invalidateElementsAfterReadErrors() <= this.invalidValueCounter) { - this.setValue(null); + this.setInputValue(null); } } @@ -116,9 +177,9 @@ public void invalidate(AbstractModbusBridge bridge) { * * @return myself */ - public AbstractModbusElement debug() { + public SELF debug() { this.isDebug = true; - return this; + return this.self(); } protected boolean isDebug() { @@ -145,5 +206,6 @@ public String toString() { @Override public void deactivate() { this.onUpdateCallbacks.clear(); + this.onSetNextWriteCallbacks.clear(); } } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractModbusRegisterElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractModbusRegisterElement.java deleted file mode 100644 index 4db45a8f334..00000000000 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractModbusRegisterElement.java +++ /dev/null @@ -1,88 +0,0 @@ -package io.openems.edge.bridge.modbus.api.element; - -import java.nio.ByteOrder; -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 io.openems.common.exceptions.OpenemsException; -import io.openems.common.types.OpenemsType; - -/** - * A ModbusRegisterElement represents one or more Modbus Registers. - * - * @param the subclass of myself - * @param the target type - */ -public abstract class AbstractModbusRegisterElement extends AbstractModbusElement - implements ModbusRegisterElement { - - private final Logger log = LoggerFactory.getLogger(AbstractModbusRegisterElement.class); - - private Optional nextWriteValue = Optional.empty(); - - public AbstractModbusRegisterElement(OpenemsType type, int startAddress) { - super(type, startAddress); - } - - protected void setNextWriteValueRegisters(Optional writeValueOpt) throws OpenemsException { - if (writeValueOpt.isPresent() && writeValueOpt.get().length != this.getLength()) { - throw new OpenemsException("Modbus Element [" + this + "]: write registers length [" - + writeValueOpt.get().length + "] does not match required size of [" + this.getLength() + "]"); - } - this.nextWriteValue = writeValueOpt; - } - - @Override - public Optional getNextWriteValue() { - return this.nextWriteValue; - } - - /* - * ByteOrder of the input registers - */ - private ByteOrder byteOrder = ByteOrder.BIG_ENDIAN; - - /** - * Sets the Byte-Order. Default is "BIG_ENDIAN". See - * http://www.simplymodbus.ca/FAQ.htm#Order. - * - * @param byteOrder the ByteOrder - * @return myself - */ - public final SELF byteOrder(ByteOrder byteOrder) { - this.byteOrder = byteOrder; - return this.self(); - } - - public ByteOrder getByteOrder() { - return this.byteOrder; - } - - @Override - public void setInputRegisters(InputRegister... registers) throws OpenemsException { - if (this.isDebug()) { - var b = new StringBuilder("Element [" + this + "] set input registers to ["); - for (var i = 0; i < registers.length; i++) { - b.append(registers[i].getValue()); - if (i < registers.length - 1) { - b.append(","); - } - } - b.append("]."); - this.log.info(b.toString()); - } - if (registers.length != this.getLength()) { - throw new OpenemsException("Modbus Element [" + this + "]: registers length [" + registers.length - + "] does not match required size of [" + this.getLength() + "]"); - } - this._setInputRegisters(registers); - } - - protected abstract void _setInputRegisters(InputRegister... registers); - -} diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractMultipleWordsElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractMultipleWordsElement.java new file mode 100644 index 00000000000..10ad4c4b57c --- /dev/null +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractMultipleWordsElement.java @@ -0,0 +1,48 @@ +package io.openems.edge.bridge.modbus.api.element; + +import com.ghgande.j2mod.modbus.procimg.Register; + +import io.openems.common.types.OpenemsType; + +/** + * A WordElement has a size of one Modbus Registers or 16 bit. + * + * @param the subclass of myself + * @param the OpenEMS type + */ +public abstract class AbstractMultipleWordsElement, T> + extends ModbusRegisterElement { + + private WordOrder wordOrder = WordOrder.MSWLSW; + + protected AbstractMultipleWordsElement(OpenemsType type, int startAddress, int length) { + super(type, startAddress, length); + } + + @Override + protected final T registersToValue(Register[] registers) { + return this.commonRegistersToValue(registers, this.wordOrder); + } + + @Override + protected Register[] valueToRaw(T value) { + return this.valueToRaw(value, this.wordOrder); + } + + /** + * Sets the Word-Order. Default is "MWSLSW" - "Most Significant Word; Least + * Significant Word". See http://www.simplymodbus.ca/FAQ.htm#Order. + * + * @param wordOrder the WordOrder + * @return myself + */ + public final SELF wordOrder(WordOrder wordOrder) { + this.wordOrder = wordOrder; + return this.self(); + } + + protected WordOrder getWordOrder() { + return this.wordOrder; + } + +} diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractQuadrupleWordElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractQuadrupleWordElement.java index 442e42197ad..a0257b190e4 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractQuadrupleWordElement.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractQuadrupleWordElement.java @@ -1,117 +1,20 @@ package io.openems.edge.bridge.modbus.api.element; -import java.nio.ByteBuffer; -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.common.exceptions.OpenemsException; import io.openems.common.types.OpenemsType; /** * A QuadrupleWordElement has a size of four Modbus Registers or 64 bit. * * @param the subclass of myself - * @param the target type + * @param the OpenEMS type */ -public abstract class AbstractQuadrupleWordElement extends AbstractModbusRegisterElement { - - private final Logger log = LoggerFactory.getLogger(AbstractDoubleWordElement.class); +public abstract class AbstractQuadrupleWordElement, T> + extends AbstractMultipleWordsElement { public AbstractQuadrupleWordElement(OpenemsType type, int startAddress) { - super(type, startAddress); - } - - @Override - public final int getLength() { - return 4; - } - - @Override - protected final void _setInputRegisters(InputRegister... registers) { - // fill buffer - var buff = ByteBuffer.allocate(8).order(this.getByteOrder()); - if (this.wordOrder == WordOrder.MSWLSW) { - buff.put(registers[0].toBytes()); - buff.put(registers[1].toBytes()); - buff.put(registers[2].toBytes()); - buff.put(registers[3].toBytes()); - } else { - buff.put(registers[3].toBytes()); - buff.put(registers[2].toBytes()); - buff.put(registers[1].toBytes()); - buff.put(registers[0].toBytes()); - } - buff.rewind(); - // convert registers to Long - var value = this.fromByteBuffer(buff); - // set value - super.setValue(value); - } - - /** - * Converts a 8-byte ByteBuffer to the current OpenemsType. - * - * @param buff the ByteBuffer - * @return an instance of the current OpenemsType - */ - protected abstract T fromByteBuffer(ByteBuffer buff); - - @Override - public final void _setNextWriteValue(Optional valueOpt) throws OpenemsException { - if (this.isDebug()) { - this.log.info("Element [" + this + "] set next write value to [" + valueOpt.orElse(null) + "]."); - } - if (valueOpt.isPresent()) { - var buff = ByteBuffer.allocate(8).order(this.getByteOrder()); - buff = this.toByteBuffer(buff, valueOpt.get()); - var b = buff.array(); - if (this.wordOrder == WordOrder.MSWLSW) { - this.setNextWriteValueRegisters(Optional.of(new Register[] { // - new SimpleRegister(b[0], b[1]), // - new SimpleRegister(b[2], b[3]), // - new SimpleRegister(b[4], b[5]), // - new SimpleRegister(b[6], b[7]) // - })); - } else { - this.setNextWriteValueRegisters(Optional.of(new Register[] { // - new SimpleRegister(b[6], b[7]), // - new SimpleRegister(b[4], b[5]), // - new SimpleRegister(b[2], b[3]), // - new SimpleRegister(b[0], b[1]) // - })); - } - } else { - this.setNextWriteValueRegisters(Optional.empty()); - } - this.onSetNextWriteCallbacks.forEach(callback -> callback.accept(valueOpt)); - } - - /** - * Converts the current OpenemsType to a 8-byte ByteBuffer. - * - * @param buff the target ByteBuffer - * @param value the value - * @return the ByteBuffer - */ - protected abstract ByteBuffer toByteBuffer(ByteBuffer buff, T value); - - /** - * Sets the Word-Order. Default is "MWSLSW" - "Most Significant Word; Least - * Significant Word". See http://www.simplymodbus.ca/FAQ.htm#Order. - * - * @param wordOrder the WordOrder - * @return myself - */ - public final SELF wordOrder(WordOrder wordOrder) { - this.wordOrder = wordOrder; - return this.self(); + super(type, startAddress, 4); } - private WordOrder wordOrder = WordOrder.MSWLSW; } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractSingleWordElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractSingleWordElement.java new file mode 100644 index 00000000000..7703e812d78 --- /dev/null +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractSingleWordElement.java @@ -0,0 +1,31 @@ +package io.openems.edge.bridge.modbus.api.element; + +import com.ghgande.j2mod.modbus.procimg.Register; + +import io.openems.common.types.OpenemsType; + +/** + * A WordElement has a size of one Modbus Registers or 16 bit. + * + * @param the subclass of myself + * @param the OpenEMS type + */ +public abstract class AbstractSingleWordElement, T> + extends ModbusRegisterElement { + + public AbstractSingleWordElement(OpenemsType type, int startAddress) { + super(type, startAddress, 1); + } + + @Override + protected T registersToValue(Register[] registers) { + // length of registers array is guaranteed to be 1 here. + return this.commonRegistersToValue(registers, WordOrder.MSWLSW /* makes no difference for SingleWord */); + } + + @Override + protected Register[] valueToRaw(T value) { + return this.valueToRaw(value, WordOrder.MSWLSW /* makes no difference for SingleWord */); + } + +} \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractWordElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractWordElement.java deleted file mode 100644 index 44d074438ef..00000000000 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/AbstractWordElement.java +++ /dev/null @@ -1,79 +0,0 @@ -package io.openems.edge.bridge.modbus.api.element; - -import java.nio.ByteBuffer; -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.common.exceptions.OpenemsException; -import io.openems.common.types.OpenemsType; - -/** - * A WordElement has a size of one Modbus Registers or 16 bit. - * - * @param the subclass of myself - * @param the target type - */ -public abstract class AbstractWordElement extends AbstractModbusRegisterElement { - - private final Logger log = LoggerFactory.getLogger(AbstractWordElement.class); - - public AbstractWordElement(OpenemsType type, int startAddress) { - super(type, startAddress); - } - - @Override - public final int getLength() { - return 1; - } - - @Override - protected void _setInputRegisters(InputRegister... registers) { - // convert registers - var buff = ByteBuffer.allocate(2).order(this.getByteOrder()); - buff.put(registers[0].toBytes()); - var value = this.fromByteBuffer(buff); - // set value - super.setValue(value); - } - - /** - * Converts a 2-byte ByteBuffer to the the current OpenemsType. - * - * @param buff the ByteBuffer - * @return an instance of the current OpenemsType - */ - protected abstract T fromByteBuffer(ByteBuffer buff); - - @Override - public void _setNextWriteValue(Optional valueOpt) throws OpenemsException { - if (this.isDebug()) { - this.log.info("Element [" + this + "] set next write value to [" + valueOpt.orElse(null) + "]."); - } - if (valueOpt.isPresent()) { - var buff = ByteBuffer.allocate(2).order(this.getByteOrder()); - buff = this.toByteBuffer(buff, valueOpt.get()); - var b = buff.array(); - this.setNextWriteValueRegisters(Optional.of(new Register[] { // - new SimpleRegister(b[0], b[1]) })); - } else { - this.setNextWriteValueRegisters(Optional.empty()); - } - this.onSetNextWriteCallbacks.forEach(callback -> callback.accept(valueOpt)); - } - - /** - * Converts the current OpenemsType to a 2-byte ByteBuffer. - * - * @param buff the target ByteBuffer - * @param value the value - * @return the ByteBuffer - */ - protected abstract ByteBuffer toByteBuffer(ByteBuffer buff, Object value); - -} diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/BitsWordElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/BitsWordElement.java index b40e1c957e3..3f273b7964f 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/BitsWordElement.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/BitsWordElement.java @@ -1,20 +1,15 @@ package io.openems.edge.bridge.modbus.api.element; import java.nio.ByteBuffer; -import java.nio.ByteOrder; import java.util.ArrayList; import java.util.List; -import java.util.Optional; +import java.util.Objects; import java.util.stream.Collectors; +import java.util.stream.Stream; -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.common.exceptions.OpenemsException; import io.openems.common.types.ChannelAddress; import io.openems.common.types.OpenemsType; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; @@ -28,20 +23,37 @@ * A BitsWordElement is an {@link UnsignedWordElement} where every bit * represents a Boolean value. */ -public class BitsWordElement extends UnsignedWordElement { - - private final Logger log = LoggerFactory.getLogger(BitsWordElement.class); +public class BitsWordElement extends AbstractSingleWordElement { private final AbstractOpenemsModbusComponent component; - /* - * Holds the ChannelWrapper. 'null' if not explicitly defined! - */ + private static record ChannelWrapper(Channel channel, BitConverter converter) { + } + + /** Holds the ChannelWrapper; 'null' if not explicitly defined. */ private final ChannelWrapper[] channels = new ChannelWrapper[16]; public BitsWordElement(int address, AbstractOpenemsModbusComponent component) { - super(address); + super(OpenemsType.INTEGER, address); this.component = component; + + // On Value Update: set the individual BooleanChannel-Values + this.onUpdateCallback(value -> { + if (value == null) { + value = new Boolean[16]; + } + + for (var bitIndex = 0; bitIndex < 16; bitIndex++) { + // Get Wrapper + var wrapper = this.channels[bitIndex]; + if (wrapper == null) { + continue; + } + + // Set Value to Channel + wrapper.channel().setNextValue(value[bitIndex]); + } + }); } @Override @@ -58,7 +70,7 @@ protected BitsWordElement self() { * @return myself for builder pattern */ public BitsWordElement bit(int bitIndex, ChannelId channelId, BitConverter converter) { - return this.bit(bitIndex, channelId, converter, new ChannelMetaInfoBit(this.getStartAddress(), bitIndex)); + return this.bit(bitIndex, channelId, converter, new ChannelMetaInfoBit(this.startAddress, bitIndex)); } /** @@ -99,16 +111,18 @@ public BitsWordElement bit(int bitIndex, ChannelId channelId, BitConverter conve var channelWrapper = new ChannelWrapper(booleanChannel, converter); - // Set Channel-Source + // Add Modbus Address and Bit-Index to Channel Source channel.setMetaInfo(channelMetaInfoBit); - // Add Modbus Address and Bit-Index to Channel Source + // Handle Writes to Bit-Channels if (channel instanceof WriteChannel) { - // Handle Writes to Bit-Channels var booleanWriteChannel = (WriteChannel) booleanChannel; booleanWriteChannel.onSetNextWrite(value -> { // Listen on Writes to the BooleanChannel and store the value - channelWrapper.setWriteValue(value); + if (this.nextWriteValue == null) { + this.nextWriteValue = new Boolean[16]; + } + this.nextWriteValue[bitIndex] = value; }); } @@ -127,74 +141,15 @@ public BitsWordElement bit(int bitIndex, ChannelId channelId) { return this.bit(bitIndex, channelId, BitConverter.DIRECT_1_TO_1); } - /** - * Sets the individual BooleanChannel-Values from an InputRegister. - * - * @param registers the InputRegisters - */ @Override - protected void _setInputRegisters(InputRegister... registers) { - if (registers.length != 1) { - throw new IllegalArgumentException("Expected only one Register instead of [" + registers.length - + "] for Component [" + this.component.id() + "] on address [" + this.getStartAddress() + "]"); - } - - // convert Register to int - var buff = ByteBuffer.allocate(2).order(this.getByteOrder()); - buff.put(registers[0].toBytes()); - var value = Short.toUnsignedInt(buff.getShort(0)); - - for (var bitIndex = 0; bitIndex < 16; bitIndex++) { - // Get Wrapper - var wrapper = this.channels[bitIndex]; - if (wrapper == null) { - continue; - } - - // Get Value - var setValue = value << ~bitIndex < 0; - - // Apply Bit-Conversion - switch (wrapper.converter) { - case DIRECT_1_TO_1: - break; - case INVERT: - setValue = !setValue; - break; - } - - // Set Value to Channel - wrapper.channel.setNextValue(setValue); + protected Register[] valueToRaw(Boolean[] values) { + // Check if any Boolean is set; if none: return null immediately + if (Stream.of(values) // + .allMatch(Objects::isNull)) { + return null; } - } - /** - * Gets the next write value from all Bits and resets them. - * - *

    - * This method should be called once in every cycle on the - * TOPIC_CYCLE_EXECUTE_WRITE event. It makes sure, that the nextWriteValue gets - * initialized in every Cycle. If registers need to be written again in every - * cycle, next setNextWriteValue()-method needs to called on every Cycle. - * - * @return the next value as an Optional array of Registers - */ - @Override - public Optional getNextWriteValueAndReset() { - // Check if any BooleanWriteChannel has a Write-Value; if none: return - // Optional.empty. - var isAnyBooleanWriteChannelSet = false; - for (ChannelWrapper wrapper : this.channels) { - if (wrapper != null && wrapper.getWriteValue().isPresent()) { - isAnyBooleanWriteChannelSet = true; - break; - } - } - if (!isAnyBooleanWriteChannelSet) { - return Optional.empty(); - } - - // Get value of each BooleanChannel + // Get value of each Channel var b0 = (byte) 0; var b1 = (byte) 0; List channelsWithMissingWriteValue = new ArrayList<>(); @@ -203,15 +158,22 @@ public Optional getNextWriteValueAndReset() { if (wrapper == null) { continue; } - var valueOpt = wrapper.getWriteValue(); - if (!valueOpt.isPresent()) { + var value = values[bitIndex]; + + if (value == null) { + // Value is missing channelsWithMissingWriteValue.add(wrapper.channel.address()); continue; } - // Write-Value exists - boolean value = valueOpt.get(); - if ((value ? wrapper.converter == BitConverter.DIRECT_1_TO_1 // - : wrapper.converter == BitConverter.INVERT)) { + + // Value exists + final boolean setValue; + setValue = switch (wrapper.converter) { + case DIRECT_1_TO_1 -> value; + case INVERT -> !value; + }; + + if (setValue) { // Value is true -> set the bit of the byte if (bitIndex < 8) { b0 |= 1 << bitIndex; @@ -221,79 +183,73 @@ public Optional getNextWriteValueAndReset() { } } - // If at least one BooleanWriteChannel had no Write-Value: Error + return - // Optional.empty. + // If at least one value was missing: Error + return null if (!channelsWithMissingWriteValue.isEmpty()) { new IllegalArgumentException( "The following BooleanWriteChannels have no Write-Value: " + channelsWithMissingWriteValue.stream() // .map(ChannelAddress::toString) // .collect(Collectors.joining(","))) .printStackTrace(); - return Optional.empty(); + return null; } - // Clear all Write-Values - for (ChannelWrapper wrapper : this.channels) { - if (wrapper != null) { - wrapper.setWriteValue(null); + // create Register - always BIG_ENDIAN + return new Register[] { new SimpleRegister(b1, b0) }; + } + + /** + * Length of registers array is guaranteed to be 1 here. + */ + @Override + protected Boolean[] registersToValue(Register[] registers) { + // convert Register to int + var buff = ByteBuffer.allocate(2); + buff.put(registers[0].toBytes()); + var value = Short.toUnsignedInt(buff.getShort(0)); + + var result = new Boolean[16]; + for (var bitIndex = 0; bitIndex < 16; bitIndex++) { + // Get Wrapper + var wrapper = this.channels[bitIndex]; + if (wrapper == null) { + continue; } - } - // create Register - Register result; - if (this.getByteOrder() == ByteOrder.BIG_ENDIAN) { - result = new SimpleRegister(b1, b0); - } else { - result = new SimpleRegister(b0, b1); - } + // Get Value + var bit = value << ~bitIndex < 0; - // Log Debug - if (this.isDebug()) { - this.log.info("BitsWordElement [" + this + "]: " // - + "next write value is [" - + String.format("%16s", Integer.toBinaryString(result.getValue())).replace(' ', '0') // - + "/0x" + String.format("%4s", Integer.toHexString(result.getValue())).replace(' ', '0') + "]."); + // Apply Bit-Conversion + result[bitIndex] = switch (wrapper.converter) { + case DIRECT_1_TO_1 -> bit; + case INVERT -> !bit; + }; } - return Optional.of(new Register[] { result }); - + return result; } @Override - protected Integer fromByteBuffer(ByteBuffer buff) { - throw new IllegalArgumentException("BitsWordElement.fromByteBuffer() should never be called"); + protected void onNextWriteValueReset() { + // Clear all Write-Values + Stream.of(this.channels) // + .filter(Objects::nonNull) // + .map(ChannelWrapper::channel) // + .filter(WriteChannel.class::isInstance) // + .map(WriteChannel.class::cast) // + .forEach(WriteChannel::getNextWriteValueAndReset); } @Override - public Optional getNextWriteValue() { - throw new IllegalArgumentException("BitsWordElement.getNextWriteValue() should never be called"); + protected Boolean[] byteBufferToValue(ByteBuffer buff) { + // byteBufferToValue() is called by ModbusRegisterElement.rawToValue(). + // It does not apply here. + throw new IllegalArgumentException("BitsWordElement.byteBufferToValue() should never be called"); } @Override - public void _setNextWriteValue(Optional valueOpt) throws OpenemsException { - throw new IllegalArgumentException("BitsWordElement._setNextWriteValue() should never be called"); - } - - protected ByteBuffer toByteBuffer(ByteBuffer buff, Integer value) { - throw new IllegalArgumentException("BitsWordElement.toByteBuffer() should never be called"); - } - - private static class ChannelWrapper { - private final Channel channel; - private final BitConverter converter; - private Optional writeValue = Optional.empty(); - - protected ChannelWrapper(Channel channel, BitConverter converter) { - this.channel = channel; - this.converter = converter; - } - - protected void setWriteValue(Boolean value) { - this.writeValue = Optional.ofNullable(value); - } - - protected Optional getWriteValue() { - return this.writeValue; - } + protected void valueToByteBuffer(ByteBuffer buff, Boolean[] value) { + // byteBufferToValue() is called by ModbusRegisterElement.valueToRaw(). + // It does not apply here. + throw new IllegalArgumentException("BitsWordElement.valueToByteBuffer() should never be called"); } } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/CoilElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/CoilElement.java index 6d7814760d9..84315b097bc 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/CoilElement.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/CoilElement.java @@ -1,60 +1,29 @@ package io.openems.edge.bridge.modbus.api.element; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.function.Consumer; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.openems.common.exceptions.OpenemsException; import io.openems.common.types.OpenemsType; /** * A CoilElement has a size of one Modbus Coil or 1 bit. */ -public class CoilElement extends AbstractModbusElement implements ModbusCoilElement { - - private final Logger log = LoggerFactory.getLogger(CoilElement.class); - private final List>> onSetNextWriteCallbacks = new ArrayList<>(); - - private Optional nextWriteValue = Optional.empty(); +public class CoilElement extends AbstractModbusElement { public CoilElement(int startAddress) { - super(OpenemsType.BOOLEAN, startAddress); - } - - @Override - public Optional getNextWriteValue() { - return this.nextWriteValue; + super(OpenemsType.BOOLEAN, startAddress, 1); } @Override - public int getLength() { - return 1; + protected CoilElement self() { + return this; } @Override - public void _setNextWriteValue(Optional valueOpt) throws OpenemsException { - if (this.isDebug()) { - this.log.info("Element [" + this + "] set next write value to [" + valueOpt.orElse(null) + "]."); - } - this.nextWriteValue = valueOpt; - this.onSetNextWriteCallbacks.forEach(callback -> callback.accept(valueOpt)); + protected Boolean valueToRaw(Boolean value) { + return value; } @Override - public void setInputCoil(Boolean coil) throws OpenemsException { - if (this.isDebug()) { - this.log.info("Element [" + this + "] set input coil to [" + coil + "]"); - } - // set value - super.setValue(coil); + protected Boolean rawToValue(Boolean value) { + return value; } - @Override - protected CoilElement self() { - return this; - } } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/DummyCoilElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/DummyCoilElement.java deleted file mode 100644 index 463cc49694a..00000000000 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/DummyCoilElement.java +++ /dev/null @@ -1,44 +0,0 @@ -package io.openems.edge.bridge.modbus.api.element; - -import java.util.Optional; - -import io.openems.common.types.OpenemsType; - -/** - * A DummyCoilElement is a placeholder for an empty {@link ModbusCoilElement}. - */ -public class DummyCoilElement extends AbstractModbusElement - implements ModbusCoilElement, DummyElement { - - public DummyCoilElement(int startAddress) { - super(OpenemsType.BOOLEAN, startAddress); - } - - @Override - public int getLength() { - return 1; - } - - @Override - public void _setNextWriteValue(Optional valueOpt) { - // ignore write - this.onSetNextWriteCallbacks.forEach(callback -> callback.accept(valueOpt)); - } - - /** - * We are not setting a value for a DummyElement. - */ - @Override - public void setInputCoil(Boolean coil) { - } - - @Override - public Optional getNextWriteValue() { - return Optional.empty(); - } - - @Override - protected DummyCoilElement self() { - return this; - } -} diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/DummyElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/DummyElement.java deleted file mode 100644 index 6e8e53fea43..00000000000 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/DummyElement.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.openems.edge.bridge.modbus.api.element; - -/** - * A placeholder for an empty Modbus Element. - */ -public interface DummyElement { - -} diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/DummyRegisterElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/DummyRegisterElement.java index 99bbdcf83ad..b34cec4b76b 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/DummyRegisterElement.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/DummyRegisterElement.java @@ -1,8 +1,7 @@ package io.openems.edge.bridge.modbus.api.element; -import java.util.Optional; +import java.nio.ByteBuffer; -import com.ghgande.j2mod.modbus.procimg.InputRegister; import com.ghgande.j2mod.modbus.procimg.Register; import io.openems.common.types.OpenemsType; @@ -11,51 +10,34 @@ * A DummyRegisterElement is a placeholder for an empty * {@link ModbusRegisterElement}. */ -public class DummyRegisterElement extends AbstractModbusRegisterElement - implements ModbusRegisterElement, DummyElement { - - private final int length; +public class DummyRegisterElement extends AbstractMultipleWordsElement { public DummyRegisterElement(int address) { this(address, address); } public DummyRegisterElement(int fromAddress, int toAddress) { - super(OpenemsType.INTEGER /* does not matter */, fromAddress); - this.length = toAddress - fromAddress + 1; - } - - @Override - public int getLength() { - return this.length; + super(OpenemsType.INTEGER /* does not matter */, fromAddress, toAddress - fromAddress + 1); } - /** - * We are not setting a value for a DummyElement. - */ @Override - public void setInputRegisters(InputRegister... registers) { - } - - @Override - protected void _setInputRegisters(InputRegister... registers) { + protected DummyRegisterElement self() { + return this; } @Override - @Deprecated - public void _setNextWriteValue(Optional valueOpt) { - // ignore write - this.onSetNextWriteCallbacks.forEach(callback -> callback.accept(valueOpt)); + protected Register[] valueToRaw(Void value) { + return new Register[length]; } @Override - public Optional getNextWriteValue() { - return Optional.empty(); + protected Void byteBufferToValue(ByteBuffer buff) { + return null; } @Override - protected DummyRegisterElement self() { - return this; + protected void valueToByteBuffer(ByteBuffer buff, Void value) { + // Nothing } } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/FloatDoublewordElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/FloatDoublewordElement.java index 6b20b9a6f25..043f92a6fae 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/FloatDoublewordElement.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/FloatDoublewordElement.java @@ -20,12 +20,12 @@ protected FloatDoublewordElement self() { } @Override - protected Float fromByteBuffer(ByteBuffer buff) { - return buff.order(this.getByteOrder()).getFloat(0); + protected Float byteBufferToValue(ByteBuffer buff) { + return buff.getFloat(0); } @Override - protected ByteBuffer toByteBuffer(ByteBuffer buff, Float value) { - return buff.putFloat(value.floatValue()); + protected void valueToByteBuffer(ByteBuffer buff, Float value) { + buff.putFloat(value.floatValue()); } } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/FloatQuadruplewordElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/FloatQuadruplewordElement.java index aa7ed1122cb..cc33bf816e0 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/FloatQuadruplewordElement.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/FloatQuadruplewordElement.java @@ -20,13 +20,12 @@ protected FloatQuadruplewordElement self() { } @Override - protected Double fromByteBuffer(ByteBuffer buff) { - return Double.valueOf(buff.getDouble()); + protected Double byteBufferToValue(ByteBuffer buff) { + return buff.getDouble(); } @Override - protected ByteBuffer toByteBuffer(ByteBuffer buff, Double value) { - return buff.putDouble(value.doubleValue()); + protected void valueToByteBuffer(ByteBuffer buff, Double value) { + buff.putDouble(value.doubleValue()); } - } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/ModbusCoilElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/ModbusCoilElement.java deleted file mode 100644 index be9a24889c4..00000000000 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/ModbusCoilElement.java +++ /dev/null @@ -1,67 +0,0 @@ -package io.openems.edge.bridge.modbus.api.element; - -import java.util.Optional; - -import io.openems.common.exceptions.OpenemsException; -import io.openems.common.types.OpenemsType; -import io.openems.edge.common.type.TypeUtils; - -/** - * A ModbusCoilElement represents one or more Modbus Coils. - */ -public interface ModbusCoilElement extends ModbusElement { - - /** - * Sets the boolean value of this Element from Modbus Coil. - * - * @param coil the value - * @throws OpenemsException on error - */ - public void setInputCoil(Boolean coil) throws OpenemsException; - - /** - * Sets a value that should be written to the Modbus device. - * - * @param valueOpt the Optional value - * @throws OpenemsException on error - */ - public default void setNextWriteValue(Optional valueOpt) throws OpenemsException { - if (valueOpt.isPresent()) { - this._setNextWriteValue(// - Optional.of(// - TypeUtils.getAsType(OpenemsType.BOOLEAN, valueOpt.get()))); - } else { - this._setNextWriteValue(Optional.empty()); - } - } - - /** - * Gets the next write value and resets it. - * - *

    - * This method should be called once in every cycle on the - * TOPIC_CYCLE_EXECUTE_WRITE event. It makes sure, that the nextWriteValue gets - * initialized in every Cycle. If registers need to be written again in every - * cycle, next setNextWriteValue()-method needs to called on every Cycle. - * - * @return the Optional next write value - */ - public default Optional getNextWriteValueAndReset() { - var valueOpt = this.getNextWriteValue(); - try { - if (valueOpt.isPresent()) { - this._setNextWriteValue(Optional.empty()); - } - } catch (OpenemsException e) { - // can be safely ignored - } - return valueOpt; - } - - /** - * Gets the next write value. - * - * @return the Optional next write value - */ - public Optional getNextWriteValue(); -} diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/ModbusElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/ModbusElement.java index 6bde1fe8911..37bff50a0b1 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/ModbusElement.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/ModbusElement.java @@ -1,33 +1,43 @@ package io.openems.edge.bridge.modbus.api.element; -import java.util.Optional; -import java.util.function.Consumer; - -import io.openems.common.exceptions.OpenemsException; -import io.openems.common.types.OpenemsType; import io.openems.edge.bridge.modbus.api.AbstractModbusBridge; import io.openems.edge.bridge.modbus.api.task.AbstractTask; import io.openems.edge.bridge.modbus.api.task.Task; /** - * A ModbusElement represents one or more registers or coils in an - * {@link AbstractTask}. + * This abstract class serves as an Interface-like abstraction to avoid Java + * Generics for external access. */ -public interface ModbusElement { +@SuppressWarnings("rawtypes") +public abstract sealed class ModbusElement permits AbstractModbusElement { + + /** The start address of this Modbus element. */ + public final int startAddress; + /** Number of Registers or Coils. */ + public final int length; + + /** The Task - set via {@link #setModbusTask(Task)}. */ + private Task task = null; + + public ModbusElement(int startAddress, int length) { + this.startAddress = startAddress; + this.length = length; + } /** - * Gets the start address of this Modbus element. - * - * @return the start address + * This is called on deactivate of the Modbus-Bridge. It can be used to clear + * any references like listeners. */ - public int getStartAddress(); + public abstract void deactivate(); /** - * Number of Registers or Coils. + * Invalidates the Channel in case it could not be read from the Modbus device, + * i.e. sets the value to 'UNDEFINED'/null. Applies the + * 'invalidateElementsAfterReadErrors' config setting of the bridge. * - * @return the number of Registers or Coils + * @param bridge the {@link AbstractModbusBridge} */ - public abstract int getLength(); + public abstract void invalidate(AbstractModbusBridge bridge); /** * Set the {@link Task}, where this Element belongs to. @@ -37,44 +47,12 @@ public interface ModbusElement { * * @param task the {@link Task} */ - public void setModbusTask(Task task); - - /** - * Gets the type of this Register, e.g. INTEGER, BOOLEAN,.. - * - * @return the OpenemsType - */ - public OpenemsType getType(); - - /** - * Sets a value that should be written to the Modbus device. - * - * @param valueOpt the Optional value - * @throws OpenemsException on error - * @throws IllegalArgumentException on error - */ - public void _setNextWriteValue(Optional valueOpt) throws OpenemsException, IllegalArgumentException; + public final void setModbusTask(Task task) { + this.task = task; + } - /** - * Add an onSetNextWrite callback. It is called when a 'next write value' was - * set. - * - * @param callback the callback - */ - public void onSetNextWrite(Consumer> callback); + public final Task getModbusTask() { + return this.task; + } - /** - * Invalidates the Channel in case it could not be read from the Modbus device, - * i.e. sets the value to 'UNDEFINED'/null. Applies the - * 'invalidateElementsAfterReadErrors' config setting of the bridge. - * - * @param bridge the {@link AbstractModbusBridge} - */ - public void invalidate(AbstractModbusBridge bridge); - - /** - * This is called on deactivate of the Modbus-Bridge. It can be used to clear - * any references like listeners. - */ - public void deactivate(); } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/ModbusRegisterElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/ModbusRegisterElement.java index 8b63eecfe71..422c81d6123 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/ModbusRegisterElement.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/ModbusRegisterElement.java @@ -1,72 +1,120 @@ package io.openems.edge.bridge.modbus.api.element; -import java.util.Optional; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; -import com.ghgande.j2mod.modbus.procimg.InputRegister; import com.ghgande.j2mod.modbus.procimg.Register; +import com.ghgande.j2mod.modbus.procimg.SimpleRegister; -import io.openems.common.exceptions.OpenemsException; -import io.openems.edge.common.type.TypeUtils; +import io.openems.common.types.OpenemsType; /** * A ModbusRegisterElement represents one or more Modbus Registers. - * - * @param the target type + * + * @param the subclass of myself + * @param the binary type + * @param the OpenEMS type */ -public interface ModbusRegisterElement extends ModbusElement { +public abstract class ModbusRegisterElement, T> + extends AbstractModbusElement { - /** - * Sets the value of this Element from InputRegisters. - * - * @param registers the InputRegisters - * @throws OpenemsException on error - */ - public void setInputRegisters(InputRegister... registers) throws OpenemsException; + /** ByteOrder of the input registers. */ + private ByteOrder byteOrder = ByteOrder.BIG_ENDIAN; + + protected ModbusRegisterElement(OpenemsType type, int startAddress, int length) { + super(type, startAddress, length); + } + + protected abstract T byteBufferToValue(ByteBuffer buff); + + protected abstract void valueToByteBuffer(ByteBuffer buff, T value); /** - * Sets a value that should be written to the Modbus device. + * Sets the Byte-Order. Default is "BIG_ENDIAN". See + * http://www.simplymodbus.ca/FAQ.htm#Order. * - * @param valueOpt the Optional value - * @throws OpenemsException on error - * @throws IllegalArgumentException on error + * @param byteOrder the ByteOrder + * @return myself */ - public default void setNextWriteValue(Optional valueOpt) throws OpenemsException, IllegalArgumentException { - if (valueOpt.isPresent()) { - this._setNextWriteValue(// - Optional.of(// - TypeUtils.getAsType(this.getType(), valueOpt.get()))); - } else { - this._setNextWriteValue(Optional.empty()); + public final SELF byteOrder(ByteOrder byteOrder) { + this.byteOrder = byteOrder; + return this.self(); + } + + protected final ByteOrder getByteOrder() { + return this.byteOrder; + } + + private final ByteBuffer buildByteBuffer() { + return ByteBuffer.allocate(this.length * 2).order(this.getByteOrder()); + } + + protected final Register[] valueToRaw(T value, WordOrder wordOrder) { + var buff = this.buildByteBuffer(); + this.valueToByteBuffer(buff, value); + var b = buff.array(); + var result = new Register[this.length]; + switch (wordOrder) { + + case LSWMSW -> { + // Least significant word, most significant word + for (int i = 0, j = this.length - 1; i < this.length; i++, j--) { + result[i] = new SimpleRegister(b[j * 2], b[j * 2 + 1]); + } + } + + case MSWLSW -> { + // Most significant word, least significant word + for (int i = 0; i < this.length; i++) { + result[i] = new SimpleRegister(b[i * 2], b[i * 2 + 1]); + } + } + } + return result; + } + + @Override + protected final T rawToValue(Register[] registers) { + if (registers.length != this.length) { + throw new IllegalArgumentException("Registers length does not match. " // + + "Expected [" + this.length + "] " // + + "Got [" + registers.length + "] " // + + "for " + this.toString()); } + return this.registersToValue(registers); } /** - * Gets the next write value and resets it. - * + * Converts the {@link Register}s from j2mod to the expected type. + * *

    - * This method should be called once in every cycle on the - * TOPIC_CYCLE_EXECUTE_WRITE event. It makes sure, that the nextWriteValue gets - * initialized in every Cycle. If registers need to be written again in every - * cycle, next setNextWriteValue()-method needs to called on every Cycle. - * - * @return the next value as an Optional array of Registers + * The length of the registers array is guaranteed to match `this.length`. + * + * @param registers the Registers + * @return the typed/converted value */ - public default Optional getNextWriteValueAndReset() { - var valueOpt = this.getNextWriteValue(); - try { - if (valueOpt.isPresent()) { - this._setNextWriteValue(Optional.empty()); + protected abstract T registersToValue(Register[] registers); + + protected final T commonRegistersToValue(Register[] registers, WordOrder wordOrder) { + // fill buffer + var buff = this.buildByteBuffer(); + switch (wordOrder) { + + case LSWMSW -> { + // Most significant word, least significant word + for (int i = this.length - 1; i >= 0; i--) { + buff.put(registers[i].toBytes()); } - } catch (OpenemsException e) { - // can be safely ignored } - return valueOpt; - } - /** - * Gets the next write value. - * - * @return the next value as an Optional array of Registers - */ - public Optional getNextWriteValue(); -} + case MSWLSW -> { + // Least significant word, most significant word + for (int i = 0; i < this.length; i++) { + buff.put(registers[i].toBytes()); + } + } + } + buff.rewind(); + return this.byteBufferToValue(buff); + } +} \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/ModbusWriteElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/ModbusWriteElement.java deleted file mode 100644 index d4ca7cac865..00000000000 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/ModbusWriteElement.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.openems.edge.bridge.modbus.api.element; - -public interface ModbusWriteElement extends ModbusElement { - -} diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/SignedDoublewordElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/SignedDoublewordElement.java index ff15c3de78c..555cc0a9e15 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/SignedDoublewordElement.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/SignedDoublewordElement.java @@ -20,13 +20,13 @@ protected SignedDoublewordElement self() { } @Override - protected Long fromByteBuffer(ByteBuffer buff) { + protected Long byteBufferToValue(ByteBuffer buff) { return Long.valueOf(buff.getInt()); } @Override - protected ByteBuffer toByteBuffer(ByteBuffer buff, Long value) { - return buff.putInt(value.intValue()); + protected void valueToByteBuffer(ByteBuffer buff, Long value) { + buff.putInt(value.intValue()); } } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/SignedQuadruplewordElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/SignedQuadruplewordElement.java index aeb2f8ca58d..0d72fe8c956 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/SignedQuadruplewordElement.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/SignedQuadruplewordElement.java @@ -20,13 +20,13 @@ protected SignedQuadruplewordElement self() { } @Override - protected Long fromByteBuffer(ByteBuffer buff) { - return Long.valueOf(buff.getLong()); + protected Long byteBufferToValue(ByteBuffer buff) { + return buff.getLong(); } @Override - protected ByteBuffer toByteBuffer(ByteBuffer buff, Long value) { - return buff.putLong(value.longValue()); + protected void valueToByteBuffer(ByteBuffer buff, Long value) { + buff.putLong(value.longValue()); } } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/SignedWordElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/SignedWordElement.java index 2a416bfc2d3..69731c30c71 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/SignedWordElement.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/SignedWordElement.java @@ -7,9 +7,9 @@ /** * An SignedWordElement represents a Short value in an - * {@link AbstractWordElement}. + * {@link AbstractSingleWordElement}. */ -public class SignedWordElement extends AbstractWordElement { +public class SignedWordElement extends AbstractSingleWordElement { public SignedWordElement(int address) { super(OpenemsType.SHORT, address); @@ -21,14 +21,14 @@ protected SignedWordElement self() { } @Override - protected Short fromByteBuffer(ByteBuffer buff) { - return buff.order(this.getByteOrder()).getShort(0); + protected Short byteBufferToValue(ByteBuffer buff) { + return buff.getShort(0); } @Override - protected ByteBuffer toByteBuffer(ByteBuffer buff, Object object) { - Short value = TypeUtils.getAsType(OpenemsType.SHORT, object); - return buff.putShort(value.shortValue()); + protected void valueToByteBuffer(ByteBuffer buff, Short value) { + Short s = TypeUtils.getAsType(OpenemsType.SHORT, value); + buff.putShort(s.shortValue()); } -} +} \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/StringWordElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/StringWordElement.java index 160e23052f7..09ddd72067b 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/StringWordElement.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/StringWordElement.java @@ -1,94 +1,61 @@ package io.openems.edge.bridge.modbus.api.element; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Optional; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import static java.nio.ByteOrder.BIG_ENDIAN; -import com.ghgande.j2mod.modbus.procimg.InputRegister; -import com.ghgande.j2mod.modbus.procimg.Register; -import com.ghgande.j2mod.modbus.procimg.SimpleRegister; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; -import io.openems.common.exceptions.OpenemsException; import io.openems.common.types.OpenemsType; /** * An StringWordElement represents a String value. Each Register (= 2 bytes) * represents two characters. */ -public class StringWordElement extends AbstractModbusRegisterElement { - - private static final ByteOrder DEFAULT_BYTE_ORDER = ByteOrder.BIG_ENDIAN; - - private final Logger log = LoggerFactory.getLogger(AbstractWordElement.class); - - protected ByteOrder byteOrder = DEFAULT_BYTE_ORDER; - private final int length; +public class StringWordElement extends AbstractMultipleWordsElement { public StringWordElement(int startAddress, int length) { - super(OpenemsType.STRING, startAddress); - this.length = length; + super(OpenemsType.STRING, startAddress, length); } @Override - public final int getLength() { - return this.length; - } - - @Override - protected final void _setInputRegisters(InputRegister... registers) { - // convert registers - var buff = ByteBuffer.allocate(this.length * 2).order(this.getByteOrder()); - for (InputRegister r : registers) { - var bs = r.toBytes(); - for (var i = 0; i < bs.length; i++) { - if (bs[i] == 0) { - bs[i] = 32; // replace '0' with ASCII space - } + protected String byteBufferToValue(ByteBuffer buff) { + var src = buff.array(); + var out = new byte[src.length]; + for (int i = 0; i < src.length; i += 2) { + if (this.getByteOrder() == BIG_ENDIAN) { + out[i] = src[i]; + out[i + 1] = src[i + 1]; + + } else { // LITTLE_ENDIAN + out[i] = src[i + 1]; + out[i + 1] = src[i]; } - buff.put(bs); } - - var value = this.fromByteBuffer(buff); - // set value - super.setValue(value); + return new String(tidyUp(out)).trim(); } @Override - public void _setNextWriteValue(Optional valueOpt) throws OpenemsException { - if (this.isDebug()) { - this.log.info("Element [" + this + "] set next write value to [" + valueOpt.orElse(null) + "]."); - } - if (valueOpt.isPresent()) { - var buff = ByteBuffer.allocate(2 * this.length).order(this.getByteOrder()); - buff = this.toByteBuffer(buff, valueOpt.get()); - var b = buff.array(); - - var registers = new Register[this.length]; - for (var i = 0; i < this.length; i = i + 1) { - registers[i] = new SimpleRegister(b[i * 2], b[i * 2 + 1]); - } - - this.setNextWriteValueRegisters(Optional.of(registers)); - } else { - this.setNextWriteValueRegisters(Optional.empty()); - } - this.onSetNextWriteCallbacks.forEach(callback -> callback.accept(valueOpt)); + protected StringWordElement self() { + return this; } - protected String fromByteBuffer(ByteBuffer buff) { - return new String(buff.array()).trim(); + @Override + protected void valueToByteBuffer(ByteBuffer buff, String value) { + buff.put(value.getBytes(StandardCharsets.US_ASCII)); } - protected ByteBuffer toByteBuffer(ByteBuffer buff, String value) { - return ByteBuffer.wrap(value.getBytes()); + private static byte[] tidyUp(byte[] array) { + for (var i = 0; i < array.length; i++) { + array[i] = tidyUp(array[i]); + } + return array; } - @Override - protected StringWordElement self() { - return this; + private static byte tidyUp(byte b) { + if (b == 0) { + b = 32;// replace '0' with ASCII space + } + return b; } -} +} \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/UnsignedDoublewordElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/UnsignedDoublewordElement.java index d57d6192d6e..2f02a8b29e6 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/UnsignedDoublewordElement.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/UnsignedDoublewordElement.java @@ -20,12 +20,13 @@ protected UnsignedDoublewordElement self() { } @Override - protected Long fromByteBuffer(ByteBuffer buff) { + protected Long byteBufferToValue(ByteBuffer buff) { return Integer.toUnsignedLong(buff.getInt(0)); } @Override - protected ByteBuffer toByteBuffer(ByteBuffer buff, Long value) { - return buff.putInt(value.intValue()); + protected void valueToByteBuffer(ByteBuffer buff, Long value) { + buff.putInt(value.intValue()); } -} + +} \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/UnsignedQuadruplewordElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/UnsignedQuadruplewordElement.java index 20a869fa332..10e9aa8dad1 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/UnsignedQuadruplewordElement.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/UnsignedQuadruplewordElement.java @@ -20,15 +20,13 @@ protected UnsignedQuadruplewordElement self() { } @Override - protected Long fromByteBuffer(ByteBuffer buff) { - + protected Long byteBufferToValue(ByteBuffer buff) { return buff.getLong(0); } @Override - protected ByteBuffer toByteBuffer(ByteBuffer buff, Long value) { - - return buff.putLong(value); + protected void valueToByteBuffer(ByteBuffer buff, Long value) { + buff.putLong(value); } -} +} \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/UnsignedWordElement.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/UnsignedWordElement.java index 18ccda2170c..054ef8def78 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/UnsignedWordElement.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/element/UnsignedWordElement.java @@ -7,9 +7,9 @@ /** * An UnsignedWordElement represents an Integer value in an - * {@link AbstractWordElement}. + * {@link AbstractSingleWordElement}. */ -public class UnsignedWordElement extends AbstractWordElement { +public class UnsignedWordElement extends AbstractSingleWordElement { public UnsignedWordElement(int address) { super(OpenemsType.INTEGER, address); @@ -21,14 +21,14 @@ protected UnsignedWordElement self() { } @Override - protected Integer fromByteBuffer(ByteBuffer buff) { + protected Integer byteBufferToValue(ByteBuffer buff) { return Short.toUnsignedInt(buff.getShort(0)); } @Override - protected ByteBuffer toByteBuffer(ByteBuffer buff, Object object) { - Integer value = TypeUtils.getAsType(OpenemsType.INTEGER, object); - return buff.putShort(value.shortValue()); + protected void valueToByteBuffer(ByteBuffer buff, Integer value) { + Integer i = TypeUtils.getAsType(OpenemsType.INTEGER, value); + buff.putShort(i.shortValue()); } -} +} \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadDigitalInputsTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadDigitalInputsTask.java index 1e72aa8f9ba..a7a6cec7edc 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadDigitalInputsTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadDigitalInputsTask.java @@ -8,27 +8,27 @@ import com.ghgande.j2mod.modbus.util.BitVector; import io.openems.common.exceptions.OpenemsException; -import io.openems.edge.bridge.modbus.api.element.ModbusCoilElement; +import io.openems.edge.bridge.modbus.api.element.CoilElement; import io.openems.edge.bridge.modbus.api.element.ModbusElement; import io.openems.edge.common.taskmanager.Priority; public abstract class AbstractReadDigitalInputsTask // - extends AbstractReadTask { + extends AbstractReadTask { public AbstractReadDigitalInputsTask(String name, Class responseClazz, int startAddress, - Priority priority, ModbusCoilElement... elements) { - super(name, responseClazz, ModbusCoilElement.class, startAddress, priority, elements); + Priority priority, CoilElement... elements) { + super(name, responseClazz, CoilElement.class, startAddress, priority, elements); } @Override - protected void handleResponse(ModbusCoilElement element, int position, Boolean[] response) throws OpenemsException { - element.setInputCoil(response[position]); + protected void handleResponse(CoilElement element, int position, Boolean[] response) throws OpenemsException { + element.setInputValue(response[position]); } @Override - protected int calculateNextPosition(ModbusElement modbusElement, int position) { + protected int calculateNextPosition(ModbusElement modbusElement, int position) { return position + 1; } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadInputRegistersTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadInputRegistersTask.java index ac144f3ae0b..939fad4ddfb 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadInputRegistersTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadInputRegistersTask.java @@ -16,18 +16,19 @@ public abstract class AbstractReadInputRegistersTask { public AbstractReadInputRegistersTask(String name, Class responseClazz, int startAddress, - Priority priority, ModbusElement... elements) { + Priority priority, ModbusElement... elements) { super(name, responseClazz, ModbusRegisterElement.class, startAddress, priority, elements); } + @SuppressWarnings("unchecked") @Override protected void handleResponse(ModbusRegisterElement element, int position, InputRegister[] response) throws OpenemsException { - element.setInputRegisters(Arrays.copyOfRange(response, position, position + element.getLength())); + element.setInputValue(Arrays.copyOfRange(response, position, position + element.length)); } @Override - protected int calculateNextPosition(ModbusElement modbusElement, int position) { - return position + modbusElement.getLength(); + protected int calculateNextPosition(ModbusElement modbusElement, int position) { + return position + modbusElement.length; } } \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadTask.java index 89e747224de..e9384bf099a 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadTask.java @@ -17,14 +17,13 @@ /** * An abstract Modbus 'AbstractTask' is holding references to one or more Modbus - * {@link AbstractModbusElement} which have register addresses in the same - * range. + * {@link ModbusElement}s which have register addresses in the same range. */ @SuppressWarnings("rawtypes") public abstract class AbstractReadTask // extends AbstractTask implements ReadTask { @@ -33,7 +32,7 @@ public abstract class AbstractReadTask elementClazz; public AbstractReadTask(String name, Class responseClazz, Class elementClazz, int startAddress, - Priority priority, ModbusElement... elements) { + Priority priority, ModbusElement... elements) { super(name, responseClazz, startAddress, elements); this.elementClazz = elementClazz; this.priority = priority; @@ -134,7 +133,7 @@ public Priority getPriority() { * @param modbusElement current Element * @return next position */ - protected abstract int calculateNextPosition(ModbusElement modbusElement, int position); + protected abstract int calculateNextPosition(ModbusElement modbusElement, int position); /** * Factory for a {@link ModbusRequest}. diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractTask.java index b4b981472b2..f25b7dcdc81 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractTask.java @@ -23,7 +23,7 @@ /** * An abstract Modbus 'AbstractTask' is holding references to one or more Modbus - * {@link ModbusElement} which have register addresses in the same range. + * {@link ModbusElement}s which have register addresses in the same range. */ public abstract non-sealed class AbstractTask responseClazz; protected final int startAddress; protected final int length; - protected final ModbusElement[] elements; + protected final ModbusElement[] elements; private final Logger log = LoggerFactory.getLogger(AbstractTask.class); private AbstractOpenemsModbusComponent parent = null; // this is always set by ModbusProtocol.addTask() - public AbstractTask(String name, Class responseClazz, int startAddress, ModbusElement... elements) { + public AbstractTask(String name, Class responseClazz, int startAddress, ModbusElement... elements) { this.name = name; this.responseClazz = responseClazz; this.startAddress = startAddress; @@ -47,20 +47,20 @@ public AbstractTask(String name, Class responseClazz, int startAddress var nextStartAddress = startAddress; var length = 0; for (var element : elements) { - if (element.getStartAddress() != nextStartAddress) { + if (element.startAddress != nextStartAddress) { throw new IllegalArgumentException("StartAddress for Modbus Element wrong. " // - + "Got [" + element.getStartAddress() + "/0x" + Integer.toHexString(element.getStartAddress()) - + "] Expected [" + nextStartAddress + "/0x" + Integer.toHexString(nextStartAddress) + "]"); + + "Got [" + element.startAddress + "/0x" + Integer.toHexString(element.startAddress) + "] " // + + "Expected [" + nextStartAddress + "/0x" + Integer.toHexString(nextStartAddress) + "]"); } - nextStartAddress += element.getLength(); - length += element.getLength(); + nextStartAddress += element.length; + length += element.length; element.setModbusTask(this); } this.length = length; } // Override for Task.getElements() - public ModbusElement[] getElements() { + public ModbusElement[] getElements() { return this.elements; } @@ -211,7 +211,7 @@ protected LogVerbosity getLogVerbosity(AbstractModbusBridge bridge) { * Deactivate. */ public void deactivate() { - for (ModbusElement element : this.elements) { + for (ModbusElement element : this.elements) { element.deactivate(); } } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractWriteTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractWriteTask.java index 7209e1ef08d..40460ca1f75 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractWriteTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractWriteTask.java @@ -16,8 +16,7 @@ public abstract class AbstractWriteTask // extends AbstractTask implements WriteTask { - public AbstractWriteTask(String name, Class responseClazz, int startAddress, - ModbusElement... elements) { + public AbstractWriteTask(String name, Class responseClazz, int startAddress, ModbusElement... elements) { super(name, responseClazz, startAddress, elements); } @@ -39,7 +38,7 @@ protected final String payloadToString(RESPONSE response) { public abstract static class Single> // + ELEMENT extends ModbusElement> // extends AbstractWriteTask { protected final ELEMENT element; diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC16WriteRegistersTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC16WriteRegistersTask.java index 4af28910fab..0c16e83e897 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC16WriteRegistersTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC16WriteRegistersTask.java @@ -28,7 +28,7 @@ public class FC16WriteRegistersTask private final Logger log = LoggerFactory.getLogger(FC16WriteRegistersTask.class); - public FC16WriteRegistersTask(int startAddress, ModbusElement... elements) { + public FC16WriteRegistersTask(int startAddress, ModbusElement... elements) { super("FC16WriteRegisters", WriteMultipleRegistersResponse.class, startAddress, elements); } @@ -70,23 +70,24 @@ public ExecuteState execute(AbstractModbusBridge bridge) { * @param logWarn {@link Consumer} to log a warning * @return a list of CombinedWriteRegisters */ - protected static List mergeWriteRegisters(ModbusElement[] elements, + protected static List mergeWriteRegisters(ModbusElement[] elements, Consumer logWarn) { final var writes = new ArrayList(); for (var element : elements) { - if (element instanceof ModbusRegisterElement e) { - e.getNextWriteValueAndReset().ifPresent(registers -> { + if (element instanceof ModbusRegisterElement e) { + var registers = e.getNextWriteValueAndReset(); + if (registers != null) { // found value registers -> add to 'writes' final MergedWriteRegisters write; if (writes.isEmpty()) { // no writes created yet - write = MergedWriteRegisters.of(e.getStartAddress()); + write = MergedWriteRegisters.of(e.startAddress); writes.add(write); } else { var lastWrite = writes.get(writes.size() - 1); - if (lastWrite.getLastAddress() + 1 != e.getStartAddress()) { + if (lastWrite.getLastAddress() + 1 != e.startAddress) { // there is a hole between last element and current element - write = MergedWriteRegisters.of(e.getStartAddress()); + write = MergedWriteRegisters.of(e.startAddress); writes.add(write); } else { // no hole -> combine writes @@ -94,7 +95,7 @@ protected static List mergeWriteRegisters(ModbusElement } } write.add(registers); - }); + } } else { logWarn.accept( "Unable to execute Write for ModbusElement [" + element + "]: No ModbusRegisterElement!"); diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC1ReadCoilsTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC1ReadCoilsTask.java index 141bdaedecf..859db8748aa 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC1ReadCoilsTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC1ReadCoilsTask.java @@ -4,7 +4,7 @@ import com.ghgande.j2mod.modbus.msg.ReadCoilsResponse; import com.ghgande.j2mod.modbus.util.BitVector; -import io.openems.edge.bridge.modbus.api.element.ModbusCoilElement; +import io.openems.edge.bridge.modbus.api.element.CoilElement; import io.openems.edge.common.taskmanager.Priority; /** @@ -13,7 +13,7 @@ */ public class FC1ReadCoilsTask extends AbstractReadDigitalInputsTask { - public FC1ReadCoilsTask(int startAddress, Priority priority, ModbusCoilElement... elements) { + public FC1ReadCoilsTask(int startAddress, Priority priority, CoilElement... elements) { super("FC1ReadCoils", ReadCoilsResponse.class, startAddress, priority, elements); } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC2ReadInputsTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC2ReadInputsTask.java index 8d3ac19b3f9..bd42892fa01 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC2ReadInputsTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC2ReadInputsTask.java @@ -4,7 +4,7 @@ import com.ghgande.j2mod.modbus.msg.ReadInputDiscretesResponse; import com.ghgande.j2mod.modbus.util.BitVector; -import io.openems.edge.bridge.modbus.api.element.ModbusCoilElement; +import io.openems.edge.bridge.modbus.api.element.CoilElement; import io.openems.edge.common.taskmanager.Priority; /** @@ -14,7 +14,7 @@ public class FC2ReadInputsTask extends AbstractReadDigitalInputsTask { - public FC2ReadInputsTask(int startAddress, Priority priority, ModbusCoilElement... elements) { + public FC2ReadInputsTask(int startAddress, Priority priority, CoilElement... elements) { super("FC2ReadCoils", ReadInputDiscretesResponse.class, startAddress, priority, elements); } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC3ReadRegistersTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC3ReadRegistersTask.java index 06fc6d700a4..8b32d90e58f 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC3ReadRegistersTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC3ReadRegistersTask.java @@ -16,7 +16,7 @@ public class FC3ReadRegistersTask extends AbstractReadInputRegistersTask { - public FC3ReadRegistersTask(int startAddress, Priority priority, ModbusElement... elements) { + public FC3ReadRegistersTask(int startAddress, Priority priority, ModbusElement... elements) { super("FC3ReadHoldingRegisters", ReadMultipleRegistersResponse.class, startAddress, priority, elements); } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC4ReadInputRegistersTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC4ReadInputRegistersTask.java index 6e7218c4e73..568971022b7 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC4ReadInputRegistersTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC4ReadInputRegistersTask.java @@ -16,7 +16,7 @@ public class FC4ReadInputRegistersTask extends AbstractReadInputRegistersTask { - public FC4ReadInputRegistersTask(int startAddress, Priority priority, ModbusElement... elements) { + public FC4ReadInputRegistersTask(int startAddress, Priority priority, ModbusElement... elements) { super("FC4ReadInputRegisters", ReadInputRegistersResponse.class, startAddress, priority, elements); } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC5WriteCoilTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC5WriteCoilTask.java index f30dc641d05..25c3d3de17a 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC5WriteCoilTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC5WriteCoilTask.java @@ -4,25 +4,23 @@ import com.ghgande.j2mod.modbus.msg.WriteCoilResponse; import io.openems.common.exceptions.OpenemsException; -import io.openems.edge.bridge.modbus.api.element.ModbusCoilElement; +import io.openems.edge.bridge.modbus.api.element.CoilElement; /** * Implements a Write Single Coil Task, using Modbus function code 5 * (http://www.simplymodbus.ca/FC05.htm). */ -public class FC5WriteCoilTask extends AbstractWriteTask.Single { +public class FC5WriteCoilTask extends AbstractWriteTask.Single { - public FC5WriteCoilTask(int startAddress, ModbusCoilElement element) { + public FC5WriteCoilTask(int startAddress, CoilElement element) { super("FC5WriteCoil", WriteCoilResponse.class, startAddress, element); } @Override protected WriteCoilRequest createModbusRequest() throws OpenemsException { - var valueOpt = this.element.getNextWriteValueAndReset(); - if (valueOpt.isPresent()) { - boolean value = valueOpt.get(); + var value = this.element.getNextWriteValueAndReset(); + if (value != null) { return new WriteCoilRequest(startAddress, value); - } else { return null; } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC6WriteRegisterTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC6WriteRegisterTask.java index 7aeffd4836b..c116f3907ab 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC6WriteRegisterTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC6WriteRegisterTask.java @@ -5,32 +5,29 @@ import io.openems.common.exceptions.OpenemsException; import io.openems.edge.bridge.modbus.api.ModbusUtils; -import io.openems.edge.bridge.modbus.api.element.AbstractWordElement; +import io.openems.edge.bridge.modbus.api.element.AbstractSingleWordElement; public class FC6WriteRegisterTask extends - AbstractWriteTask.Single> { + AbstractWriteTask.Single> { - public FC6WriteRegisterTask(int startAddress, AbstractWordElement element) { + public FC6WriteRegisterTask(int startAddress, AbstractSingleWordElement element) { super("FC6WriteRegister", WriteSingleRegisterResponse.class, startAddress, element); } @Override protected WriteSingleRegisterRequest createModbusRequest() throws OpenemsException { - var valueOpt = this.element.getNextWriteValueAndReset(); - if (valueOpt.isPresent()) { - var registers = valueOpt.get(); - - if (registers.length == 1 && registers[0] != null) { - // found value -> write - var register = registers[0]; - return new WriteSingleRegisterRequest(this.startAddress, register); + var registers = this.element.getNextWriteValueAndReset(); + if (registers == null) { + return null; + } - } else { - throw new OpenemsException("Expected exactly one register. Got [" + registers.length + "]"); - } + if (registers.length == 1 && registers[0] != null) { + // found value -> write + var register = registers[0]; + return new WriteSingleRegisterRequest(this.startAddress, register); } else { - return null; + throw new OpenemsException("Expected exactly one register. Got [" + registers.length + "]"); } } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/ReadTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/ReadTask.java index b77a6e87f6a..bc04d31ea95 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/ReadTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/ReadTask.java @@ -1,12 +1,12 @@ package io.openems.edge.bridge.modbus.api.task; -import io.openems.edge.bridge.modbus.api.element.AbstractModbusElement; +import io.openems.edge.bridge.modbus.api.element.ModbusElement; /** * A Modbus 'ReadTask' is holding references to one or more Modbus - * {@link AbstractModbusElement} which have register addresses in the same - * range. The ReadTask handles the execution (query) on this range. @{link - * WriteTask} inherits from ReadTask. + * {@link ModbusElement}s which have register addresses in the same range. The + * ReadTask handles the execution (query) on this range. @{link WriteTask} + * inherits from ReadTask. */ public non-sealed interface ReadTask extends Task { } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/Task.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/Task.java index 44baa414d72..81833a06ad8 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/Task.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/Task.java @@ -14,7 +14,7 @@ public sealed interface Task extends ManagedTask permits AbstractTask, ReadTask, * * @return an array of ModbusElements */ - public ModbusElement[] getElements(); + public ModbusElement[] getElements(); /** * Gets the start Modbus register address. diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/WaitTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/WaitTask.java index 817da169482..bcf24376616 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/WaitTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/WaitTask.java @@ -33,7 +33,7 @@ public final Priority getPriority() { } @Override - public final ModbusElement[] getElements() { + public final ModbusElement[] getElements() { return new ModbusElement[0]; } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/ModbusWorker.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/ModbusWorker.java index 56def5a2b05..f51191f8631 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/ModbusWorker.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/ModbusWorker.java @@ -33,7 +33,7 @@ public class ModbusWorker extends AbstractImmediateWorker { // Callbacks private final Function execute; - private final Consumer[]> invalidate; + private final Consumer invalidate; private final DefectiveComponents defectiveComponents; private final TasksSupplierImpl tasksSupplier; @@ -54,7 +54,7 @@ public class ModbusWorker extends AbstractImmediateWorker { * channel * @param logVerbosity the configured {@link LogVerbosity} */ - public ModbusWorker(Function execute, Consumer[]> invalidate, + public ModbusWorker(Function execute, Consumer invalidate, Consumer cycleTimeIsTooShortChannel, Consumer cycleDelayChannel, AtomicReference logVerbosity) { this.execute = execute; diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/AbstractOpenemsSunSpecComponent.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/AbstractOpenemsSunSpecComponent.java index cb39232c26c..ff19b82f577 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/AbstractOpenemsSunSpecComponent.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/AbstractOpenemsSunSpecComponent.java @@ -21,9 +21,9 @@ import io.openems.edge.bridge.modbus.api.ElementToChannelScaleFactorConverter; import io.openems.edge.bridge.modbus.api.ModbusProtocol; import io.openems.edge.bridge.modbus.api.ModbusUtils; -import io.openems.edge.bridge.modbus.api.element.AbstractModbusElement; import io.openems.edge.bridge.modbus.api.element.DummyRegisterElement; import io.openems.edge.bridge.modbus.api.element.ModbusElement; +import io.openems.edge.bridge.modbus.api.element.ModbusRegisterElement; import io.openems.edge.bridge.modbus.api.element.UnsignedDoublewordElement; import io.openems.edge.bridge.modbus.api.element.UnsignedWordElement; import io.openems.edge.bridge.modbus.api.task.FC16WriteRegistersTask; @@ -283,12 +283,12 @@ public boolean isSunSpecInitializationCompleted() { protected void addBlock(int startAddress, SunSpecModel model, Priority priority) throws OpenemsException { this.logInfo(this.log, "Adding SunSpec-Model [" + model.getBlockId() + ":" + model.label() + "] starting at [" + startAddress + "]"); - var elements = new ModbusElement[model.points().length]; + var elements = new ModbusElement[model.points().length]; startAddress += 2; for (var i = 0; i < model.points().length; i++) { var point = model.points()[i]; var element = point.get().generateModbusElement(startAddress); - startAddress += element.getLength(); + startAddress += element.length; elements[i] = element; var channelId = point.getChannelId(); @@ -335,20 +335,20 @@ protected void addBlock(int startAddress, SunSpecModel model, Priority priority) switch (point.get().accessMode) { case READ_ONLY: // Read-Only -> replace element with dummy - element = new DummyRegisterElement(element.getStartAddress(), - element.getStartAddress() + point.get().type.length - 1); + element = new DummyRegisterElement(element.startAddress, + element.startAddress + point.get().type.length - 1); break; case READ_WRITE: case WRITE_ONLY: // Add a Write-Task // TODO create one FC16WriteRegistersTask for entire block - final Task writeTask = new FC16WriteRegistersTask(element.getStartAddress(), element); + final Task writeTask = new FC16WriteRegistersTask(element.startAddress, element); this.modbusProtocol.addTask(writeTask); break; } } - final Task readTask = new FC3ReadRegistersTask(elements[0].getStartAddress(), priority, elements); + final Task readTask = new FC3ReadRegistersTask(elements[0].startAddress, priority, elements); this.modbusProtocol.addTask(readTask); } @@ -361,7 +361,7 @@ protected void addBlock(int startAddress, SunSpecModel model, Priority priority) * @throws OpenemsException on error */ @SafeVarargs - private final CompletableFuture> readElementsOnceTyped(AbstractModbusElement... elements) + private final CompletableFuture> readElementsOnceTyped(ModbusRegisterElement... elements) throws OpenemsException { // Register listeners for elements @SuppressWarnings("unchecked") @@ -381,7 +381,7 @@ private final CompletableFuture> readElementsOnceTyped(AbstractModbu } // Activate task - final Task task = new FC3ReadRegistersTask(elements[0].getStartAddress(), Priority.HIGH, elements); + final Task task = new FC3ReadRegistersTask(elements[0].startAddress, Priority.HIGH, elements); this.modbusProtocol.addTask(task); // Prepare result diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/SunSpecPoint.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/SunSpecPoint.java index 6beb47fcf4e..0a0846cb463 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/SunSpecPoint.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/SunSpecPoint.java @@ -6,9 +6,9 @@ import io.openems.common.channel.Unit; import io.openems.common.types.OpenemsType; import io.openems.common.types.OptionsEnum; -import io.openems.edge.bridge.modbus.api.element.AbstractModbusElement; import io.openems.edge.bridge.modbus.api.element.DummyRegisterElement; import io.openems.edge.bridge.modbus.api.element.FloatDoublewordElement; +import io.openems.edge.bridge.modbus.api.element.ModbusElement; import io.openems.edge.bridge.modbus.api.element.SignedDoublewordElement; import io.openems.edge.bridge.modbus.api.element.SignedQuadruplewordElement; import io.openems.edge.bridge.modbus.api.element.SignedWordElement; @@ -100,12 +100,12 @@ public PointImpl(String channelId, String label, String description, String note } /** - * Generates a Modbus Element for the given point + startAddress. + * Generates a {@link ModbusElement} for the given point + startAddress. * * @param startAddress the startAddress of the Point * @return a new Modbus Element */ - public final AbstractModbusElement generateModbusElement(Integer startAddress) { + public final ModbusElement generateModbusElement(Integer startAddress) { switch (this.type) { case UINT16: case ACC16: diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/BitsWordElementTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/BitsWordElementTest.java index 2dfe6742fb0..65b0e885a56 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/BitsWordElementTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/BitsWordElementTest.java @@ -3,14 +3,11 @@ import static io.openems.common.channel.AccessMode.READ_WRITE; import static io.openems.common.types.OpenemsType.BOOLEAN; import static io.openems.common.types.OpenemsType.INTEGER; -import static java.nio.ByteOrder.LITTLE_ENDIAN; import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import java.util.Optional; - import org.junit.Test; import com.ghgande.j2mod.modbus.procimg.Register; @@ -18,6 +15,7 @@ import io.openems.common.exceptions.OpenemsException; import io.openems.common.types.OpenemsType; +import io.openems.edge.bridge.modbus.api.AbstractModbusBridge; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent.BitConverter; import io.openems.edge.common.channel.BooleanWriteChannel; import io.openems.edge.common.channel.Channel; @@ -34,14 +32,29 @@ public void testRead() throws Exception { final var channel1 = addBit(sut, 1); final var channel2 = addBit(sut, 2, BitConverter.INVERT); - // TODO ByteOrder is not handled here - sut.element.setInputRegisters(new Register[] { new SimpleRegister((byte) 0x00, (byte) 0x01) }); + sut.element.setInputValue(new Register[] { new SimpleRegister((byte) 0x00, (byte) 0x01) }); assertTrue(channel0.getNextValue().get()); assertFalse(channel1.getNextValue().get()); assertTrue(channel2.getNextValue().get()); } + @Test + public void testInvalidate() throws Exception { + var sut = generateSut(); + final var bridge = (AbstractModbusBridge) sut.getBridgeModbus(); + + final var channel0 = addBit(sut, 0); + + sut.element.setInputValue(new Register[] { new SimpleRegister((byte) 0x00, (byte) 0x01) }); + + assertTrue(channel0.getNextValue().get()); + sut.element.invalidate(bridge); // invalidValueCounter = 1 + assertTrue(channel0.getNextValue().get()); + sut.element.invalidate(bridge); // invalidValueCounter = 2 + assertNull(channel0.getNextValue().get()); + } + @Test public void testWriteNone1() throws Exception { var sut = generateSut(); @@ -51,7 +64,7 @@ public void testWriteNone1() throws Exception { addBit(sut, 2, BitConverter.INVERT); addBit(sut, 3); - assertEquals(Optional.empty(), sut.element.getNextWriteValueAndReset()); + assertNull(sut.element.getNextWriteValueAndReset()); } @Test @@ -68,7 +81,7 @@ public void testWriteNone2() throws Exception { channel2.setNextWriteValue(false); System.out.println("NOTE: the following IllegalArgumentException is expected"); - assertEquals(Optional.empty(), sut.element.getNextWriteValueAndReset()); + assertNull(sut.element.getNextWriteValueAndReset()); } @Test @@ -85,27 +98,14 @@ public void testWriteBigEndian() throws Exception { channel2.setNextWriteValue(false); channel8.setNextWriteValue(true); - var registers = sut.element.getNextWriteValueAndReset().get(); + var registers = sut.element.getNextWriteValueAndReset(); assertArrayEquals(new byte[] { (byte) 0x01, (byte) 0x06 }, registers[0].toBytes()); } - @Test - public void testWriteLittleEndian() throws Exception { + @Test(expected = IllegalArgumentException.class) + public void testRegistersLengthDoesNotMatch() throws Exception { var sut = generateSut(); - sut.element.byteOrder(LITTLE_ENDIAN); - - final var channel0 = addBit(sut, 0); - final var channel1 = addBit(sut, 1); - final var channel2 = addBit(sut, 2, BitConverter.INVERT); - final var channel8 = addBit(sut, 8); - - channel0.setNextWriteValue(false); - channel1.setNextWriteValue(true); - channel2.setNextWriteValue(false); - channel8.setNextWriteValue(true); - - var registers = sut.element.getNextWriteValueAndReset().get(); - assertArrayEquals(new byte[] { (byte) 0x06, (byte) 0x01 }, registers[0].toBytes()); + sut.element.setInputValue(new Register[2]); } @Test(expected = IllegalArgumentException.class) diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/CoilElementTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/CoilElementTest.java index fccab49c144..f672f2a8371 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/CoilElementTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/CoilElementTest.java @@ -2,8 +2,7 @@ import static io.openems.common.types.OpenemsType.BOOLEAN; import static org.junit.Assert.assertEquals; - -import java.util.Optional; +import static org.junit.Assert.assertNull; import org.junit.Test; @@ -15,13 +14,13 @@ public class CoilElementTest { @Test public void testRead() throws OpenemsException { var sut = new ModbusTest.FC1ReadCoils<>(new CoilElement(0), BOOLEAN); - sut.element.setInputCoil(true); + sut.element.setInputValue(true); assertEquals(true, sut.channel.getNextValue().get()); - sut.element.setInputCoil(false); + sut.element.setInputValue(false); assertEquals(false, sut.channel.getNextValue().get()); - sut.element.setInputCoil(null); + sut.element.setInputValue(null); assertEquals(null, sut.channel.getNextValue().get()); } @@ -29,10 +28,10 @@ public void testRead() throws OpenemsException { public void testWrite() throws IllegalArgumentException, OpenemsNamedException { var sut = new ModbusTest.FC5WriteCoil<>(new CoilElement(0), BOOLEAN); sut.channel.setNextWriteValueFromObject(true); - assertEquals(true, ((CoilElement) sut.element).getNextWriteValueAndReset().get()); + assertEquals(true, sut.element.getNextWriteValueAndReset()); sut.channel.setNextWriteValueFromObject(null); - assertEquals(Optional.empty(), ((CoilElement) sut.element).getNextWriteValueAndReset()); + assertNull(sut.element.getNextWriteValueAndReset()); } } diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/DummyCoilElementTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/DummyCoilElementTest.java deleted file mode 100644 index 9710f39721c..00000000000 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/DummyCoilElementTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.openems.edge.bridge.modbus.api.element; - -import org.junit.Test; - -public class DummyCoilElementTest { - - @Test - public void test() { - new DummyCoilElement(0); - } - -} diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/FloatDoublewordElementTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/FloatDoublewordElementTest.java index 25ab83d3641..fbd8c17e298 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/FloatDoublewordElementTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/FloatDoublewordElementTest.java @@ -1,6 +1,7 @@ package io.openems.edge.bridge.modbus.api.element; import static io.openems.common.types.OpenemsType.FLOAT; +import static io.openems.edge.bridge.modbus.api.element.WordOrder.LSWMSW; import static java.nio.ByteOrder.LITTLE_ENDIAN; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -20,7 +21,7 @@ public void testReadBigEndianMswLsw() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(// new FloatDoublewordElement(0), // FLOAT); - sut.element.setInputRegisters(new Register[] { // + sut.element.setInputValue(new Register[] { // new SimpleRegister((byte) 0x44, (byte) 0x9A), // new SimpleRegister((byte) 0x51, (byte) 0xEC) // }); @@ -30,9 +31,9 @@ public void testReadBigEndianMswLsw() throws OpenemsException { @Test public void testReadBigEndianLswMsw() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(// - new FloatDoublewordElement(0).wordOrder(WordOrder.LSWMSW), // + new FloatDoublewordElement(0).wordOrder(LSWMSW), // FLOAT); - sut.element.setInputRegisters(new Register[] { // + sut.element.setInputValue(new Register[] { // new SimpleRegister((byte) 0x51, (byte) 0xEC), // new SimpleRegister((byte) 0x44, (byte) 0x9A) // }); @@ -44,7 +45,7 @@ public void testReadLittleEndianMswLsw() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(// new FloatDoublewordElement(0).byteOrder(LITTLE_ENDIAN), // FLOAT); - sut.element.setInputRegisters(new Register[] { // + sut.element.setInputValue(new Register[] { // new SimpleRegister((byte) 0xEC, (byte) 0x51), // new SimpleRegister((byte) 0x9A, (byte) 0x44) // }); @@ -52,11 +53,11 @@ public void testReadLittleEndianMswLsw() throws OpenemsException { } @Test - public void testReadLittleEndianlswMsw() throws OpenemsException { + public void testReadLittleEndianLswMsw() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(// - new FloatDoublewordElement(0).byteOrder(LITTLE_ENDIAN).wordOrder(WordOrder.LSWMSW), // + new FloatDoublewordElement(0).wordOrder(LSWMSW).byteOrder(LITTLE_ENDIAN), // FLOAT); - sut.element.setInputRegisters(new Register[] { // + sut.element.setInputValue(new Register[] { // new SimpleRegister((byte) 0x9A, (byte) 0x44), // new SimpleRegister((byte) 0xEC, (byte) 0x51) // }); @@ -69,7 +70,7 @@ public void testWrite() throws IllegalArgumentException, OpenemsNamedException { new FloatDoublewordElement(0), // FLOAT); sut.channel.setNextWriteValueFromObject(1234.56F); - var registers = sut.element.getNextWriteValueAndReset().get(); + var registers = sut.element.getNextWriteValueAndReset(); assertArrayEquals(new byte[] { (byte) 0x44, (byte) 0x9A }, registers[0].toBytes()); assertArrayEquals(new byte[] { (byte) 0x51, (byte) 0xEC }, registers[1].toBytes()); } diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/FloatQuadruplewordElementTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/FloatQuadruplewordElementTest.java index 0465777f033..1f29352ab3d 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/FloatQuadruplewordElementTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/FloatQuadruplewordElementTest.java @@ -22,7 +22,7 @@ public void testReadBigEndianMswLsw() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(// new FloatQuadruplewordElement(0), // DOUBLE); - sut.element.setInputRegisters(new Register[] { // + sut.element.setInputValue(new Register[] { // new SimpleRegister((byte) 0x40, (byte) 0x93), // new SimpleRegister((byte) 0x4A, (byte) 0x3D), // new SimpleRegister((byte) 0x70, (byte) 0xA3), // @@ -36,7 +36,7 @@ public void testReadBigEndianLswMsw() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(// new FloatQuadruplewordElement(0).wordOrder(WordOrder.LSWMSW), // DOUBLE); - sut.element.setInputRegisters(new Register[] { // + sut.element.setInputValue(new Register[] { // new SimpleRegister((byte) 0xD7, (byte) 0x0A), // new SimpleRegister((byte) 0x70, (byte) 0xA3), // new SimpleRegister((byte) 0x4A, (byte) 0x3D), // @@ -50,7 +50,7 @@ public void testReadLittleEndianMswLsw() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(// new FloatQuadruplewordElement(0).byteOrder(LITTLE_ENDIAN), // DOUBLE); - sut.element.setInputRegisters(new Register[] { // + sut.element.setInputValue(new Register[] { // new SimpleRegister((byte) 0x0A, (byte) 0xD7), // new SimpleRegister((byte) 0xA3, (byte) 0x70), // new SimpleRegister((byte) 0x3D, (byte) 0x4A), // @@ -64,7 +64,7 @@ public void testReadLittleEndianLswMsw() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(// new FloatQuadruplewordElement(0).wordOrder(WordOrder.LSWMSW).byteOrder(LITTLE_ENDIAN), // DOUBLE); - sut.element.setInputRegisters(new Register[] { // + sut.element.setInputValue(new Register[] { // new SimpleRegister((byte) 0x93, (byte) 0x40), // new SimpleRegister((byte) 0x3D, (byte) 0x4A), // new SimpleRegister((byte) 0xA3, (byte) 0x70), // @@ -79,7 +79,7 @@ public void testWriteLittleEndianLswMsw() throws IllegalArgumentException, Opene new FloatQuadruplewordElement(0).wordOrder(LSWMSW).byteOrder(LITTLE_ENDIAN), // LONG); sut.channel.setNextWriteValueFromObject(1234.56F); - var registers = sut.element.getNextWriteValueAndReset().get(); + var registers = sut.element.getNextWriteValueAndReset(); assertArrayEquals(new byte[] { (byte) 0x93, (byte) 0x40 }, registers[0].toBytes()); assertArrayEquals(new byte[] { (byte) 0x00, (byte) 0x48 }, registers[1].toBytes()); assertArrayEquals(new byte[] { (byte) 0x00, (byte) 0x00 }, registers[2].toBytes()); diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/ModbusTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/ModbusTest.java index 5f6c7132757..1cd22e47efc 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/ModbusTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/ModbusTest.java @@ -19,7 +19,7 @@ import io.openems.edge.common.channel.internal.AbstractReadChannel; import io.openems.edge.common.taskmanager.Priority; -public class ModbusTest, CHANNEL extends Channel> +public class ModbusTest> extends DummyModbusComponent { private static final String CHANNEL_ID = "CHANNEL"; @@ -39,26 +39,25 @@ private ModbusTest(ELEMENT element, BiFunction taskFact this.getModbusProtocol().addTask(this.task); } - public static class FC1ReadCoils> - extends ModbusTest { - public FC1ReadCoils(CoilElement element, OpenemsType openemsType) throws OpenemsException { + public static class FC1ReadCoils> + extends ModbusTest { + public FC1ReadCoils(ELEMENT element, OpenemsType openemsType) throws OpenemsException { super(element, // (startAddress, priority) -> new FC1ReadCoilsTask(startAddress, priority, element), // AccessMode.READ_ONLY, openemsType); } } - public static class FC5WriteCoil, CHANNEL extends WriteChannel> + public static class FC5WriteCoil> extends ModbusTest { - @SuppressWarnings("unchecked") - public FC5WriteCoil(ModbusCoilElement element, OpenemsType openemsType) throws OpenemsException { - super((ELEMENT) element, // + public FC5WriteCoil(ELEMENT element, OpenemsType openemsType) throws OpenemsException { + super(element, // (startAddress, priority) -> new FC5WriteCoilTask(startAddress, element), // AccessMode.READ_WRITE, openemsType); } } - public static class FC3ReadRegisters, CHANNEL extends AbstractReadChannel> + public static class FC3ReadRegisters> extends ModbusTest { public FC3ReadRegisters(ELEMENT element, OpenemsType openemsType) throws OpenemsException { super(element, // @@ -67,7 +66,7 @@ public FC3ReadRegisters(ELEMENT element, OpenemsType openemsType) throws Openems } } - public static class FC6WriteRegister, CHANNEL extends WriteChannel> + public static class FC6WriteRegister, CHANNEL extends WriteChannel> extends ModbusTest { public FC6WriteRegister(ELEMENT element, OpenemsType openemsType) throws OpenemsException { super(element, // @@ -76,7 +75,7 @@ public FC6WriteRegister(ELEMENT element, OpenemsType openemsType) throws Openems } } - public static class FC16WriteRegisters, CHANNEL extends WriteChannel> + public static class FC16WriteRegisters> extends ModbusTest { public FC16WriteRegisters(ELEMENT element, OpenemsType openemsType) throws OpenemsException { super(element, // @@ -84,5 +83,4 @@ public FC16WriteRegisters(ELEMENT element, OpenemsType openemsType) throws Opene AccessMode.READ_WRITE, openemsType); } } - } \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/SignedDoublewordElementTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/SignedDoublewordElementTest.java index 58dc591703f..eee2d847bb3 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/SignedDoublewordElementTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/SignedDoublewordElementTest.java @@ -21,7 +21,7 @@ public void testReadBigEndianMswLsw() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(// new SignedDoublewordElement(0), // LONG); - sut.element.setInputRegisters(new Register[] { // + sut.element.setInputValue(new Register[] { // new SimpleRegister((byte) 0xAB, (byte) 0xCD), // new SimpleRegister((byte) 0x12, (byte) 0x34) // }); @@ -33,7 +33,7 @@ public void testReadBigEndianLswMsw() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(// new SignedDoublewordElement(0).wordOrder(LSWMSW), // LONG); - sut.element.setInputRegisters(new Register[] { // + sut.element.setInputValue(new Register[] { // new SimpleRegister((byte) 0xAB, (byte) 0xCD), // new SimpleRegister((byte) 0x12, (byte) 0x34) // }); @@ -46,7 +46,7 @@ public void testReadLittleEndianMswLsw() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(// new SignedDoublewordElement(0).byteOrder(LITTLE_ENDIAN), // LONG); - sut.element.setInputRegisters(new Register[] { // + sut.element.setInputValue(new Register[] { // new SimpleRegister((byte) 0xAB, (byte) 0xCD), // new SimpleRegister((byte) 0x12, (byte) 0x34) // }); @@ -58,7 +58,7 @@ public void testReadLittleEndianlswMsw() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(// new SignedDoublewordElement(0).byteOrder(LITTLE_ENDIAN).wordOrder(LSWMSW), // LONG); - sut.element.setInputRegisters(new Register[] { // + sut.element.setInputValue(new Register[] { // new SimpleRegister((byte) 0xAB, (byte) 0xCD), // new SimpleRegister((byte) 0x12, (byte) 0x34) // }); @@ -71,7 +71,7 @@ public void testWrite() throws IllegalArgumentException, OpenemsNamedException { new SignedDoublewordElement(0), // LONG); sut.channel.setNextWriteValueFromObject(0x1234_ABCDL); - var registers = sut.element.getNextWriteValueAndReset().get(); + var registers = sut.element.getNextWriteValueAndReset(); assertArrayEquals(new byte[] { (byte) 0x12, (byte) 0x34 }, registers[0].toBytes()); assertArrayEquals(new byte[] { (byte) 0xAB, (byte) 0xCD }, registers[1].toBytes()); } diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/SignedQuadruplewordElementTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/SignedQuadruplewordElementTest.java index 5d7b2fcdb5d..e58e3c7369f 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/SignedQuadruplewordElementTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/SignedQuadruplewordElementTest.java @@ -21,7 +21,7 @@ public void testReadBigEndianMswLsw() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(// new SignedQuadruplewordElement(0), // LONG); - sut.element.setInputRegisters(new Register[] { // + sut.element.setInputValue(new Register[] { // new SimpleRegister((byte) 0x01, (byte) 0x23), // new SimpleRegister((byte) 0x45, (byte) 0x67), // new SimpleRegister((byte) 0x89, (byte) 0xAB), // @@ -35,7 +35,7 @@ public void testReadBigEndianLswMsw() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(// new SignedQuadruplewordElement(0).wordOrder(LSWMSW), // LONG); - sut.element.setInputRegisters(new Register[] { // + sut.element.setInputValue(new Register[] { // new SimpleRegister((byte) 0x01, (byte) 0x23), // new SimpleRegister((byte) 0x45, (byte) 0x67), // new SimpleRegister((byte) 0x89, (byte) 0xAB), // @@ -49,7 +49,7 @@ public void testReadLittleEndianMswLsw() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(// new SignedQuadruplewordElement(0).byteOrder(LITTLE_ENDIAN), // LONG); - sut.element.setInputRegisters(new Register[] { // + sut.element.setInputValue(new Register[] { // new SimpleRegister((byte) 0x01, (byte) 0x23), // new SimpleRegister((byte) 0x45, (byte) 0x67), // new SimpleRegister((byte) 0x89, (byte) 0xAB), // @@ -63,7 +63,7 @@ public void testReadLittleEndianLswMsw() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(// new SignedQuadruplewordElement(0).wordOrder(LSWMSW).byteOrder(LITTLE_ENDIAN), // LONG); - sut.element.setInputRegisters(new Register[] { // + sut.element.setInputValue(new Register[] { // new SimpleRegister((byte) 0x01, (byte) 0x23), // new SimpleRegister((byte) 0x45, (byte) 0x67), // new SimpleRegister((byte) 0x89, (byte) 0xAB), // @@ -78,7 +78,7 @@ public void testWriteLittleEndianMswLsw() throws IllegalArgumentException, Opene new SignedQuadruplewordElement(0).byteOrder(LITTLE_ENDIAN), // LONG); sut.channel.setNextWriteValueFromObject(0x2301_6745_AB89_EFCDL); - var registers = sut.element.getNextWriteValueAndReset().get(); + var registers = sut.element.getNextWriteValueAndReset(); assertArrayEquals(new byte[] { (byte) 0xCD, (byte) 0xEF }, registers[0].toBytes()); assertArrayEquals(new byte[] { (byte) 0x89, (byte) 0xAB }, registers[1].toBytes()); assertArrayEquals(new byte[] { (byte) 0x45, (byte) 0x67 }, registers[2].toBytes()); @@ -91,7 +91,7 @@ public void testWriteLittleEndianLswMsw() throws IllegalArgumentException, Opene new SignedQuadruplewordElement(0).wordOrder(LSWMSW).byteOrder(LITTLE_ENDIAN), // LONG); sut.channel.setNextWriteValueFromObject(0x2301_6745_AB89_EFCDL); - var registers = sut.element.getNextWriteValueAndReset().get(); + var registers = sut.element.getNextWriteValueAndReset(); assertArrayEquals(new byte[] { (byte) 0x01, (byte) 0x23 }, registers[0].toBytes()); assertArrayEquals(new byte[] { (byte) 0x45, (byte) 0x67 }, registers[1].toBytes()); assertArrayEquals(new byte[] { (byte) 0x89, (byte) 0xAB }, registers[2].toBytes()); diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/SignedWordElementTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/SignedWordElementTest.java index 71b9db39159..0805d6c17b3 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/SignedWordElementTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/SignedWordElementTest.java @@ -18,14 +18,14 @@ public class SignedWordElementTest { @Test public void testReadBigEndian() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(new SignedWordElement(0), SHORT); - sut.element.setInputRegisters(new Register[] { new SimpleRegister((byte) 0xAB, (byte) 0xCD) }); + sut.element.setInputValue(new Register[] { new SimpleRegister((byte) 0xAB, (byte) 0xCD) }); assertEquals((short) 0xABCD, sut.channel.getNextValue().get()); } @Test public void testReadLittleEndian() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(new SignedWordElement(0).byteOrder(LITTLE_ENDIAN), SHORT); - sut.element.setInputRegisters(new Register[] { new SimpleRegister((byte) 0xAB, (byte) 0xCD) }); + sut.element.setInputValue(new Register[] { new SimpleRegister((byte) 0xAB, (byte) 0xCD) }); assertEquals((short) 0xCDAB, sut.channel.getNextValue().get()); } @@ -33,7 +33,7 @@ public void testReadLittleEndian() throws OpenemsException { public void testWriteBigEndian() throws IllegalArgumentException, OpenemsNamedException { var sut = new ModbusTest.FC6WriteRegister<>(new SignedWordElement(0), SHORT); sut.channel.setNextWriteValueFromObject(0x1234); - var registers = sut.element.getNextWriteValueAndReset().get(); + var registers = sut.element.getNextWriteValueAndReset(); assertArrayEquals(new byte[] { (byte) 0x12, (byte) 0x34 }, registers[0].toBytes()); } @@ -41,7 +41,7 @@ public void testWriteBigEndian() throws IllegalArgumentException, OpenemsNamedEx public void testWriteLittleEndian() throws IllegalArgumentException, OpenemsNamedException { var sut = new ModbusTest.FC6WriteRegister<>(new SignedWordElement(0).byteOrder(LITTLE_ENDIAN), SHORT); sut.channel.setNextWriteValueFromObject(0x1234); - var registers = sut.element.getNextWriteValueAndReset().get(); + var registers = sut.element.getNextWriteValueAndReset(); assertArrayEquals(new byte[] { (byte) 0x34, (byte) 0x12 }, registers[0].toBytes()); } } diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/StringWordElementTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/StringWordElementTest.java index 3deab6932c9..80881998f4f 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/StringWordElementTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/StringWordElementTest.java @@ -1,7 +1,9 @@ package io.openems.edge.bridge.modbus.api.element; import static io.openems.common.types.OpenemsType.STRING; +import static io.openems.edge.bridge.modbus.api.element.WordOrder.LSWMSW; import static java.nio.ByteOrder.LITTLE_ENDIAN; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import org.junit.Test; @@ -15,10 +17,10 @@ public class StringWordElementTest { @Test - public void testReadBigEndian() throws OpenemsException { + public void testReadBigEndianMswLsw() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(// new StringWordElement(0, 4), STRING); - sut.element.setInputRegisters(new Register[] { // + sut.element.setInputValue(new Register[] { // new SimpleRegister((byte) 0x4F, (byte) 0x70), // new SimpleRegister((byte) 0x65, (byte) 0x6E), // new SimpleRegister((byte) 0x45, (byte) 0x4D), // @@ -28,27 +30,57 @@ public void testReadBigEndian() throws OpenemsException { } @Test - public void testReadLittleEndian() throws OpenemsException { + public void testReadBigEndianLswMsw() throws OpenemsException { + var sut = new ModbusTest.FC3ReadRegisters<>(// + new StringWordElement(0, 4).wordOrder(LSWMSW), STRING); + sut.element.setInputValue(new Register[] { // + new SimpleRegister((byte) 0x4F, (byte) 0x70), // + new SimpleRegister((byte) 0x65, (byte) 0x6E), // + new SimpleRegister((byte) 0x45, (byte) 0x4D), // + new SimpleRegister((byte) 0x53, (byte) 0x00) // + }); + assertEquals("S EMenOp", sut.channel.getNextValue().get()); + } + + @Test + public void testReadLittleEndianMswLsw() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(// - // TODO byteOrder is not applied new StringWordElement(0, 4).byteOrder(LITTLE_ENDIAN), STRING); - sut.element.setInputRegisters(new Register[] { // + sut.element.setInputValue(new Register[] { // new SimpleRegister((byte) 0x4F, (byte) 0x70), // new SimpleRegister((byte) 0x65, (byte) 0x6E), // new SimpleRegister((byte) 0x45, (byte) 0x4D), // new SimpleRegister((byte) 0x53, (byte) 0x00) // }); - assertEquals("OpenEMS", sut.channel.getNextValue().get()); + assertEquals("pOneME S", sut.channel.getNextValue().get()); + } + + @Test + public void testReadLittleEndianLswMsw() throws OpenemsException { + var sut = new ModbusTest.FC3ReadRegisters<>(// + new StringWordElement(0, 4).wordOrder(LSWMSW).byteOrder(LITTLE_ENDIAN), STRING); + sut.element.setInputValue(new Register[] { // + new SimpleRegister((byte) 0x4F, (byte) 0x70), // + new SimpleRegister((byte) 0x65, (byte) 0x6E), // + new SimpleRegister((byte) 0x45, (byte) 0x4D), // + new SimpleRegister((byte) 0x53, (byte) 0x00) // + }); + assertEquals("SMEnepO", sut.channel.getNextValue().get()); } @Test public void testWriteBigEndian() throws IllegalArgumentException, OpenemsNamedException { - // TODO StringWordElement._setNextWriteValue is non-functional - // var sut = new ModbusTest.FC6WriteRegister<>(new StringWordElement(0, 10), - // STRING); - // sut.channel.setNextWriteValueFromObject("OpenEMS "); - // var registers = sut.element.getNextWriteValueAndReset().get(); - // assertArrayEquals(new byte[] { (byte) 0x4F, (byte) 0x70 }, - // registers[0].toBytes()); + var sut = new ModbusTest.FC16WriteRegisters<>(new StringWordElement(0, 6), STRING); + sut.channel.setNextWriteValueFromObject("OpenEMS"); + var registers = sut.element.getNextWriteValueAndReset(); + for (var reg : registers) { + System.out.println(Integer.toHexString(reg.toBytes()[0]) + " " + Integer.toHexString(reg.toBytes()[1])); + } + assertEquals(6, registers.length); + assertArrayEquals(new byte[] { (byte) 0x4F, (byte) 0x70 }, registers[0].toBytes()); + assertArrayEquals(new byte[] { (byte) 0x65, (byte) 0x6E }, registers[1].toBytes()); + assertArrayEquals(new byte[] { (byte) 0x45, (byte) 0x4D }, registers[2].toBytes()); + assertArrayEquals(new byte[] { (byte) 0x53, (byte) 0x00 }, registers[3].toBytes()); + assertArrayEquals(new byte[] { (byte) 0x00, (byte) 0x00 }, registers[4].toBytes()); } } diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/UnsignedDoublewordElementTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/UnsignedDoublewordElementTest.java index 0ea6451ad06..906eec21361 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/UnsignedDoublewordElementTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/UnsignedDoublewordElementTest.java @@ -21,7 +21,7 @@ public void testReadBigEndianMswLsw() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(// new UnsignedDoublewordElement(0), // LONG); - sut.element.setInputRegisters(new Register[] { // + sut.element.setInputValue(new Register[] { // new SimpleRegister((byte) 0xAB, (byte) 0xCD), // new SimpleRegister((byte) 0x12, (byte) 0x34) // }); @@ -33,7 +33,7 @@ public void testReadBigEndianLswMsw() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(// new UnsignedDoublewordElement(0).wordOrder(LSWMSW), // LONG); - sut.element.setInputRegisters(new Register[] { // + sut.element.setInputValue(new Register[] { // new SimpleRegister((byte) 0xAB, (byte) 0xCD), // new SimpleRegister((byte) 0x12, (byte) 0x34) // }); @@ -45,7 +45,7 @@ public void testReadLittleEndianMswLsw() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(// new UnsignedDoublewordElement(0).byteOrder(LITTLE_ENDIAN), // LONG); - sut.element.setInputRegisters(new Register[] { // + sut.element.setInputValue(new Register[] { // new SimpleRegister((byte) 0xAB, (byte) 0xCD), // new SimpleRegister((byte) 0x12, (byte) 0x34) // }); @@ -57,7 +57,7 @@ public void testReadLittleEndianLswMsw() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(// new UnsignedDoublewordElement(0).wordOrder(LSWMSW).byteOrder(LITTLE_ENDIAN), // LONG); - sut.element.setInputRegisters(new Register[] { // + sut.element.setInputValue(new Register[] { // new SimpleRegister((byte) 0xAB, (byte) 0xCD), // new SimpleRegister((byte) 0x12, (byte) 0x34) // }); @@ -70,7 +70,7 @@ public void testWriteBigEndianMswLsw() throws IllegalArgumentException, OpenemsN new UnsignedDoublewordElement(0), // LONG); sut.channel.setNextWriteValueFromObject(0xABCD1234L); - var registers = sut.element.getNextWriteValueAndReset().get(); + var registers = sut.element.getNextWriteValueAndReset(); assertArrayEquals(new byte[] { (byte) 0xAB, (byte) 0xCD }, registers[0].toBytes()); assertArrayEquals(new byte[] { (byte) 0x12, (byte) 0x34 }, registers[1].toBytes()); } @@ -81,7 +81,7 @@ public void testWriteBigEndianLswMsw() throws IllegalArgumentException, OpenemsN new UnsignedDoublewordElement(0).wordOrder(LSWMSW), // LONG); sut.channel.setNextWriteValueFromObject(0xABCD1234L); - var registers = sut.element.getNextWriteValueAndReset().get(); + var registers = sut.element.getNextWriteValueAndReset(); assertArrayEquals(new byte[] { (byte) 0x12, (byte) 0x34 }, registers[0].toBytes()); assertArrayEquals(new byte[] { (byte) 0xAB, (byte) 0xCD }, registers[1].toBytes()); } @@ -92,7 +92,7 @@ public void testWriteLittleEndianMswLsw() throws IllegalArgumentException, Opene new UnsignedDoublewordElement(0).byteOrder(LITTLE_ENDIAN), // LONG); sut.channel.setNextWriteValueFromObject(0xABCD1234L); - var registers = sut.element.getNextWriteValueAndReset().get(); + var registers = sut.element.getNextWriteValueAndReset(); assertArrayEquals(new byte[] { (byte) 0x34, (byte) 0x12 }, registers[0].toBytes()); assertArrayEquals(new byte[] { (byte) 0xCD, (byte) 0xAB }, registers[1].toBytes()); } @@ -103,7 +103,7 @@ public void testWriteLittleEndianLswMsw() throws IllegalArgumentException, Opene new UnsignedDoublewordElement(0).wordOrder(LSWMSW).byteOrder(LITTLE_ENDIAN), // LONG); sut.channel.setNextWriteValueFromObject(0xABCD1234L); - var registers = sut.element.getNextWriteValueAndReset().get(); + var registers = sut.element.getNextWriteValueAndReset(); assertArrayEquals(new byte[] { (byte) 0xCD, (byte) 0xAB }, registers[0].toBytes()); assertArrayEquals(new byte[] { (byte) 0x34, (byte) 0x12 }, registers[1].toBytes()); } diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/UnsignedQuadruplewordElementTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/UnsignedQuadruplewordElementTest.java index 2e79f64bc00..324b1443a73 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/UnsignedQuadruplewordElementTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/UnsignedQuadruplewordElementTest.java @@ -22,7 +22,7 @@ public void testReadBigEndianMswLsw() throws OpenemsException { new UnsignedQuadruplewordElement(0), // LONG); sut.element.debug(); - sut.element.setInputRegisters(new Register[] { // + sut.element.setInputValue(new Register[] { // new SimpleRegister((byte) 0x01, (byte) 0x23), // new SimpleRegister((byte) 0x45, (byte) 0x67), // new SimpleRegister((byte) 0x89, (byte) 0xAB), // @@ -36,7 +36,7 @@ public void testReadBigEndianLswMsw() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(// new UnsignedQuadruplewordElement(0).wordOrder(LSWMSW), // LONG); - sut.element.setInputRegisters(new Register[] { // + sut.element.setInputValue(new Register[] { // new SimpleRegister((byte) 0x01, (byte) 0x23), // new SimpleRegister((byte) 0x45, (byte) 0x67), // new SimpleRegister((byte) 0x89, (byte) 0xAB), // @@ -50,7 +50,7 @@ public void testReadLittleEndianMswLsw() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(// new UnsignedQuadruplewordElement(0).byteOrder(LITTLE_ENDIAN), // LONG); - sut.element.setInputRegisters(new Register[] { // + sut.element.setInputValue(new Register[] { // new SimpleRegister((byte) 0x01, (byte) 0x23), // new SimpleRegister((byte) 0x45, (byte) 0x67), // new SimpleRegister((byte) 0x89, (byte) 0xAB), // @@ -64,7 +64,7 @@ public void testReadLittleEndianLswMsw() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(// new UnsignedQuadruplewordElement(0).wordOrder(LSWMSW).byteOrder(LITTLE_ENDIAN), // LONG); - sut.element.setInputRegisters(new Register[] { // + sut.element.setInputValue(new Register[] { // new SimpleRegister((byte) 0x01, (byte) 0x23), // new SimpleRegister((byte) 0x45, (byte) 0x67), // new SimpleRegister((byte) 0x89, (byte) 0xAB), // @@ -79,7 +79,7 @@ public void testWriteLittleEndianMswLsw() throws IllegalArgumentException, Opene new UnsignedQuadruplewordElement(0).byteOrder(LITTLE_ENDIAN), // LONG); sut.channel.setNextWriteValueFromObject(0x2301_6745_AB89_EFCDL); - var registers = sut.element.getNextWriteValueAndReset().get(); + var registers = sut.element.getNextWriteValueAndReset(); assertArrayEquals(new byte[] { (byte) 0xCD, (byte) 0xEF }, registers[0].toBytes()); assertArrayEquals(new byte[] { (byte) 0x89, (byte) 0xAB }, registers[1].toBytes()); assertArrayEquals(new byte[] { (byte) 0x45, (byte) 0x67 }, registers[2].toBytes()); @@ -92,7 +92,7 @@ public void testWriteLittleEndianLswMsw() throws IllegalArgumentException, Opene new UnsignedQuadruplewordElement(0).wordOrder(LSWMSW).byteOrder(LITTLE_ENDIAN), // LONG); sut.channel.setNextWriteValueFromObject(0x2301_6745_AB89_EFCDL); - var registers = sut.element.getNextWriteValueAndReset().get(); + var registers = sut.element.getNextWriteValueAndReset(); assertArrayEquals(new byte[] { (byte) 0x01, (byte) 0x23 }, registers[0].toBytes()); assertArrayEquals(new byte[] { (byte) 0x45, (byte) 0x67 }, registers[1].toBytes()); assertArrayEquals(new byte[] { (byte) 0x89, (byte) 0xAB }, registers[2].toBytes()); diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/UnsignedWordElementTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/UnsignedWordElementTest.java index 0908638eba7..ddb4011842b 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/UnsignedWordElementTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/UnsignedWordElementTest.java @@ -6,11 +6,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import java.util.Optional; - import org.junit.Test; -import com.ghgande.j2mod.modbus.procimg.InputRegister; import com.ghgande.j2mod.modbus.procimg.Register; import com.ghgande.j2mod.modbus.procimg.SimpleRegister; @@ -23,14 +20,14 @@ public class UnsignedWordElementTest { @Test public void testReadBigEndian() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(new UnsignedWordElement(0), INTEGER); - sut.element.setInputRegisters(new Register[] { new SimpleRegister((byte) 0xAB, (byte) 0xCD) }); + sut.element.setInputValue(new Register[] { new SimpleRegister((byte) 0xAB, (byte) 0xCD) }); assertEquals(0xABCD, sut.channel.getNextValue().get()); } @Test public void testReadLittleEndian() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(new UnsignedWordElement(0).byteOrder(LITTLE_ENDIAN), INTEGER); - sut.element.setInputRegisters(new Register[] { new SimpleRegister((byte) 0xAB, (byte) 0xCD) }); + sut.element.setInputValue(new Register[] { new SimpleRegister((byte) 0xAB, (byte) 0xCD) }); assertEquals(0xCDAB, sut.channel.getNextValue().get()); } @@ -38,7 +35,7 @@ public void testReadLittleEndian() throws OpenemsException { public void testWriteBigEndian() throws IllegalArgumentException, OpenemsNamedException { var sut = new ModbusTest.FC6WriteRegister<>(new UnsignedWordElement(0), INTEGER); sut.channel.setNextWriteValueFromObject(0xBCDE); - var registers = sut.element.getNextWriteValueAndReset().get(); + var registers = sut.element.getNextWriteValueAndReset(); assertArrayEquals(new byte[] { (byte) 0xBC, (byte) 0xDE }, registers[0].toBytes()); } @@ -46,7 +43,7 @@ public void testWriteBigEndian() throws IllegalArgumentException, OpenemsNamedEx public void testWriteLittleEndian() throws IllegalArgumentException, OpenemsNamedException { var sut = new ModbusTest.FC6WriteRegister<>(new UnsignedWordElement(0).byteOrder(LITTLE_ENDIAN), INTEGER); sut.channel.setNextWriteValueFromObject(0xBCDE); - var registers = sut.element.getNextWriteValueAndReset().get(); + var registers = sut.element.getNextWriteValueAndReset(); assertArrayEquals(new byte[] { (byte) 0xDE, (byte) 0xBC }, registers[0].toBytes()); } @@ -54,7 +51,7 @@ public void testWriteLittleEndian() throws IllegalArgumentException, OpenemsName public void testInvalidate() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(new UnsignedWordElement(0), INTEGER); var bridge = (AbstractModbusBridge) sut.getBridgeModbus(); - sut.element._setInputRegisters(new Register[] { new SimpleRegister((byte) 0xAB, (byte) 0xCD) }); + sut.element.setInputValue(new Register[] { new SimpleRegister((byte) 0xAB, (byte) 0xCD) }); assertEquals(0xABCD, sut.channel.getNextValue().get()); sut.element.invalidate(bridge); // invalidValueCounter = 1 assertEquals(0xABCD, sut.channel.getNextValue().get()); @@ -62,22 +59,22 @@ public void testInvalidate() throws OpenemsException { assertNull(sut.channel.getNextValue().get()); } - @Test(expected = OpenemsException.class) + @Test(expected = IllegalArgumentException.class) public void testReadWrongLength() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(new UnsignedWordElement(0), INTEGER); - sut.element.setInputRegisters(new Register[3]); + sut.element.setInputValue(new Register[3]); } - @Test(expected = NullPointerException.class) + @Test public void testInputValueNull() throws OpenemsException { var sut = new ModbusTest.FC3ReadRegisters<>(new UnsignedWordElement(0), INTEGER); - sut.element.setInputRegisters((InputRegister[]) null); - // TODO this should not throw a NPE + sut.element.setInputValue(null); + assertNull(sut.channel.getNextValue().get()); } @Test public void testWriteNone() throws IllegalArgumentException, OpenemsNamedException { var sut = new ModbusTest.FC6WriteRegister<>(new UnsignedWordElement(0), INTEGER); - assertEquals(Optional.empty(), sut.element.getNextWriteValueAndReset()); + assertNull(sut.element.getNextWriteValueAndReset()); } } diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC16WriteRegistersTaskTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC16WriteRegistersTaskTest.java index c7961ba2c3b..b3d1fa83a7f 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC16WriteRegistersTaskTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC16WriteRegistersTaskTest.java @@ -2,8 +2,6 @@ import static org.junit.Assert.assertEquals; -import java.util.Optional; - import org.junit.Test; import com.ghgande.j2mod.modbus.msg.WriteMultipleRegistersRequest; @@ -26,13 +24,13 @@ public void testMergeWriteRegisters() throws OpenemsException, IllegalArgumentEx final var element1 = new UnsignedWordElement(1); final var element2 = new UnsignedWordElement(2); final var element3 = new UnsignedWordElement(3); - var elements = new ModbusElement[] { element0, element1, element2, element3 }; + var elements = new ModbusElement[] { element0, element1, element2, element3 }; // Has Hole (no value for element2) - element0.setNextWriteValue(Optional.empty()); - element1.setNextWriteValue(Optional.of(100)); - element2.setNextWriteValue(Optional.empty()); - element3.setNextWriteValue(Optional.of(300)); + element0.setNextWriteValue(null); + element1.setNextWriteValue(100); + element2.setNextWriteValue(null); + element3.setNextWriteValue(300); { var result = FC16WriteRegistersTask.mergeWriteRegisters(elements, (message) -> System.out.println(message)); @@ -45,10 +43,10 @@ public void testMergeWriteRegisters() throws OpenemsException, IllegalArgumentEx } // Has NO Hole (all values set) - element0.setNextWriteValue(Optional.of(100)); - element1.setNextWriteValue(Optional.of(200)); - element2.setNextWriteValue(Optional.of(300)); - element3.setNextWriteValue(Optional.of(400)); + element0.setNextWriteValue(100); + element1.setNextWriteValue(200); + element2.setNextWriteValue(300); + element3.setNextWriteValue(400); { var result = FC16WriteRegistersTask.mergeWriteRegisters(elements, (message) -> System.out.println(message)); diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC1ReadCoilsTaskTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC1ReadCoilsTaskTest.java index 6dde298f3cf..9bccb7feb3f 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC1ReadCoilsTaskTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC1ReadCoilsTaskTest.java @@ -9,7 +9,7 @@ import io.openems.common.exceptions.OpenemsException; import io.openems.edge.bridge.modbus.DummyModbusComponent; import io.openems.edge.bridge.modbus.api.LogVerbosity; -import io.openems.edge.bridge.modbus.api.element.DummyCoilElement; +import io.openems.edge.bridge.modbus.api.element.CoilElement; import io.openems.edge.common.taskmanager.Priority; public class FC1ReadCoilsTaskTest { @@ -17,7 +17,7 @@ public class FC1ReadCoilsTaskTest { @Test public void testToLogMessage() throws OpenemsException { var component = new DummyModbusComponent(); - var task = new FC1ReadCoilsTask(10, Priority.HIGH, new DummyCoilElement(10), new DummyCoilElement(11)); + var task = new FC1ReadCoilsTask(10, Priority.HIGH, new CoilElement(10), new CoilElement(11)); task.setParent(component); var request = task.createModbusRequest(); var response = (ReadCoilsResponse) request.getResponse(); diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC2ReadInputsTaskTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC2ReadInputsTaskTest.java index 6f6599a512a..10dd5f3585e 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC2ReadInputsTaskTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC2ReadInputsTaskTest.java @@ -9,7 +9,7 @@ import io.openems.common.exceptions.OpenemsException; import io.openems.edge.bridge.modbus.DummyModbusComponent; import io.openems.edge.bridge.modbus.api.LogVerbosity; -import io.openems.edge.bridge.modbus.api.element.DummyCoilElement; +import io.openems.edge.bridge.modbus.api.element.CoilElement; import io.openems.edge.common.taskmanager.Priority; public class FC2ReadInputsTaskTest { @@ -17,7 +17,7 @@ public class FC2ReadInputsTaskTest { @Test public void testToLogMessage() throws OpenemsException { var component = new DummyModbusComponent(); - var task = new FC2ReadInputsTask(10, Priority.HIGH, new DummyCoilElement(10), new DummyCoilElement(11)); + var task = new FC2ReadInputsTask(10, Priority.HIGH, new CoilElement(10), new CoilElement(11)); task.setParent(component); var request = task.createModbusRequest(); var response = (ReadInputDiscretesResponse) request.getResponse(); diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC5WriteCoilTaskTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC5WriteCoilTaskTest.java index f1522cbb49b..ce520d17128 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC5WriteCoilTaskTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC5WriteCoilTaskTest.java @@ -10,14 +10,14 @@ import io.openems.common.exceptions.OpenemsException; import io.openems.edge.bridge.modbus.DummyModbusComponent; import io.openems.edge.bridge.modbus.api.LogVerbosity; -import io.openems.edge.bridge.modbus.api.element.DummyCoilElement; +import io.openems.edge.bridge.modbus.api.element.CoilElement; public class FC5WriteCoilTaskTest { @Test public void testToLogMessage() throws OpenemsException { var component = new DummyModbusComponent(); - var task = new FC5WriteCoilTask(20, new DummyCoilElement(20)); + var task = new FC5WriteCoilTask(20, new CoilElement(20)); task.setParent(component); var request = new WriteCoilRequest(20, true); var response = (WriteCoilResponse) request.getResponse(); diff --git a/io.openems.edge.edge2edge/src/io/openems/edge/edge2edge/common/AbstractEdge2Edge.java b/io.openems.edge.edge2edge/src/io/openems/edge/edge2edge/common/AbstractEdge2Edge.java index c02f596b8ef..e3a922e5a58 100644 --- a/io.openems.edge.edge2edge/src/io/openems/edge/edge2edge/common/AbstractEdge2Edge.java +++ b/io.openems.edge.edge2edge/src/io/openems/edge/edge2edge/common/AbstractEdge2Edge.java @@ -228,8 +228,8 @@ private void mapRemoteChannels(TreeMap natureStartAddresses) thr .map(method -> method.apply(this.remoteAccessMode)) // .collect(Collectors.toUnmodifiableList()); - var readElements = new ArrayDeque>(); - var writeElements = new ArrayDeque>(); + var readElements = new ArrayDeque(); + var writeElements = new ArrayDeque(); for (var entry : natureStartAddresses.entrySet()) { var natureStartAddress = entry.getKey(); var hash = entry.getValue(); @@ -253,11 +253,11 @@ private void mapRemoteChannels(TreeMap natureStartAddresses) thr // Fill gaps with DummyModbusElements var lastElement = readElements.peekLast(); if (lastElement != null) { - var gap = address - lastElement.getStartAddress() - lastElement.getLength(); + var gap = address - lastElement.startAddress - lastElement.length; if (gap > 0) { readElements.add(new DummyRegisterElement(// - lastElement.getStartAddress() + lastElement.getLength(), - lastElement.getStartAddress() + lastElement.getLength() + gap - 1)); + lastElement.startAddress + lastElement.length, + lastElement.startAddress + lastElement.length + gap - 1)); } } @@ -267,7 +267,9 @@ private void mapRemoteChannels(TreeMap natureStartAddresses) thr } else { var onUpdateCallback = this.getOnUpdateCallback(modbusSlaveNatureTable, record); if (onUpdateCallback != null) { - m(element).build().onUpdateCallback(value -> onUpdateCallback.accept(value)); + // This is guaranteed to work because of sealed abstract classes + ((AbstractModbusElement) m(element).build()) + .onUpdateCallback(value -> onUpdateCallback.accept(value)); } } @@ -294,16 +296,16 @@ private void mapRemoteChannels(TreeMap natureStartAddresses) thr */ { var length = 0; - var taskElements = new ArrayDeque>(); + var taskElements = new ArrayDeque(); var element = readElements.pollFirst(); while (element != null) { - if (length + element.getLength() > 126 /* limit of j2mod */) { + if (length + element.length > 126 /* limit of j2mod */) { this.addReadTask(taskElements); length = 0; taskElements.clear(); } taskElements.add(element); - length += element.getLength(); + length += element.length; element = readElements.pollFirst(); } this.addReadTask(taskElements); @@ -313,12 +315,11 @@ private void mapRemoteChannels(TreeMap natureStartAddresses) thr * Add the Write-Task(s) */ { - var taskElements = new ArrayDeque>(); + var taskElements = new ArrayDeque(); var element = writeElements.pollFirst(); while (element != null) { var lastElement = taskElements.peekLast(); - if (lastElement != null - && (lastElement.getStartAddress() + lastElement.getLength() < element.getStartAddress())) { + if (lastElement != null && (lastElement.startAddress + lastElement.length < element.startAddress)) { // Found gap this.addWriteTask(taskElements); taskElements.clear(); @@ -358,10 +359,10 @@ protected abstract io.openems.edge.common.channel.ChannelId getWriteChannelId( * Create ModbusElement from type and address. * * @param type the {@link ModbusType} - * @param address the address of the {@link ModbusElement} - * @return the {@link ModbusElement} + * @param address the address of the {@link AbstractModbusElement} + * @return the {@link AbstractModbusElement} */ - private static AbstractModbusElement generateModbusElement(ModbusType type, int address) { + private static ModbusElement generateModbusElement(ModbusType type, int address) { switch (type) { case ENUM16: case UINT16: @@ -387,10 +388,10 @@ protected abstract io.openems.edge.common.channel.ChannelId getWriteChannelId( *

  • Adds only if queue is not empty * * - * @param elements the {@link ModbusElement}s + * @param elements the {@link AbstractModbusElement}s * @throws OpenemsException on error */ - private void addReadTask(Deque> elements) throws OpenemsException { + private void addReadTask(Deque elements) throws OpenemsException { if (elements.isEmpty()) { return; } @@ -402,23 +403,23 @@ private void addReadTask(Deque> elements) throws OpenemsExcepti } this.modbusProtocol.addTask(// new FC3ReadRegistersTask(// - elements.peekFirst().getStartAddress(), Priority.HIGH, + elements.peekFirst().startAddress, Priority.HIGH, elements.toArray(new ModbusElement[elements.size()]))); } /** * Adds a Write-Task with ModbusElements. * - * @param elements the {@link ModbusElement}s + * @param elements the {@link AbstractModbusElement}s * @throws OpenemsException on error */ - private void addWriteTask(Deque> elements) throws OpenemsException { + private void addWriteTask(Deque elements) throws OpenemsException { if (elements.isEmpty()) { return; } this.modbusProtocol.addTask(// new FC16WriteRegistersTask(// - elements.peekFirst().getStartAddress(), elements.toArray(new ModbusElement[elements.size()]))); + elements.peekFirst().startAddress, elements.toArray(new ModbusElement[elements.size()]))); } /** diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/AbstractGoodWe.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/AbstractGoodWe.java index 56bdc7bec47..375135c1cfa 100644 --- a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/AbstractGoodWe.java +++ b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/AbstractGoodWe.java @@ -21,10 +21,10 @@ import io.openems.edge.bridge.modbus.api.ElementToChannelConverter; import io.openems.edge.bridge.modbus.api.ModbusProtocol; import io.openems.edge.bridge.modbus.api.ModbusUtils; -import io.openems.edge.bridge.modbus.api.element.AbstractModbusElement; import io.openems.edge.bridge.modbus.api.element.BitsWordElement; import io.openems.edge.bridge.modbus.api.element.DummyRegisterElement; import io.openems.edge.bridge.modbus.api.element.FloatDoublewordElement; +import io.openems.edge.bridge.modbus.api.element.ModbusElement; import io.openems.edge.bridge.modbus.api.element.SignedDoublewordElement; import io.openems.edge.bridge.modbus.api.element.SignedWordElement; import io.openems.edge.bridge.modbus.api.element.StringWordElement; @@ -1688,7 +1688,7 @@ private void handleDspVersion5(ModbusProtocol protocol) throws OpenemsException ); } - protected AbstractModbusElement getSocModbusElement(int address) throws NotImplementedException { + protected ModbusElement getSocModbusElement(int address) throws NotImplementedException { if (this instanceof HybridEss) { return m(SymmetricEss.ChannelId.SOC, new UnsignedWordElement(address), new ElementToChannelConverter( // element -> channel diff --git a/io.openems.edge.io.wago/src/io/openems/edge/wago/Fieldbus4xxDI.java b/io.openems.edge.io.wago/src/io/openems/edge/wago/Fieldbus4xxDI.java index ed20673b22c..d2a194051f1 100644 --- a/io.openems.edge.io.wago/src/io/openems/edge/wago/Fieldbus4xxDI.java +++ b/io.openems.edge.io.wago/src/io/openems/edge/wago/Fieldbus4xxDI.java @@ -1,7 +1,7 @@ package io.openems.edge.wago; import io.openems.common.channel.PersistencePriority; -import io.openems.edge.bridge.modbus.api.element.ModbusCoilElement; +import io.openems.edge.bridge.modbus.api.element.CoilElement; import io.openems.edge.common.channel.BooleanDoc; import io.openems.edge.common.channel.BooleanReadChannel; @@ -9,16 +9,16 @@ public class Fieldbus4xxDI extends FieldbusModule { private static final String ID_TEMPLATE = "DIGITAL_INPUT_M"; - private final ModbusCoilElement[] inputCoil0Elements; - private final ModbusCoilElement[] inputCoil512Elements = {}; - private final ModbusCoilElement[] outputCoil512Elements = {}; + private final CoilElement[] inputCoil0Elements; + private final CoilElement[] inputCoil512Elements = {}; + private final CoilElement[] outputCoil512Elements = {}; private final BooleanReadChannel[] readChannels; public Fieldbus4xxDI(IoWagoImpl parent, int moduleCount, int coilOffset0, int channelsCount) { var id = ID_TEMPLATE + moduleCount; this.readChannels = new BooleanReadChannel[channelsCount]; - this.inputCoil0Elements = new ModbusCoilElement[channelsCount]; + this.inputCoil0Elements = new CoilElement[channelsCount]; for (var i = 0; i < channelsCount; i++) { var doc = new BooleanDoc(); doc.persistencePriority(PersistencePriority.MEDIUM); @@ -37,17 +37,17 @@ public String getName() { } @Override - public ModbusCoilElement[] getInputCoil0Elements() { + public CoilElement[] getInputCoil0Elements() { return this.inputCoil0Elements; } @Override - public ModbusCoilElement[] getInputCoil512Elements() { + public CoilElement[] getInputCoil512Elements() { return this.inputCoil512Elements; } @Override - public ModbusCoilElement[] getOutputCoil512Elements() { + public CoilElement[] getOutputCoil512Elements() { return this.outputCoil512Elements; } diff --git a/io.openems.edge.io.wago/src/io/openems/edge/wago/Fieldbus523RO1Ch.java b/io.openems.edge.io.wago/src/io/openems/edge/wago/Fieldbus523RO1Ch.java index 5e9f7d06060..cf8c5515878 100644 --- a/io.openems.edge.io.wago/src/io/openems/edge/wago/Fieldbus523RO1Ch.java +++ b/io.openems.edge.io.wago/src/io/openems/edge/wago/Fieldbus523RO1Ch.java @@ -2,8 +2,7 @@ import io.openems.common.channel.AccessMode; import io.openems.common.channel.PersistencePriority; -import io.openems.edge.bridge.modbus.api.element.DummyCoilElement; -import io.openems.edge.bridge.modbus.api.element.ModbusCoilElement; +import io.openems.edge.bridge.modbus.api.element.CoilElement; import io.openems.edge.common.channel.BooleanDoc; import io.openems.edge.common.channel.BooleanReadChannel; import io.openems.edge.common.channel.BooleanWriteChannel; @@ -13,9 +12,9 @@ public class Fieldbus523RO1Ch extends FieldbusModule { private static final String ID_TEMPLATE = "RELAY_M"; - private final ModbusCoilElement[] inputCoil0Elements; - private final ModbusCoilElement[] inputCoil512Elements; - private final ModbusCoilElement[] outputCoil512Elements; + private final CoilElement[] inputCoil0Elements; + private final CoilElement[] inputCoil512Elements; + private final CoilElement[] outputCoil512Elements; private final BooleanReadChannel[] readChannels; public Fieldbus523RO1Ch(IoWagoImpl parent, int moduleCount, int coilOffset0, int coilOffset512) { @@ -37,17 +36,17 @@ public Fieldbus523RO1Ch(IoWagoImpl parent, int moduleCount, int coilOffset0, int } this.readChannels = new BooleanReadChannel[] { channel1, channel2 }; - this.inputCoil0Elements = new ModbusCoilElement[] { // + this.inputCoil0Elements = new CoilElement[] { // parent.createModbusCoilElement(channel2.channelId(), coilOffset0), // - new DummyCoilElement(coilOffset0 + 1) // + new CoilElement(coilOffset0 + 1) // }; - this.inputCoil512Elements = new ModbusCoilElement[] { // + this.inputCoil512Elements = new CoilElement[] { // parent.createModbusCoilElement(channel1.channelId(), coilOffset512), // - new DummyCoilElement(coilOffset512 + 1), // + new CoilElement(coilOffset512 + 1), // }; - this.outputCoil512Elements = new ModbusCoilElement[] { // + this.outputCoil512Elements = new CoilElement[] { // parent.createModbusCoilElement(channel1.channelId(), coilOffset512), // - new DummyCoilElement(coilOffset512 + 1) // + new CoilElement(coilOffset512 + 1) // }; } @@ -57,17 +56,17 @@ public String getName() { } @Override - public ModbusCoilElement[] getInputCoil0Elements() { + public CoilElement[] getInputCoil0Elements() { return this.inputCoil0Elements; } @Override - public ModbusCoilElement[] getInputCoil512Elements() { + public CoilElement[] getInputCoil512Elements() { return this.inputCoil512Elements; } @Override - public ModbusCoilElement[] getOutputCoil512Elements() { + public CoilElement[] getOutputCoil512Elements() { return this.outputCoil512Elements; } diff --git a/io.openems.edge.io.wago/src/io/openems/edge/wago/Fieldbus5xxDO.java b/io.openems.edge.io.wago/src/io/openems/edge/wago/Fieldbus5xxDO.java index f979b38f261..532b7a0bc7e 100644 --- a/io.openems.edge.io.wago/src/io/openems/edge/wago/Fieldbus5xxDO.java +++ b/io.openems.edge.io.wago/src/io/openems/edge/wago/Fieldbus5xxDO.java @@ -2,7 +2,7 @@ import io.openems.common.channel.AccessMode; import io.openems.common.channel.PersistencePriority; -import io.openems.edge.bridge.modbus.api.element.ModbusCoilElement; +import io.openems.edge.bridge.modbus.api.element.CoilElement; import io.openems.edge.common.channel.BooleanDoc; import io.openems.edge.common.channel.BooleanReadChannel; import io.openems.edge.common.channel.BooleanWriteChannel; @@ -11,17 +11,17 @@ public class Fieldbus5xxDO extends FieldbusModule { private static final String ID_TEMPLATE = "DIGITAL_OUTPUT_M"; - private final ModbusCoilElement[] inputCoil0Elements = {}; - private final ModbusCoilElement[] inputCoil512Elements; - private final ModbusCoilElement[] outputCoil512Elements; + private final CoilElement[] inputCoil0Elements = {}; + private final CoilElement[] inputCoil512Elements; + private final CoilElement[] outputCoil512Elements; private final BooleanReadChannel[] readChannels; public Fieldbus5xxDO(IoWagoImpl parent, int moduleCount, int coilOffset512, int channelsCount) { var id = ID_TEMPLATE + moduleCount; this.readChannels = new BooleanReadChannel[channelsCount]; - this.inputCoil512Elements = new ModbusCoilElement[channelsCount]; - this.outputCoil512Elements = new ModbusCoilElement[channelsCount]; + this.inputCoil512Elements = new CoilElement[channelsCount]; + this.outputCoil512Elements = new CoilElement[channelsCount]; for (var i = 0; i < channelsCount; i++) { var doc = new BooleanDoc() // @@ -43,17 +43,17 @@ public String getName() { } @Override - public ModbusCoilElement[] getInputCoil0Elements() { + public CoilElement[] getInputCoil0Elements() { return this.inputCoil0Elements; } @Override - public ModbusCoilElement[] getInputCoil512Elements() { + public CoilElement[] getInputCoil512Elements() { return this.inputCoil512Elements; } @Override - public ModbusCoilElement[] getOutputCoil512Elements() { + public CoilElement[] getOutputCoil512Elements() { return this.outputCoil512Elements; } diff --git a/io.openems.edge.io.wago/src/io/openems/edge/wago/FieldbusModule.java b/io.openems.edge.io.wago/src/io/openems/edge/wago/FieldbusModule.java index 64386066ec9..23abca063a4 100644 --- a/io.openems.edge.io.wago/src/io/openems/edge/wago/FieldbusModule.java +++ b/io.openems.edge.io.wago/src/io/openems/edge/wago/FieldbusModule.java @@ -1,6 +1,6 @@ package io.openems.edge.wago; -import io.openems.edge.bridge.modbus.api.element.ModbusCoilElement; +import io.openems.edge.bridge.modbus.api.element.CoilElement; import io.openems.edge.common.channel.BooleanReadChannel; import io.openems.edge.common.channel.Channel; @@ -18,7 +18,7 @@ public abstract class FieldbusModule { * * @return the array; empty for no input coils */ - public abstract ModbusCoilElement[] getInputCoil0Elements(); + public abstract CoilElement[] getInputCoil0Elements(); /** * Gets the {@link ModbusCoilElement} for the input coils starting at address @@ -26,7 +26,7 @@ public abstract class FieldbusModule { * * @return the array; empty for no input coils */ - public abstract ModbusCoilElement[] getInputCoil512Elements(); + public abstract CoilElement[] getInputCoil512Elements(); /** * Gets the {@link ModbusCoilElement} for the output coils starting at address @@ -34,7 +34,7 @@ public abstract class FieldbusModule { * * @return the array; empty for no output coils */ - public abstract ModbusCoilElement[] getOutputCoil512Elements(); + public abstract CoilElement[] getOutputCoil512Elements(); /** * Gets the Channels of the {@link FieldbusModule}. diff --git a/io.openems.edge.io.wago/src/io/openems/edge/wago/IoWagoImpl.java b/io.openems.edge.io.wago/src/io/openems/edge/wago/IoWagoImpl.java index 957b36cb7e6..250029d4998 100644 --- a/io.openems.edge.io.wago/src/io/openems/edge/wago/IoWagoImpl.java +++ b/io.openems.edge.io.wago/src/io/openems/edge/wago/IoWagoImpl.java @@ -46,7 +46,6 @@ import io.openems.edge.bridge.modbus.api.ModbusComponent; import io.openems.edge.bridge.modbus.api.ModbusProtocol; import io.openems.edge.bridge.modbus.api.element.CoilElement; -import io.openems.edge.bridge.modbus.api.element.ModbusCoilElement; import io.openems.edge.bridge.modbus.api.task.FC1ReadCoilsTask; import io.openems.edge.bridge.modbus.api.task.FC5WriteCoilTask; import io.openems.edge.common.channel.BooleanReadChannel; @@ -238,23 +237,23 @@ protected List parseXml(Document doc) { * @throws OpenemsException on error */ protected void createProtocolFromModules(List modules) throws OpenemsException { - List readCoilElements0 = new ArrayList<>(); - List readCoilElements512 = new ArrayList<>(); + List readCoilElements0 = new ArrayList<>(); + List readCoilElements512 = new ArrayList<>(); for (FieldbusModule module : modules) { Collections.addAll(readCoilElements0, module.getInputCoil0Elements()); Collections.addAll(readCoilElements512, module.getInputCoil512Elements()); - for (ModbusCoilElement element : module.getOutputCoil512Elements()) { - var writeCoilTask = new FC5WriteCoilTask(element.getStartAddress(), element); + for (CoilElement element : module.getOutputCoil512Elements()) { + var writeCoilTask = new FC5WriteCoilTask(element.startAddress, element); this.protocol.addTask(writeCoilTask); } } if (!readCoilElements0.isEmpty()) { - this.protocol.addTask(new FC1ReadCoilsTask(0, Priority.LOW, - readCoilElements0.stream().toArray(ModbusCoilElement[]::new))); + this.protocol.addTask( + new FC1ReadCoilsTask(0, Priority.LOW, readCoilElements0.stream().toArray(CoilElement[]::new))); } if (!readCoilElements512.isEmpty()) { - this.protocol.addTask(new FC1ReadCoilsTask(512, Priority.LOW, - readCoilElements512.stream().toArray(ModbusCoilElement[]::new))); + this.protocol.addTask( + new FC1ReadCoilsTask(512, Priority.LOW, readCoilElements512.stream().toArray(CoilElement[]::new))); } } diff --git a/io.openems.edge.io.weidmueller/src/io/openems/edge/io/weidmueller/IoWeidmuellerUr20Impl.java b/io.openems.edge.io.weidmueller/src/io/openems/edge/io/weidmueller/IoWeidmuellerUr20Impl.java index e42337819e6..288bc173545 100644 --- a/io.openems.edge.io.weidmueller/src/io/openems/edge/io/weidmueller/IoWeidmuellerUr20Impl.java +++ b/io.openems.edge.io.weidmueller/src/io/openems/edge/io/weidmueller/IoWeidmuellerUr20Impl.java @@ -121,8 +121,7 @@ private void activate(ComponentContext context, Config config) throws OpenemsExc this.modules.get(module).add(channel); element.bit(i, channelId); } - tasks = new Task[] { - new FC3ReadRegistersTask(element.getStartAddress(), Priority.HIGH, element) }; + tasks = new Task[] { new FC3ReadRegistersTask(element.startAddress, Priority.HIGH, element) }; break; } @@ -134,17 +133,16 @@ private void activate(ComponentContext context, Config config) throws OpenemsExc var channel = (BooleanWriteChannel) this.addChannel(channelId); var outputElement = new CoilElement(nextOutputCoil++); - var channelMetaInfoBit = new ChannelMetaInfoBitReadAndWrite(inputElement.getStartAddress(), - i, PROCESS_DATA_OUTPUT_BASE_COIL, outputElement.getStartAddress()); + var channelMetaInfoBit = new ChannelMetaInfoBitReadAndWrite(inputElement.startAddress, i, + PROCESS_DATA_OUTPUT_BASE_COIL, outputElement.startAddress); writeChannels.add(channel); - myTasks.add(new FC5WriteCoilTask(outputElement.getStartAddress(), + myTasks.add(new FC5WriteCoilTask(outputElement.startAddress, m(channelId, outputElement, channelMetaInfoBit))); this.modules.get(module).add(channel); inputElement.bit(i, channelId, channelMetaInfoBit); } - myTasks.add( - new FC3ReadRegistersTask(inputElement.getStartAddress(), Priority.HIGH, inputElement)); + myTasks.add(new FC3ReadRegistersTask(inputElement.startAddress, Priority.HIGH, inputElement)); tasks = myTasks.toArray(Task[]::new); break; } From 4530b1f17aa50f2bc11a0ac2437506eb1c6ae638 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Aug 2023 22:35:07 +0200 Subject: [PATCH 05/32] Bump @types/uuid from 9.0.1 to 9.0.2 in /ui (#2225) * Bump @types/uuid from 9.0.1 to 9.0.2 in /ui Bumps [@types/uuid](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/uuid) from 9.0.1 to 9.0.2. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/uuid) --- updated-dependencies: - dependency-name: "@types/uuid" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Cleanup --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Stefan Feilmeier --- ui/package-lock.json | 14 +++++++------- ui/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index 5c51e20a8f1..60f9eef11d7 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -52,7 +52,7 @@ "@types/jasmine": "~4.3.2", "@types/jasminewd2": "~2.0.10", "@types/node": "^20.2.5", - "@types/uuid": "^9.0.1", + "@types/uuid": "^9.0.2", "@typescript-eslint/eslint-plugin": "^6.2.0", "@typescript-eslint/parser": "6.2.0", "eslint": "^8.41.0", @@ -4185,9 +4185,9 @@ } }, "node_modules/@types/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.2.tgz", + "integrity": "sha512-kNnC1GFBLuhImSnV7w4njQkUiJi0ZXUycu1rUaouPqiKlXkh77JKgdRnTAp1x5eBwcIwbtI+3otwzuIDEuDoxQ==", "dev": true }, "node_modules/@types/ws": { @@ -19027,9 +19027,9 @@ } }, "@types/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.2.tgz", + "integrity": "sha512-kNnC1GFBLuhImSnV7w4njQkUiJi0ZXUycu1rUaouPqiKlXkh77JKgdRnTAp1x5eBwcIwbtI+3otwzuIDEuDoxQ==", "dev": true }, "@types/ws": { diff --git a/ui/package.json b/ui/package.json index 74794a73533..bfdac09bbae 100644 --- a/ui/package.json +++ b/ui/package.json @@ -47,7 +47,7 @@ "@types/jasmine": "~4.3.2", "@types/jasminewd2": "~2.0.10", "@types/node": "^20.2.5", - "@types/uuid": "^9.0.1", + "@types/uuid": "^9.0.2", "@typescript-eslint/eslint-plugin": "^6.2.0", "@typescript-eslint/parser": "6.2.0", "eslint": "^8.41.0", From 9147b8b0781df6ff716789d649d26c9862c29f70 Mon Sep 17 00:00:00 2001 From: Joop <6662540+JoopAue@users.noreply.github.com> Date: Fri, 4 Aug 2023 20:32:47 +0800 Subject: [PATCH 06/32] AbstractChannelListenerManager: clean up unused listeners after deactivate() (#2303) As discussed in issue: https://github.com/OpenEMS/openems/issues/2302 --- .../edge/common/channel/AbstractChannelListenerManager.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/io.openems.edge.common/src/io/openems/edge/common/channel/AbstractChannelListenerManager.java b/io.openems.edge.common/src/io/openems/edge/common/channel/AbstractChannelListenerManager.java index 4b85ee6b477..d7823313ca6 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/channel/AbstractChannelListenerManager.java +++ b/io.openems.edge.common/src/io/openems/edge/common/channel/AbstractChannelListenerManager.java @@ -54,10 +54,12 @@ public synchronized void deactivate() { Channel channel = listener.component.channel(listener.channelId); channel.removeOnSetNextValueCallback(listener.callback); } + this.onSetNextValueListeners.clear(); for (OnChangeListener listener : this.onChangeListeners) { Channel channel = listener.component.channel(listener.channelId); channel.removeOnChangeCallback(listener.callback); } + this.onChangeListeners.clear(); } /** From e4b69bc0710c92b13fc23689180dffe903e8ee59 Mon Sep 17 00:00:00 2001 From: Lukas Rieger <73471197+lukasrgr@users.noreply.github.com> Date: Mon, 7 Aug 2023 22:17:33 +0200 Subject: [PATCH 07/32] UI: Add redirection for 'OpenEMS Edge' as backend (#2305) Adds a check for OpenEMS Edge as backend, to be getting redirected to live-view Solves this community Issue: https://community.openems.io/t/2023-8-ui-bleibt-nach-login-hangen/1837/4 --- ui/src/app/index/index.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/app/index/index.component.ts b/ui/src/app/index/index.component.ts index 190589c1915..52431a080a3 100644 --- a/ui/src/app/index/index.component.ts +++ b/ui/src/app/index/index.component.ts @@ -132,7 +132,7 @@ export class IndexComponent implements OnInit, OnDestroy { let edgeIds = Object.keys(metadata.edges); this.onlyOneEdgeAvailable = edgeIds.length <= 1; this.noEdges = edgeIds.length === 0; - this.loggedInUserCanInstall = Role.isAtLeast(metadata.user.globalRole, "installer"); + this.loggedInUserCanInstall = environment.backend === 'OpenEMS Backend' && Role.isAtLeast(metadata.user.globalRole, "installer"); // Forward directly to device page, if // - Direct local access to Edge From 4d0531367afd0ce0efd04017a75b8be551dac9c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Aug 2023 08:30:48 +0200 Subject: [PATCH 08/32] Bump com.squareup.okio:okio-jvm from 3.4.0 to 3.5.0 in /cnf (#2307) * Bump com.squareup.okio:okio-jvm from 3.4.0 to 3.5.0 in /cnf Bumps [com.squareup.okio:okio-jvm](https://github.com/square/okio) from 3.4.0 to 3.5.0. - [Changelog](https://github.com/square/okio/blob/master/CHANGELOG.md) - [Commits](https://github.com/square/okio/compare/parent-3.4.0...parent-3.5.0) --- updated-dependencies: - dependency-name: com.squareup.okio:okio-jvm dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Update bndrun --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Stefan Feilmeier --- cnf/pom.xml | 2 +- io.openems.backend.application/BackendApp.bndrun | 2 +- io.openems.edge.application/EdgeApp.bndrun | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cnf/pom.xml b/cnf/pom.xml index b1a496af81c..91f0e9f6bef 100644 --- a/cnf/pom.xml +++ b/cnf/pom.xml @@ -60,7 +60,7 @@ com.squareup.okio okio-jvm - 3.4.0 + 3.5.0 diff --git a/io.openems.backend.application/BackendApp.bndrun b/io.openems.backend.application/BackendApp.bndrun index 3ec547a58af..2cd50d2361c 100644 --- a/io.openems.backend.application/BackendApp.bndrun +++ b/io.openems.backend.application/BackendApp.bndrun @@ -62,7 +62,7 @@ com.google.gson;version='[2.10.1,2.10.2)',\ com.google.guava;version='[32.1.2,32.1.3)',\ com.google.guava.failureaccess;version='[1.0.1,1.0.2)',\ - com.squareup.okio;version='[3.4.0,3.4.1)',\ + com.squareup.okio;version='[3.5.0,3.5.1)',\ com.zaxxer.HikariCP;version='[5.0.1,5.0.2)',\ io.openems.backend.alerting;version=snapshot,\ io.openems.backend.application;version=snapshot,\ diff --git a/io.openems.edge.application/EdgeApp.bndrun b/io.openems.edge.application/EdgeApp.bndrun index a944aa1c73d..82d7fcd8ebf 100644 --- a/io.openems.edge.application/EdgeApp.bndrun +++ b/io.openems.edge.application/EdgeApp.bndrun @@ -186,7 +186,7 @@ com.google.gson;version='[2.10.1,2.10.2)',\ com.google.guava;version='[32.1.2,32.1.3)',\ com.google.guava.failureaccess;version='[1.0.1,1.0.2)',\ - com.squareup.okio;version='[3.4.0,3.4.1)',\ + com.squareup.okio;version='[3.5.0,3.5.1)',\ com.sun.jna;version='[5.13.0,5.13.1)',\ io.openems.common;version=snapshot,\ io.openems.edge.application;version=snapshot,\ From 533e8b393dc2f4ae102f24f3f520cace288d086b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Aug 2023 09:05:37 +0200 Subject: [PATCH 09/32] Bump org.checkerframework:checker-qual from 3.36.0 to 3.37.0 in /cnf (#2308) * Bump org.checkerframework:checker-qual from 3.36.0 to 3.37.0 in /cnf Bumps [org.checkerframework:checker-qual](https://github.com/typetools/checker-framework) from 3.36.0 to 3.37.0. - [Release notes](https://github.com/typetools/checker-framework/releases) - [Changelog](https://github.com/typetools/checker-framework/blob/master/docs/CHANGELOG.md) - [Commits](https://github.com/typetools/checker-framework/compare/checker-framework-3.36.0...checker-framework-3.37.0) --- updated-dependencies: - dependency-name: org.checkerframework:checker-qual dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Update bndrun --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Stefan Feilmeier --- cnf/pom.xml | 2 +- io.openems.backend.application/BackendApp.bndrun | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cnf/pom.xml b/cnf/pom.xml index 91f0e9f6bef..f80dc9dd508 100644 --- a/cnf/pom.xml +++ b/cnf/pom.xml @@ -261,7 +261,7 @@ org.checkerframework checker-qual - 3.36.0 + 3.37.0 org.dhatim diff --git a/io.openems.backend.application/BackendApp.bndrun b/io.openems.backend.application/BackendApp.bndrun index 2cd50d2361c..10333e90c4f 100644 --- a/io.openems.backend.application/BackendApp.bndrun +++ b/io.openems.backend.application/BackendApp.bndrun @@ -58,7 +58,7 @@ -runbundles: \ Java-WebSocket;version='[1.5.4,1.5.5)',\ - checker-qual;version='[3.36.0,3.36.1)',\ + checker-qual;version='[3.37.0,3.37.1)',\ com.google.gson;version='[2.10.1,2.10.2)',\ com.google.guava;version='[32.1.2,32.1.3)',\ com.google.guava.failureaccess;version='[1.0.1,1.0.2)',\ From c6c7edf66df89cceb555e7c84f26c4436c08c8de Mon Sep 17 00:00:00 2001 From: Christian Lehne <51822163+clehne@users.noreply.github.com> Date: Tue, 8 Aug 2023 09:08:17 +0200 Subject: [PATCH 10/32] Backend Timedata: prepare new data notification types and utilities (#2298) * Split TimestampedDatanotification into AggregatedDataNotification, TimestampedDataNotification and ResendDataNotification. Added/enhanced some utilities classes. * Update Timedata and Timemanager interface to support * TimestampedDataNotification * AggregatedDataNotification * ResendDataNotification * Refactored and optimized TimedataInfluxdb --- .../common/edgewebsocket/EdgeCache.java | 144 ++-- .../common/edgewebsocket/EdgeWebsocket.java | 4 +- .../common/timedata/BackendTimedata.java | 65 ++ .../timedata/InternalTimedataException.java | 21 + .../backend/common/timedata/Timedata.java | 18 +- .../common/timedata/TimedataManager.java | 18 +- .../common/edgewebsocket/EdgeCacheTest.java | 17 +- .../timedatamanager/TimedataManagerImpl.java | 152 ++++- .../edgewebsocket/EdgeWebsocketImpl.java | 30 +- .../backend/edgewebsocket/OnNotification.java | 56 +- .../backend/timedata/dummy/TimedataDummy.java | 43 +- .../backend/timedata/influx/Function.java | 7 - .../timedata/influx/TimedataInfluxDb.java | 186 ++++-- .../timescaledb/TimedataTimescaleDb.java | 21 +- io.openems.edge.timedata.influxdb/bnd.bnd | 5 +- .../edge/timedata/influxdb/Config.java | 3 + .../influxdb/TimedataInfluxDbImpl.java | 19 +- .../edge/timedata/influxdb/MyConfig.java | 11 + .../influxdb/TimedataInfluxDbImplTest.java | 1 + .../influxdb/AbstractMergePointsWorker.java | 123 ++++ .../openems/shared/influxdb/DbDataUtils.java | 157 +++++ .../influxdb/ForceMergePointsWorker.java | 22 + .../shared/influxdb/InfluxConnector.java | 288 +++++++- .../shared/influxdb/MergePointsWorker.java | 85 +-- .../influxdb/SafeMergePointsWorker.java | 50 ++ .../shared/influxdb/proxy/FluxProxy.java | 169 ++++- .../shared/influxdb/proxy/InfluxQlProxy.java | 617 ++++++++++++++++-- .../shared/influxdb/proxy/QueryProxy.java | 227 ++++++- .../shared/influxdb/proxy/package-info.java | 3 + .../influxdb/proxy/QueryBuilderTest.java | 47 +- 30 files changed, 2177 insertions(+), 432 deletions(-) create mode 100644 io.openems.backend.common/src/io/openems/backend/common/timedata/BackendTimedata.java create mode 100644 io.openems.backend.common/src/io/openems/backend/common/timedata/InternalTimedataException.java delete mode 100644 io.openems.backend.timedata.influx/src/io/openems/backend/timedata/influx/Function.java create mode 100644 io.openems.shared.influxdb/src/io/openems/shared/influxdb/AbstractMergePointsWorker.java create mode 100644 io.openems.shared.influxdb/src/io/openems/shared/influxdb/DbDataUtils.java create mode 100644 io.openems.shared.influxdb/src/io/openems/shared/influxdb/ForceMergePointsWorker.java create mode 100644 io.openems.shared.influxdb/src/io/openems/shared/influxdb/SafeMergePointsWorker.java create mode 100644 io.openems.shared.influxdb/src/io/openems/shared/influxdb/proxy/package-info.java diff --git a/io.openems.backend.common/src/io/openems/backend/common/edgewebsocket/EdgeCache.java b/io.openems.backend.common/src/io/openems/backend/common/edgewebsocket/EdgeCache.java index 8bc41272252..e733127dc73 100644 --- a/io.openems.backend.common/src/io/openems/backend/common/edgewebsocket/EdgeCache.java +++ b/io.openems.backend.common/src/io/openems/backend/common/edgewebsocket/EdgeCache.java @@ -2,20 +2,75 @@ import java.util.HashMap; import java.util.Map; -import java.util.Map.Entry; +import java.util.Set; import java.util.SortedMap; +import java.util.TreeMap; +import java.util.TreeSet; import com.google.gson.JsonElement; import com.google.gson.JsonNull; +import io.openems.common.jsonrpc.notification.AggregatedDataNotification; +import io.openems.common.jsonrpc.notification.TimestampedDataNotification; + public class EdgeCache { - /** - * The Timestamp of the data in the Cache. - */ - private long timestamp = 0L; + private final ChannelDataCache current = new ChannelDataCache(); + private final ChannelDataCache aggregated = new ChannelDataCache(); + + public static record Pair(A a, B b) { + + } + + private static class ChannelDataCache { + private long timestamp = 0L; + private final HashMap data = new HashMap<>(); + + /** + * Gets the channel value from cache. + * + * @param address the Channel-Address of the channel + * @return the value; {@link JsonNull} if it is not in cache + */ + public final JsonElement getChannelValue(String address) { + synchronized (this) { + return this.data.get(address); + } + } + + /** + * Updates the Cache. + * + * @param incomingDatas the incoming data + */ + public void update(SortedMap> incomingDatas) { + for (var entry : incomingDatas.entrySet()) { + var incomingTimestamp = entry.getKey(); + var incomingData = entry.getValue(); - private final HashMap data = new HashMap<>(); + // Check if cache should be applied + if (incomingTimestamp < this.timestamp) { + // Incoming data is older than cache -> do not apply cache + + } else { + // Incoming data is more recent than cache + + if (incomingTimestamp > this.timestamp + 15 * 60 * 1000) { + // Cache is not anymore valid (elder than 15 minutes) -> clear Cache + synchronized (this) { + this.data.clear(); + } + } + + // update cache + this.timestamp = incomingTimestamp; + synchronized (this) { + this.data.putAll(incomingData); + } + } + } + } + } /** * Gets the channel value from cache. @@ -24,46 +79,61 @@ public class EdgeCache { * @return the value; {@link JsonNull} if it is not in cache */ public final JsonElement getChannelValue(String address) { - synchronized (this) { - var result = this.data.get(address); - if (result == null) { - return JsonNull.INSTANCE; - } + final var result = this.current.getChannelValue(address); + if (result != null) { return result; } + final var aggregatedResult = this.aggregated.getChannelValue(address); + if (aggregatedResult != null) { + return aggregatedResult; + } + return JsonNull.INSTANCE; } /** - * Updates the Cache. + * Gets the channel values from cache. * - * @param incomingDatas the incoming data + * @param addresses the Channel-Addresses of the channels + * @return a) Map of Channel-Address to values ({@link JsonNull} if not in + * cache); b) Set of Channel-Addresses that are only available as + * aggregated data */ - public void update(SortedMap> incomingDatas) { - for (Entry> entry : incomingDatas.entrySet()) { - var incomingTimestamp = entry.getKey(); - var incomingData = entry.getValue(); - - // Check if cache should be applied - if (incomingTimestamp < this.timestamp) { - // Incoming data is older than cache -> do not apply cache - - } else { - // Incoming data is more recent than cache - - if (incomingTimestamp > this.timestamp + 15 * 60 * 1000) { - // Cache is not anymore valid (elder than 15 minutes) -> clear Cache - synchronized (this) { - this.data.clear(); - } - } - - // update cache - this.timestamp = incomingTimestamp; - synchronized (this) { - this.data.putAll(incomingData); - } + public final Pair, Set> getChannelValues(Set addresses) { + final var result = new TreeMap(); + final var aggregatedChannelValues = new TreeSet(); + for (var address : addresses) { + final var value = this.current.getChannelValue(address); + if (value != null) { + result.put(address, value); + continue; + } + final var aggregatedValue = this.aggregated.getChannelValue(address); + if (aggregatedValue != null) { + result.put(address, aggregatedValue); + aggregatedChannelValues.add(address); + continue; } + result.put(address, JsonNull.INSTANCE); } + return new Pair<>(result, aggregatedChannelValues); + } + + /** + * Updates the Cache. + * + * @param notification the incoming data + */ + public void updateCurrentData(TimestampedDataNotification notification) { + this.current.update(notification.getData().rowMap()); + } + + /** + * Updates the aggregated data cache. + * + * @param notification the incoming data + */ + public void updateAggregatedData(AggregatedDataNotification notification) { + this.aggregated.update(notification.getData().rowMap()); } } diff --git a/io.openems.backend.common/src/io/openems/backend/common/edgewebsocket/EdgeWebsocket.java b/io.openems.backend.common/src/io/openems/backend/common/edgewebsocket/EdgeWebsocket.java index b65be2c5d0b..dcb74728811 100644 --- a/io.openems.backend.common/src/io/openems/backend/common/edgewebsocket/EdgeWebsocket.java +++ b/io.openems.backend.common/src/io/openems/backend/common/edgewebsocket/EdgeWebsocket.java @@ -1,7 +1,7 @@ package io.openems.backend.common.edgewebsocket; -import java.util.Map; import java.util.Set; +import java.util.SortedMap; import java.util.concurrent.CompletableFuture; import org.osgi.annotation.versioning.ProviderType; @@ -62,6 +62,6 @@ public CompletableFuture handleSubscribeSystemLogRequest * @param channelAddresses The {@link ChannelAddress}es * @return the values; possibly {@link JsonNull} */ - public Map getChannelValues(String edgeId, Set channelAddresses); + public SortedMap getChannelValues(String edgeId, Set channelAddresses); } diff --git a/io.openems.backend.common/src/io/openems/backend/common/timedata/BackendTimedata.java b/io.openems.backend.common/src/io/openems/backend/common/timedata/BackendTimedata.java new file mode 100644 index 00000000000..af02d5eb35a --- /dev/null +++ b/io.openems.backend.common/src/io/openems/backend/common/timedata/BackendTimedata.java @@ -0,0 +1,65 @@ +package io.openems.backend.common.timedata; + +import java.time.ZonedDateTime; +import java.util.Set; +import java.util.SortedMap; + +import org.osgi.annotation.versioning.ProviderType; + +import com.google.gson.JsonElement; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.jsonrpc.notification.AggregatedDataNotification; +import io.openems.common.jsonrpc.notification.ResendDataNotification; +import io.openems.common.jsonrpc.notification.TimestampedDataNotification; +import io.openems.common.timedata.CommonTimedataService; +import io.openems.common.types.ChannelAddress; + +@ProviderType +public interface BackendTimedata extends CommonTimedataService { + + /** + * Sends the data points to the Timedata service. + * + * @param edgeId The unique Edge-ID + * @param data Table of timestamp (epoch in milliseconds), Channel-Address and + * the Channel value as JsonElement. Sorted by timestamp. + */ + public void write(String edgeId, TimestampedDataNotification data); + + /** + * Sends the data points to the Timedata service. + * + * @param edgeId The unique Edge-ID + * @param data Table of timestamp (epoch in milliseconds), Channel-Address and + * the Channel value as AggregatedData. Sorted by timestamp. + */ + public void write(String edgeId, AggregatedDataNotification data); + + /** + * Sends the data points to the Timedata service. + * + * @param edgeId The unique Edge-ID + * @param data Table of timestamp (epoch in milliseconds), Channel-Address and + * the Channel value as ResendData. Sorted by timestamp. + */ + public void write(String edgeId, ResendDataNotification data); + + /** + * Queries the latest values which are before the given {@link ZonedDateTime}. + * + * @param edgeId the id of the edge + * @param date the bounding date exclusive + * @param channels the channels + * @return the channel values + * @throws OpenemsNamedException on error + */ + public default SortedMap queryFirstValueBefore(// + final String edgeId, // + final ZonedDateTime date, // + final Set channels // + ) throws OpenemsNamedException { + return null; + } + +} diff --git a/io.openems.backend.common/src/io/openems/backend/common/timedata/InternalTimedataException.java b/io.openems.backend.common/src/io/openems/backend/common/timedata/InternalTimedataException.java new file mode 100644 index 00000000000..49fdd082f0f --- /dev/null +++ b/io.openems.backend.common/src/io/openems/backend/common/timedata/InternalTimedataException.java @@ -0,0 +1,21 @@ +package io.openems.backend.common.timedata; + +import io.openems.common.exceptions.OpenemsException; + +public class InternalTimedataException extends OpenemsException { + + private static final long serialVersionUID = -2037204231739569486L; + + public InternalTimedataException(String message) { + super(message); + } + + public InternalTimedataException(String message, Throwable cause) { + super(message, cause); + } + + public InternalTimedataException(Throwable cause) { + super(cause); + } + +} diff --git a/io.openems.backend.common/src/io/openems/backend/common/timedata/Timedata.java b/io.openems.backend.common/src/io/openems/backend/common/timedata/Timedata.java index f490c3ef3db..4d97a36699d 100644 --- a/io.openems.backend.common/src/io/openems/backend/common/timedata/Timedata.java +++ b/io.openems.backend.common/src/io/openems/backend/common/timedata/Timedata.java @@ -2,14 +2,8 @@ import org.osgi.annotation.versioning.ProviderType; -import com.google.common.collect.TreeBasedTable; -import com.google.gson.JsonElement; - -import io.openems.common.exceptions.OpenemsException; -import io.openems.common.timedata.CommonTimedataService; - @ProviderType -public interface Timedata extends CommonTimedataService { +public interface Timedata extends BackendTimedata { /** * Returns a unique ID for this OpenEMS component. @@ -18,14 +12,4 @@ public interface Timedata extends CommonTimedataService { */ public String id(); - /** - * Sends the data points to the Timedata service. - * - * @param edgeId The unique Edge-ID - * @param data Table of timestamp (epoch in milliseconds), Channel-Address and - * the Channel value as JsonElement. Sorted by timestamp. - * @throws OpenemsException on error - */ - public void write(String edgeId, TreeBasedTable data) throws OpenemsException; - } diff --git a/io.openems.backend.common/src/io/openems/backend/common/timedata/TimedataManager.java b/io.openems.backend.common/src/io/openems/backend/common/timedata/TimedataManager.java index e4f9f9c0cb5..1cee34c0182 100644 --- a/io.openems.backend.common/src/io/openems/backend/common/timedata/TimedataManager.java +++ b/io.openems.backend.common/src/io/openems/backend/common/timedata/TimedataManager.java @@ -2,23 +2,7 @@ import org.osgi.annotation.versioning.ProviderType; -import com.google.common.collect.TreeBasedTable; -import com.google.gson.JsonElement; - -import io.openems.common.exceptions.OpenemsException; -import io.openems.common.timedata.CommonTimedataService; - @ProviderType -public interface TimedataManager extends CommonTimedataService { - - /** - * Sends the data points to the Timedata services. - * - * @param edgeId The unique Edge-ID - * @param data Table of timestamp (epoch in milliseconds), Channel-Address and - * the Channel value as JsonElement. Sorted by timestamp. - * @throws OpenemsException on error - */ - public void write(String edgeId, TreeBasedTable data); +public interface TimedataManager extends BackendTimedata { } diff --git a/io.openems.backend.common/test/io/openems/backend/common/edgewebsocket/EdgeCacheTest.java b/io.openems.backend.common/test/io/openems/backend/common/edgewebsocket/EdgeCacheTest.java index 4265348ce31..08ffc66ef5a 100644 --- a/io.openems.backend.common/test/io/openems/backend/common/edgewebsocket/EdgeCacheTest.java +++ b/io.openems.backend.common/test/io/openems/backend/common/edgewebsocket/EdgeCacheTest.java @@ -4,16 +4,17 @@ import java.util.HashMap; import java.util.Map; -import java.util.SortedMap; import java.util.TreeMap; import org.junit.Test; +import com.google.common.collect.TreeBasedTable; import com.google.gson.JsonElement; import com.google.gson.JsonNull; import com.google.gson.JsonPrimitive; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.jsonrpc.notification.TimestampedDataNotification; import io.openems.common.types.ChannelAddress; public class EdgeCacheTest { @@ -28,34 +29,36 @@ public void test() throws OpenemsNamedException { var timestamp = 0L; var data1 = buildData(timestamp, CHANNEL1, "value1"); - cache.update(data1); + cache.updateCurrentData(data1); assertEquals("value1", cache.getChannelValue(CHANNEL1).getAsString()); // older than cache var data2 = buildData(timestamp - 1, CHANNEL1, "ignore"); - cache.update(data2); + cache.updateCurrentData(data2); assertEquals("value1", cache.getChannelValue(CHANNEL1).getAsString()); // normal operation var data3 = buildData(timestamp += 2 * 60 * 1000, CHANNEL2, "value2"); - cache.update(data3); + cache.updateCurrentData(data3); assertEquals("value1", cache.getChannelValue(CHANNEL1).getAsString()); assertEquals("value2", cache.getChannelValue(CHANNEL2).getAsString()); // invalidate cache var data4 = buildData(timestamp += 15 * 60 * 1000 + 1, CHANNEL3, "value3"); - cache.update(data4); + cache.updateCurrentData(data4); assertEquals(JsonNull.INSTANCE, cache.getChannelValue(CHANNEL1)); assertEquals(JsonNull.INSTANCE, cache.getChannelValue(CHANNEL2)); assertEquals("value3", cache.getChannelValue(CHANNEL3).getAsString()); } - private static SortedMap> buildData(long timestamp, String channel, String value) + private static TimestampedDataNotification buildData(long timestamp, String channel, String value) throws OpenemsNamedException { var data = new TreeMap>(); var map = new HashMap(); map.put(channel, (JsonElement) new JsonPrimitive(value)); data.put(timestamp, map); - return data; + var table = TreeBasedTable.create(); + table.put(timestamp, channel, new JsonPrimitive(value)); + return new TimestampedDataNotification(table); } } diff --git a/io.openems.backend.core/src/io/openems/backend/core/timedatamanager/TimedataManagerImpl.java b/io.openems.backend.core/src/io/openems/backend/core/timedatamanager/TimedataManagerImpl.java index 5225574f44e..cf185279360 100644 --- a/io.openems.backend.core/src/io/openems/backend/core/timedatamanager/TimedataManagerImpl.java +++ b/io.openems.backend.core/src/io/openems/backend/core/timedatamanager/TimedataManagerImpl.java @@ -3,14 +3,16 @@ import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Set; import java.util.SortedMap; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Modified; import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.ReferenceCardinality; import org.osgi.service.component.annotations.ReferencePolicy; @@ -20,14 +22,20 @@ import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableSortedSet; -import com.google.common.collect.TreeBasedTable; import com.google.gson.JsonElement; import io.openems.backend.common.component.AbstractOpenemsBackendComponent; +import io.openems.backend.common.timedata.InternalTimedataException; import io.openems.backend.common.timedata.Timedata; import io.openems.backend.common.timedata.TimedataManager; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.function.ThrowingFunction; +import io.openems.common.function.ThrowingTriConsumer; +import io.openems.common.jsonrpc.notification.AbstractDataNotification; +import io.openems.common.jsonrpc.notification.AggregatedDataNotification; +import io.openems.common.jsonrpc.notification.ResendDataNotification; +import io.openems.common.jsonrpc.notification.TimestampedDataNotification; import io.openems.common.timedata.Resolution; import io.openems.common.types.ChannelAddress; @@ -39,7 +47,8 @@ public class TimedataManagerImpl extends AbstractOpenemsBackendComponent implements TimedataManager { private final Logger log = LoggerFactory.getLogger(TimedataManagerImpl.class); - private final List _configTimedataIds; + + private List _configTimedataIds; private final List _rawTimedatas = new ArrayList<>(); private final AtomicReference> timedatas = new AtomicReference<>( ImmutableSortedSet.of()); @@ -62,17 +71,6 @@ protected synchronized void removeTimedata(Timedata timedata) { } } - @Activate - public TimedataManagerImpl(Config config) { - super("Core.TimedataManager"); - this._configTimedataIds = Arrays.asList(config.timedata_ids()); - this.updateSortedTimedatas(); - } - - @Deactivate - private void deactivate() { - } - private void updateSortedTimedatas() { // TODO add JUnit test synchronized (this._rawTimedatas) { @@ -103,58 +101,146 @@ private void updateSortedTimedatas() { } } + public TimedataManagerImpl() { + super("Core.TimedataManager"); + this._configTimedataIds = Collections.emptyList(); + } + + /** + * Activates the component. + * + * @param config the {@link Config Configuration} + */ + @Activate + @Modified + public void activate(Config config) { + this._configTimedataIds = Arrays.asList(config.timedata_ids()); + this.updateSortedTimedatas(); + } + + /** + * {@inheritDoc} + * + *

    + * The {@link TimedataManager} implementation never returns null, but throws an + * Exception instead + */ @Override public SortedMap> queryHistoricData(String edgeId, ZonedDateTime fromDate, ZonedDateTime toDate, Set channels, Resolution resolution) throws OpenemsNamedException { - for (var timedata : this.timedatas.get()) { - var data = timedata.queryHistoricData(edgeId, fromDate, toDate, channels, resolution); - if (data != null) { - return data; - } + final var value = this.firstOf(t -> t.queryHistoricData(edgeId, fromDate, toDate, channels, resolution)); + if (value != null) { + return value; } // no result this.logWarn(this.log, "No timedata result for 'queryHistoricData' on Edge=" + edgeId + "; FromDate=" + fromDate + "; ToDate=" + toDate + "; Channels=" + channels + "; Resolution=" + resolution); - return null; + throw new OpenemsException("Unable to query historic data. Result is null"); } + /** + * {@inheritDoc} + * + *

    + * The {@link TimedataManager} implementation never returns null, but throws an + * Exception instead + */ @Override public SortedMap queryHistoricEnergy(String edgeId, ZonedDateTime fromDate, ZonedDateTime toDate, Set channels) throws OpenemsNamedException { - for (var timedata : this.timedatas.get()) { - var data = timedata.queryHistoricEnergy(edgeId, fromDate, toDate, channels); - if (data != null) { - return data; - } + final var value = this.firstOf(t -> t.queryHistoricEnergy(edgeId, fromDate, toDate, channels)); + if (value != null) { + return value; } // no result this.logWarn(this.log, "No timedata result for 'queryHistoricEnergy' on Edge=" + edgeId + "; FromDate=" + fromDate + "; ToDate=" + toDate + "; Channels=" + channels); - return null; + throw new OpenemsException("Unable to query historic data. Result is null"); } + /** + * {@inheritDoc} + * + *

    + * The {@link TimedataManager} implementation never returns null, but throws an + * Exception instead + */ @Override public SortedMap> queryHistoricEnergyPerPeriod(String edgeId, ZonedDateTime fromDate, ZonedDateTime toDate, Set channels, Resolution resolution) throws OpenemsNamedException { - for (var timedata : this.timedatas.get()) { - var data = timedata.queryHistoricEnergyPerPeriod(edgeId, fromDate, toDate, channels, resolution); - if (data != null) { - return data; - } + final var value = this + .firstOf(t -> t.queryHistoricEnergyPerPeriod(edgeId, fromDate, toDate, channels, resolution)); + if (value != null) { + return value; } // no result this.logWarn(this.log, "No timedata result for 'queryHistoricEnergyPerPeriod' on Edge=" + edgeId + "; FromDate=" + fromDate + "; ToDate=" + toDate + "; Channels=" + channels + "; Resolution=" + resolution); + throw new OpenemsException("Unable to query historic energy per period. Result is null"); + } + + @Override + public SortedMap queryFirstValueBefore(String edgeId, ZonedDateTime date, + Set channels) throws OpenemsNamedException { + final var value = this.firstOf(t -> t.queryFirstValueBefore(edgeId, date, channels)); + if (value != null) { + return value; + } + + this.logWarn(this.log, "No timedata result for 'queryFirstValueBefore' on Edge=" + edgeId + "; Date=" + date + + "; Channels=" + channels); + throw new OpenemsException("Unable to query first value before. Result is null"); + } + + private T firstOf(ThrowingFunction function) throws OpenemsNamedException { + var timedatas = this.timedatas.get(); + final var errors = new ArrayList(); + for (var timedata : timedatas) { + try { + var data = function.apply(timedata); + if (data != null) { + return data; + } + } catch (InternalTimedataException e) { + this.log.info(timedata.id() + ": " + e.getMessage()); + } catch (OpenemsNamedException e) { + this.log.info(timedata.id() + ": " + e.getMessage()); + errors.add(e); + } catch (RuntimeException e) { + this.log.info(timedata.id() + ": " + e.getMessage(), e); + } + } + if (!errors.isEmpty()) { + throw new OpenemsException(errors.stream().map(t -> t.getMessage()).collect(Collectors.joining("; "))); + } return null; } @Override - public void write(String edgeId, TreeBasedTable data) { + public void write(String edgeId, AggregatedDataNotification data) { + this.write(edgeId, data, Timedata::write); + } + + @Override + public void write(String edgeId, TimestampedDataNotification data) { + this.write(edgeId, data, Timedata::write); + } + + @Override + public void write(String edgeId, ResendDataNotification data) { + this.write(edgeId, data, Timedata::write); + } + + private void write(// + final String edgeId, // + final T data, // + final ThrowingTriConsumer method // + ) { for (var timedata : this.timedatas.get()) { try { - timedata.write(edgeId, data); + method.accept(timedata, edgeId, data); } catch (OpenemsException e) { this.logWarn(this.log, "Timedata write failed for Edge=" + edgeId); } diff --git a/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/EdgeWebsocketImpl.java b/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/EdgeWebsocketImpl.java index 4f56fe62894..1624acbaf22 100644 --- a/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/EdgeWebsocketImpl.java +++ b/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/EdgeWebsocketImpl.java @@ -1,7 +1,8 @@ package io.openems.backend.edgewebsocket; -import java.util.Map; import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -45,7 +46,6 @@ import io.openems.common.jsonrpc.response.AuthenticatedRpcResponse; import io.openems.common.types.ChannelAddress; import io.openems.common.utils.ThreadPoolUtils; -import io.openems.common.websocket.AbstractWebsocketServer.DebugMode; @Designate(ocd = Config.class, factory = false) @Component(// @@ -91,6 +91,10 @@ private void activate(Config config) { .append(this.server != null ? this.server.getConnections().size() : "initializing") // .toString()); }, 10, 10, TimeUnit.SECONDS); + + if (this.metadata.isInitialized()) { + this.startServer(); + } } @Deactivate @@ -101,14 +105,13 @@ private void deactivate() { /** * Create and start new server. - * - * @param port the port - * @param poolSize number of threads dedicated to handle the tasks - * @param debugMode activate a regular debug log about the state of the tasks */ - private synchronized void startServer(int port, int poolSize, DebugMode debugMode) { - this.server = new WebsocketServer(this, this.getName(), port, poolSize, debugMode); - this.server.start(); + private synchronized void startServer() { + if (this.server == null) { + this.server = new WebsocketServer(this, this.getName(), this.config.port(), this.config.poolSize(), + this.config.debugMode()); + this.server.start(); + } } /** @@ -260,15 +263,16 @@ public void handleSystemLogNotification(String edgeId, SystemLogNotification not public void handleEvent(Event event) { switch (event.getTopic()) { case Metadata.Events.AFTER_IS_INITIALIZED: - this.startServer(this.config.port(), this.config.poolSize(), this.config.debugMode()); + this.startServer(); break; } } @Override - public Map getChannelValues(String edgeId, Set channelAddresses) { - Map result = channelAddresses.stream() // - .collect(Collectors.toMap(Function.identity(), c -> JsonNull.INSTANCE)); + public SortedMap getChannelValues(String edgeId, + Set channelAddresses) { + SortedMap result = channelAddresses.stream() // + .collect(Collectors.toMap(Function.identity(), c -> JsonNull.INSTANCE, (t, u) -> u, TreeMap::new)); var ws = this.getWebSocketForEdgeId(edgeId); if (ws == null) { return result; diff --git a/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/OnNotification.java b/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/OnNotification.java index 37c28deaccf..b528209268b 100644 --- a/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/OnNotification.java +++ b/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/OnNotification.java @@ -15,8 +15,11 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; import io.openems.common.jsonrpc.base.JsonrpcNotification; +import io.openems.common.jsonrpc.notification.AbstractDataNotification; +import io.openems.common.jsonrpc.notification.AggregatedDataNotification; import io.openems.common.jsonrpc.notification.EdgeConfigNotification; import io.openems.common.jsonrpc.notification.EdgeRpcNotification; +import io.openems.common.jsonrpc.notification.ResendDataNotification; import io.openems.common.jsonrpc.notification.SystemLogNotification; import io.openems.common.jsonrpc.notification.TimestampedDataNotification; import io.openems.common.types.SemanticVersion; @@ -50,20 +53,18 @@ public void run(WebSocket ws, JsonrpcNotification notification) throws OpenemsNa // Handle notification switch (notification.getMethod()) { - case EdgeConfigNotification.METHOD: + case EdgeConfigNotification.METHOD -> this.handleEdgeConfigNotification(EdgeConfigNotification.from(notification), wsData); - return; - - case TimestampedDataNotification.METHOD: - this.handleTimestampedDataNotification(TimestampedDataNotification.from(notification), wsData); - return; - - case SystemLogNotification.METHOD: + case TimestampedDataNotification.METHOD -> + this.handleDataNotification(TimestampedDataNotification.from(notification), wsData); + case AggregatedDataNotification.METHOD -> + this.handleDataNotification(AggregatedDataNotification.from(notification), wsData); + case ResendDataNotification.METHOD -> + this.handleResendDataNotification(ResendDataNotification.from(notification), wsData); + case SystemLogNotification.METHOD -> this.handleSystemLogNotification(SystemLogNotification.from(notification), wsData); - return; + default -> this.parent.logWarn(this.log, edgeId, "Unhandled Notification: " + notification); } - - this.parent.logWarn(this.log, edgeId, "Unhandled Notification: " + notification); } /** @@ -85,7 +86,9 @@ private void handleEdgeConfigNotification(EdgeConfigNotification message, WsData // forward try { - this.parent.uiWebsocket.sendBroadcast(edgeId, new EdgeRpcNotification(edgeId, message)); + if (this.parent.uiWebsocket != null) { + this.parent.uiWebsocket.sendBroadcast(edgeId, new EdgeRpcNotification(edgeId, message)); + } } catch (OpenemsNamedException e) { this.parent.logWarn(this.log, edgeId, "Unable to forward EdgeConfigNotification to UI: " + e.getMessage()); } catch (NullPointerException e) { @@ -95,6 +98,14 @@ private void handleEdgeConfigNotification(EdgeConfigNotification message, WsData } } + private void handleResendDataNotification(// + final ResendDataNotification message, // + final WsData wsData // + ) throws OpenemsNamedException { + final var edgeId = wsData.assertEdgeId(message); + this.parent.timedataManager.write(edgeId, message); + } + /** * Handles TimestampedDataNotification. * @@ -102,23 +113,26 @@ private void handleEdgeConfigNotification(EdgeConfigNotification message, WsData * @param wsData the WebSocket attachment * @throws OpenemsNamedException on error */ - private void handleTimestampedDataNotification(TimestampedDataNotification message, WsData wsData) - throws OpenemsNamedException { + private void handleDataNotification(AbstractDataNotification message, WsData wsData) throws OpenemsNamedException { var edgeId = wsData.assertEdgeId(message); - var data = message.getData(); - - // Update the Data Cache - wsData.edgeCache.update(data.rowMap()); - try { - this.parent.timedataManager.write(edgeId, data); + // TODO java 21 switch case with type + if (message instanceof TimestampedDataNotification timestampNotification) { + wsData.edgeCache.updateCurrentData(timestampNotification); + this.parent.timedataManager.write(edgeId, timestampNotification); + } else if (message instanceof AggregatedDataNotification aggregatedNotification) { + wsData.edgeCache.updateAggregatedData(aggregatedNotification); + this.parent.timedataManager.write(edgeId, aggregatedNotification); + } } catch (IllegalArgumentException e) { e.printStackTrace(); } // Forward subscribed Channels to UI - this.parent.uiWebsocket.sendSubscribedChannels(edgeId, wsData.edgeCache); + if (this.parent.uiWebsocket != null) { + this.parent.uiWebsocket.sendSubscribedChannels(edgeId, wsData.edgeCache); + } // Read some specific channels var edge = this.parent.metadata.getEdgeOrError(edgeId); diff --git a/io.openems.backend.timedata.dummy/src/io/openems/backend/timedata/dummy/TimedataDummy.java b/io.openems.backend.timedata.dummy/src/io/openems/backend/timedata/dummy/TimedataDummy.java index f09af89f735..02009288f40 100644 --- a/io.openems.backend.timedata.dummy/src/io/openems/backend/timedata/dummy/TimedataDummy.java +++ b/io.openems.backend.timedata.dummy/src/io/openems/backend/timedata/dummy/TimedataDummy.java @@ -15,7 +15,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.collect.TreeBasedTable; import com.google.gson.JsonElement; import io.openems.backend.common.component.AbstractOpenemsBackendComponent; @@ -23,6 +22,9 @@ import io.openems.backend.common.timedata.Timedata; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.jsonrpc.notification.AggregatedDataNotification; +import io.openems.common.jsonrpc.notification.ResendDataNotification; +import io.openems.common.jsonrpc.notification.TimestampedDataNotification; import io.openems.common.timedata.Resolution; import io.openems.common.types.ChannelAddress; @@ -55,16 +57,39 @@ private void deactivate() { } @Override - public void write(String edgeId, TreeBasedTable data) throws OpenemsException { - // get existing or create new EdgeCache - var edgeCache = this.edgeCacheMap.get(edgeId); - if (edgeCache == null) { - edgeCache = new EdgeCache(); - this.edgeCacheMap.put(edgeId, edgeCache); + public void write(String edgeId, TimestampedDataNotification data) { + synchronized (this.edgeCacheMap) { + // get existing or create new EdgeCache + var edgeCache = this.edgeCacheMap.get(edgeId); + if (edgeCache == null) { + edgeCache = new EdgeCache(); + this.edgeCacheMap.put(edgeId, edgeCache); + } + + // Update the Data Cache + edgeCache.updateCurrentData(data); } + } + + @Override + public void write(String edgeId, AggregatedDataNotification data) { + synchronized (this.edgeCacheMap) { + // get existing or create new EdgeCache + var edgeCache = this.edgeCacheMap.get(edgeId); + if (edgeCache == null) { + edgeCache = new EdgeCache(); + this.edgeCacheMap.put(edgeId, edgeCache); + } + + // Update the Data Cache + edgeCache.updateAggregatedData(data); + } + } + + @Override + public void write(String edgeId, ResendDataNotification data) { + // TODO Auto-generated method stub - // Update the Data Cache - edgeCache.update(data.rowMap()); } @Override diff --git a/io.openems.backend.timedata.influx/src/io/openems/backend/timedata/influx/Function.java b/io.openems.backend.timedata.influx/src/io/openems/backend/timedata/influx/Function.java deleted file mode 100644 index 389a9fe3ec1..00000000000 --- a/io.openems.backend.timedata.influx/src/io/openems/backend/timedata/influx/Function.java +++ /dev/null @@ -1,7 +0,0 @@ -package io.openems.backend.timedata.influx; - -public enum Function { - - PLUS - -} diff --git a/io.openems.backend.timedata.influx/src/io/openems/backend/timedata/influx/TimedataInfluxDb.java b/io.openems.backend.timedata.influx/src/io/openems/backend/timedata/influx/TimedataInfluxDb.java index 16381da8033..c2847c3aad2 100644 --- a/io.openems.backend.timedata.influx/src/io/openems/backend/timedata/influx/TimedataInfluxDb.java +++ b/io.openems.backend.timedata.influx/src/io/openems/backend/timedata/influx/TimedataInfluxDb.java @@ -2,35 +2,42 @@ import java.net.URI; import java.time.ZonedDateTime; -import java.util.Map; -import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.SortedMap; -import java.util.regex.Pattern; +import java.util.function.Function; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.ConfigurationPolicy; import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Reference; +import org.osgi.service.event.Event; +import org.osgi.service.event.EventHandler; +import org.osgi.service.event.propertytypes.EventTopics; import org.osgi.service.metatype.annotations.Designate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.collect.TreeBasedTable; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; import com.google.gson.JsonElement; import com.google.gson.JsonPrimitive; import com.influxdb.client.domain.WritePrecision; import com.influxdb.client.write.Point; -import com.influxdb.exceptions.BadRequestException; import io.openems.backend.common.component.AbstractOpenemsBackendComponent; +import io.openems.backend.common.metadata.Edge; import io.openems.backend.common.metadata.Metadata; import io.openems.backend.common.timedata.Timedata; import io.openems.common.OpenemsOEM; +import io.openems.common.event.EventReader; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.jsonrpc.notification.AbstractDataNotification; +import io.openems.common.jsonrpc.notification.AggregatedDataNotification; +import io.openems.common.jsonrpc.notification.ResendDataNotification; +import io.openems.common.jsonrpc.notification.TimestampedDataNotification; import io.openems.common.timedata.Resolution; import io.openems.common.types.ChannelAddress; import io.openems.common.utils.StringUtils; @@ -45,9 +52,10 @@ }, // immediate = true // ) -public class TimedataInfluxDb extends AbstractOpenemsBackendComponent implements Timedata { - - private static final Pattern NAME_NUMBER_PATTERN = Pattern.compile("[^0-9]+([0-9]+)$"); +@EventTopics({ // + Edge.Events.ON_SET_ONLINE // +}) +public class TimedataInfluxDb extends AbstractOpenemsBackendComponent implements Timedata, EventHandler { private final Logger log = LoggerFactory.getLogger(TimedataInfluxDb.class); private final FieldTypeConflictHandler fieldTypeConflictHandler; @@ -58,6 +66,9 @@ public class TimedataInfluxDb extends AbstractOpenemsBackendComponent implements private Config config; private InfluxConnector influxConnector = null; + // edgeId, channelIds which are timestamped channels + private final Multimap timestampedChannelsForEdge = HashMultimap.create(); + public TimedataInfluxDb() { super("Timedata.InfluxDB"); this.fieldTypeConflictHandler = new FieldTypeConflictHandler(this); @@ -75,16 +86,11 @@ private void activate(Config config) throws OpenemsException, IllegalArgumentExc + (config.isReadOnly() ? ";READ_ONLY_MODE" : "") // + "]"); - this.influxConnector = new InfluxConnector(config.queryLanguage(), URI.create(config.url()), config.org(), - config.apiKey(), config.bucket(), config.isReadOnly(), config.poolSize(), config.maxQueueSize(), // - (throwable) -> { - if (throwable instanceof BadRequestException) { - this.fieldTypeConflictHandler.handleException((BadRequestException) throwable); - - } else { - this.logError(this.log, "Unable to write to InfluxDB. " + throwable.getClass().getSimpleName() - + ": " + throwable.getMessage()); - } + this.influxConnector = new InfluxConnector(config.id(), config.queryLanguage(), URI.create(config.url()), + config.org(), config.apiKey(), config.bucket(), config.isReadOnly(), config.poolSize(), + config.maxQueueSize(), // + (e) -> { + this.fieldTypeConflictHandler.handleException(e); }); } @@ -97,29 +103,104 @@ private void deactivate() { } @Override - public void write(String edgeId, TreeBasedTable data) throws OpenemsException { + public void handleEvent(Event event) { + switch (event.getTopic()) { + case Edge.Events.ON_SET_ONLINE: + final var reader = new EventReader(event); + final var edgeId = reader.getString(Edge.Events.OnSetOnline.EDGE_ID); + final var isOnline = reader.getBoolean(Edge.Events.OnSetOnline.IS_ONLINE); + if (!isOnline) { + try { + var influxEdgeId = InfluxConnector.parseNumberFromName(edgeId); + this.timestampedChannelsForEdge.removeAll(influxEdgeId); + } catch (OpenemsException e) { + e.printStackTrace(); + } + } + break; + } + } + + @Override + public void write(String edgeId, TimestampedDataNotification notification) { + if (this.config.isReadOnly()) { + return; + } + // parse the numeric EdgeId - int influxEdgeId = TimedataInfluxDb.parseNumberFromName(edgeId); + try { + int influxEdgeId = InfluxConnector.parseNumberFromName(edgeId); + + // Write data to default location + this.writeData(// + influxEdgeId, // + notification, // + channel -> { + this.timestampedChannelsForEdge.put(influxEdgeId, channel); + return true; + }); + } catch (OpenemsException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + @Override + public void write(String edgeId, AggregatedDataNotification notification) { + if (this.config.isReadOnly()) { + return; + } - // Write data to default location - this.writeData(influxEdgeId, data); + try { + int influxEdgeId = InfluxConnector.parseNumberFromName(edgeId); + + // Write data to default location + this.writeData(// + influxEdgeId, // + notification, // + channel -> !this.isTimestampedChannel(influxEdgeId, channel)); + } catch (OpenemsException e) { + e.printStackTrace(); + } + } + + @Override + public void write(String edgeId, ResendDataNotification data) { + // TODO Auto-generated method stub + } + + private boolean isTimestampedChannel(int edgeId, String channel) { + final var channelSet = this.timestampedChannelsForEdge.get(edgeId); + // if edge is not set the checked channel may be timestamped channel so + // initially return true + if (channelSet == null) { + return true; + } + return channelSet.contains(channel); } /** * Actually writes the data to InfluxDB. * - * @param influxEdgeId the unique, numeric identifier of the Edge - * @param data the data + * @param influxEdgeId the unique, numeric identifier of the Edge + * @param notification the {@link AbstractDataNotification} + * @param shouldWriteValue the function which determines if the value should be + * written * @throws OpenemsException on error */ - private void writeData(int influxEdgeId, TreeBasedTable data) { + private void writeData(// + int influxEdgeId, // + AbstractDataNotification notification, // + Function shouldWriteValue // + ) { + final var data = notification.getData(); var dataEntries = data.rowMap().entrySet(); if (dataEntries.isEmpty()) { // no data to write return; } - for (Entry> dataEntry : dataEntries) { + for (var dataEntry : dataEntries) { var channelEntries = dataEntry.getValue().entrySet(); if (channelEntries.isEmpty()) { // no points to add @@ -129,39 +210,21 @@ private void writeData(int influxEdgeId, TreeBasedTable channelEntry : channelEntries) { - this.addValue(point, channelEntry.getKey(), channelEntry.getValue()); - } - if (point.hasFields()) { - this.influxConnector.write(point); + for (var channelEntry : channelEntries) { + if (!shouldWriteValue.apply(channelEntry.getKey())) { + continue; + } + this.addValue(// + point, // + channelEntry.getKey(), // + channelEntry.getValue()); } - } - } - /** - * Parses the number of an Edge from its name string. - * - *

    - * e.g. translates "edge0" to "0". - * - * @param name the edge name - * @return the number - * @throws OpenemsException on error - */ - public static Integer parseNumberFromName(String name) throws OpenemsException { - try { - var matcher = TimedataInfluxDb.NAME_NUMBER_PATTERN.matcher(name); - if (matcher.find()) { - var nameNumberString = matcher.group(1); - return Integer.parseInt(nameNumberString); - } - } catch (NullPointerException e) { - /* ignore */ + this.influxConnector.write(point); } - throw new OpenemsException("Unable to parse number from name [" + name + "]"); } @Override @@ -169,17 +232,19 @@ public SortedMap> queryHis ZonedDateTime fromDate, ZonedDateTime toDate, Set channels, Resolution resolution) throws OpenemsNamedException { // parse the numeric EdgeId - Optional influxEdgeId = Optional.of(TimedataInfluxDb.parseNumberFromName(edgeId)); + Optional influxEdgeId = Optional.of(InfluxConnector.parseNumberFromName(edgeId)); - return this.influxConnector.queryHistoricData(influxEdgeId, fromDate, toDate, channels, resolution); + return this.influxConnector.queryHistoricData(influxEdgeId, fromDate, toDate, channels, resolution, + this.config.measurement()); } @Override public SortedMap queryHistoricEnergy(String edgeId, ZonedDateTime fromDate, ZonedDateTime toDate, Set channels) throws OpenemsNamedException { // parse the numeric EdgeId - Optional influxEdgeId = Optional.of(TimedataInfluxDb.parseNumberFromName(edgeId)); - return this.influxConnector.queryHistoricEnergy(influxEdgeId, fromDate, toDate, channels); + Optional influxEdgeId = Optional.of(InfluxConnector.parseNumberFromName(edgeId)); + return this.influxConnector.queryHistoricEnergy(influxEdgeId, fromDate, toDate, channels, + this.config.measurement()); } @Override @@ -187,8 +252,9 @@ public SortedMap> queryHis ZonedDateTime fromDate, ZonedDateTime toDate, Set channels, Resolution resolution) throws OpenemsNamedException { // parse the numeric EdgeId - Optional influxEdgeId = Optional.of(TimedataInfluxDb.parseNumberFromName(edgeId)); - return this.influxConnector.queryHistoricEnergyPerPeriod(influxEdgeId, fromDate, toDate, channels, resolution); + Optional influxEdgeId = Optional.of(InfluxConnector.parseNumberFromName(edgeId)); + return this.influxConnector.queryHistoricEnergyPerPeriod(influxEdgeId, fromDate, toDate, channels, resolution, + this.config.measurement()); } /** diff --git a/io.openems.backend.timedata.timescaledb/src/io/openems/backend/timedata/timescaledb/TimedataTimescaleDb.java b/io.openems.backend.timedata.timescaledb/src/io/openems/backend/timedata/timescaledb/TimedataTimescaleDb.java index fecc67a4481..9c38771435a 100644 --- a/io.openems.backend.timedata.timescaledb/src/io/openems/backend/timedata/timescaledb/TimedataTimescaleDb.java +++ b/io.openems.backend.timedata.timescaledb/src/io/openems/backend/timedata/timescaledb/TimedataTimescaleDb.java @@ -17,7 +17,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.collect.TreeBasedTable; import com.google.gson.JsonElement; import io.openems.backend.common.component.AbstractOpenemsBackendComponent; @@ -26,7 +25,9 @@ import io.openems.backend.timedata.timescaledb.internal.read.TimescaledbReadHandler; import io.openems.backend.timedata.timescaledb.internal.write.TimescaledbWriteHandler; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.exceptions.OpenemsException; +import io.openems.common.jsonrpc.notification.AggregatedDataNotification; +import io.openems.common.jsonrpc.notification.ResendDataNotification; +import io.openems.common.jsonrpc.notification.TimestampedDataNotification; import io.openems.common.timedata.Resolution; import io.openems.common.types.ChannelAddress; import io.openems.common.utils.ThreadPoolUtils; @@ -83,8 +84,20 @@ private void deactivate() { } @Override - public void write(String edgeId, TreeBasedTable data) throws OpenemsException { - this.timescaledbWriteHandler.write(edgeId, data); + public void write(String edgeId, TimestampedDataNotification data) { + this.timescaledbWriteHandler.write(edgeId, data.getData()); + } + + @Override + public void write(String edgeId, AggregatedDataNotification data) { + // TODO + this.logWarn(this.log, "Timedata.TimescaleDB do not support write of AggregatedDataNotification"); + } + + @Override + public void write(String edgeId, ResendDataNotification data) { + // TODO Auto-generated method stub + } @Override diff --git a/io.openems.edge.timedata.influxdb/bnd.bnd b/io.openems.edge.timedata.influxdb/bnd.bnd index 04882345c32..6a5150421e6 100644 --- a/io.openems.edge.timedata.influxdb/bnd.bnd +++ b/io.openems.edge.timedata.influxdb/bnd.bnd @@ -14,4 +14,7 @@ Bundle-Version: 1.0.0.${tstamp} -testpath: \ ${testpath},\ - io.openems.wrapper.influxdb-client-utils + io.openems.wrapper.influxdb-client-utils,\ + io.openems.wrapper.okhttp,\ + org.jetbrains.kotlin.osgi-bundle,\ + com.squareup.okio,\ \ No newline at end of file diff --git a/io.openems.edge.timedata.influxdb/src/io/openems/edge/timedata/influxdb/Config.java b/io.openems.edge.timedata.influxdb/src/io/openems/edge/timedata/influxdb/Config.java index dae8327f537..287aec74cfe 100644 --- a/io.openems.edge.timedata.influxdb/src/io/openems/edge/timedata/influxdb/Config.java +++ b/io.openems.edge.timedata.influxdb/src/io/openems/edge/timedata/influxdb/Config.java @@ -34,6 +34,9 @@ @AttributeDefinition(name = "Bucket", description = "The bucket name; for InfluxDB v1: 'database/retentionPolicy', e.g. 'db/data'") String bucket(); + @AttributeDefinition(name = "Measurement", description = "The InfluxDB measurement") + String measurement() default "data"; + @AttributeDefinition(name = "No of Cycles", description = "How many Cycles till data is written to InfluxDB.") int noOfCycles() default 1; diff --git a/io.openems.edge.timedata.influxdb/src/io/openems/edge/timedata/influxdb/TimedataInfluxDbImpl.java b/io.openems.edge.timedata.influxdb/src/io/openems/edge/timedata/influxdb/TimedataInfluxDbImpl.java index 8c0d0250c5f..9807be624eb 100644 --- a/io.openems.edge.timedata.influxdb/src/io/openems/edge/timedata/influxdb/TimedataInfluxDbImpl.java +++ b/io.openems.edge.timedata.influxdb/src/io/openems/edge/timedata/influxdb/TimedataInfluxDbImpl.java @@ -82,10 +82,10 @@ private void activate(ComponentContext context, Config config) { return; } - this.influxConnector = new InfluxConnector(config.queryLanguage(), URI.create(config.url()), config.org(), - config.apiKey(), config.bucket(), config.isReadOnly(), 5, config.maxQueueSize(), // - (throwable) -> { - this.logError(this.log, "Unable to write to InfluxDB: " + throwable.getMessage()); + this.influxConnector = new InfluxConnector(config.id(), config.queryLanguage(), URI.create(config.url()), + config.org(), config.apiKey(), config.bucket(), config.isReadOnly(), 5, config.maxQueueSize(), // + (e) -> { + // ignore }); } @@ -116,7 +116,7 @@ protected synchronized void collectAndWriteChannelValues() { if (++this.cycleCount >= this.config.noOfCycles()) { this.cycleCount = 0; - final var point = Point.measurement(InfluxConnector.MEASUREMENT).time(timestamp, WritePrecision.MS); + final var point = Point.measurement(this.config.measurement()).time(timestamp, WritePrecision.MS); final var addedAtLeastOneChannelValue = new AtomicBoolean(false); this.componentManager.getEnabledComponents().stream().filter(OpenemsComponent::isEnabled) @@ -183,7 +183,8 @@ public SortedMap> queryHis throws OpenemsNamedException { // ignore edgeId as Points are also written without Edge-ID Optional influxEdgeId = Optional.empty(); - return this.influxConnector.queryHistoricData(influxEdgeId, fromDate, toDate, channels, resolution); + return this.influxConnector.queryHistoricData(influxEdgeId, fromDate, toDate, channels, resolution, + this.config.measurement()); } @Override @@ -191,7 +192,8 @@ public SortedMap queryHistoricEnergy(String edgeId, ZonedDateTime toDate, Set channels) throws OpenemsNamedException { // ignore edgeId as Points are also written without Edge-ID Optional influxEdgeId = Optional.empty(); - return this.influxConnector.queryHistoricEnergy(influxEdgeId, fromDate, toDate, channels); + return this.influxConnector.queryHistoricEnergy(influxEdgeId, fromDate, toDate, channels, + this.config.measurement()); } @Override @@ -200,7 +202,8 @@ public SortedMap> queryHis throws OpenemsNamedException { // ignore edgeId as Points are also written without Edge-ID Optional influxEdgeId = Optional.empty(); - return this.influxConnector.queryHistoricEnergyPerPeriod(influxEdgeId, fromDate, toDate, channels, resolution); + return this.influxConnector.queryHistoricEnergyPerPeriod(influxEdgeId, fromDate, toDate, channels, resolution, + this.config.measurement()); } @Override diff --git a/io.openems.edge.timedata.influxdb/test/io/openems/edge/timedata/influxdb/MyConfig.java b/io.openems.edge.timedata.influxdb/test/io/openems/edge/timedata/influxdb/MyConfig.java index 8f1abb0742c..37b1713dfd0 100644 --- a/io.openems.edge.timedata.influxdb/test/io/openems/edge/timedata/influxdb/MyConfig.java +++ b/io.openems.edge.timedata.influxdb/test/io/openems/edge/timedata/influxdb/MyConfig.java @@ -16,6 +16,7 @@ protected static class Builder { private String org; private String url; private QueryLanguageConfig queryLanguage; + private String measurement; private Builder() { } @@ -65,6 +66,11 @@ public Builder setQueryLanguage(QueryLanguageConfig queryLanguage) { return this; } + public Builder setMeasurement(String measurement) { + this.measurement = measurement; + return this; + } + public MyConfig build() { return new MyConfig(this); } @@ -125,4 +131,9 @@ public int maxQueueSize() { public boolean isReadOnly() { return this.builder.isReadOnly; } + + @Override + public String measurement() { + return this.builder.measurement; + } } \ No newline at end of file diff --git a/io.openems.edge.timedata.influxdb/test/io/openems/edge/timedata/influxdb/TimedataInfluxDbImplTest.java b/io.openems.edge.timedata.influxdb/test/io/openems/edge/timedata/influxdb/TimedataInfluxDbImplTest.java index 2e035283d9e..1ef21032f88 100644 --- a/io.openems.edge.timedata.influxdb/test/io/openems/edge/timedata/influxdb/TimedataInfluxDbImplTest.java +++ b/io.openems.edge.timedata.influxdb/test/io/openems/edge/timedata/influxdb/TimedataInfluxDbImplTest.java @@ -24,6 +24,7 @@ public void test() throws Exception { .setOrg("-") // .setApiKey("username:password") // .setBucket("database/retentionPolicy") // + .setMeasurement("data") // .setNoOfCycles(1) // .setMaxQueueSize(5000) // .setReadOnly(false) // diff --git a/io.openems.shared.influxdb/src/io/openems/shared/influxdb/AbstractMergePointsWorker.java b/io.openems.shared.influxdb/src/io/openems/shared/influxdb/AbstractMergePointsWorker.java new file mode 100644 index 00000000000..e57c0fa607f --- /dev/null +++ b/io.openems.shared.influxdb/src/io/openems/shared/influxdb/AbstractMergePointsWorker.java @@ -0,0 +1,123 @@ +package io.openems.shared.influxdb; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.influxdb.client.write.Point; +import com.influxdb.client.write.WriteParameters; +import com.influxdb.exceptions.BadRequestException; + +import io.openems.common.worker.AbstractImmediateWorker; + +public abstract class AbstractMergePointsWorker extends AbstractImmediateWorker implements MergePointsWorker { + + private static final int MAX_POINTS_PER_WRITE = 1_000; + private static final int MAX_AGGREGATE_WAIT = 10; // [s] + private static final int POINTS_QUEUE_SIZE = 1_000_000; + + private final Logger log = LoggerFactory.getLogger(MergePointsWorker.class); + + private final String name; + protected final InfluxConnector parent; + protected final WriteParameters writeParameters; + private final Consumer onWriteError; + + private final BlockingQueue pointsQueue = new LinkedBlockingQueue<>(POINTS_QUEUE_SIZE); + + public AbstractMergePointsWorker(InfluxConnector parent, String name, WriteParameters writeParameters, + Consumer onWriteError) { + this.parent = parent; + this.name = name; + this.writeParameters = writeParameters; + this.onWriteError = onWriteError; + } + + @Override + public void activate() { + this.activate("TimescaleDB-MergePoints" + this.name); + } + + @Override + protected void forever() throws InterruptedException { + var points = this.pollPoints(); + + if (points.isEmpty()) { + return; + } + + /* + * Write points async. + */ + this.parent.executor.execute(() -> { + if (this.parent.queryProxy.isLimitReached()) { + return; + } + try { + this.parent.getInfluxConnection().writeApi.writePoints(this.writePoints(points), this.writeParameters); + this.parent.queryProxy.queryLimit.decrease(); + } catch (Throwable t) { + this.parent.queryProxy.queryLimit.increase(); + this.onWriteError(t, points); + } + }); + } + + private List pollPoints() throws InterruptedException { + final Instant maxWait = Instant.now().plusSeconds(MAX_AGGREGATE_WAIT); + var points = new ArrayList(MAX_POINTS_PER_WRITE); + for (int i = 0; i < MAX_POINTS_PER_WRITE; i++) { + var point = this.pointsQueue.poll(MAX_AGGREGATE_WAIT, TimeUnit.SECONDS); + if (point == null) { + break; + } + points.add(point); + if (Instant.now().isAfter(maxWait)) { + break; + } + } + return points; + } + + protected abstract List writePoints(List points); + + protected void onWriteError(Throwable t, List points) { + this.log.warn("Unable to write to InfluxDB. " + t.getClass().getSimpleName() + ": " + t.getMessage()); + if (t instanceof BadRequestException) { + this.onWriteError.accept((BadRequestException) t); + } + } + + /** + * Inserts the specified element into this queue if it is possible to do so + * immediately without violating capacity restrictions, returning true upon + * success and false if no space is currently available. + * + * @param point the point to add + * @return true if the point was added to this queue, else false + */ + public boolean offer(T point) { + return this.pointsQueue.offer(point); + } + + @Override + public String debugLog() { + final var pointsQueueSize = this.pointsQueue.size(); + return new StringBuilder() // + .append(this.name) // + .append(": ") // + .append(pointsQueueSize) // + .append("/") // + .append(POINTS_QUEUE_SIZE) // + .append((pointsQueueSize == POINTS_QUEUE_SIZE) ? " !!!POINTS BACKPRESSURE!!!" : "") // + .toString(); + } + +} diff --git a/io.openems.shared.influxdb/src/io/openems/shared/influxdb/DbDataUtils.java b/io.openems.shared.influxdb/src/io/openems/shared/influxdb/DbDataUtils.java new file mode 100644 index 00000000000..f8615160dd2 --- /dev/null +++ b/io.openems.shared.influxdb/src/io/openems/shared/influxdb/DbDataUtils.java @@ -0,0 +1,157 @@ +package io.openems.shared.influxdb; + +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Collections; +import java.util.Map.Entry; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.stream.Collectors; + +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonPrimitive; + +import io.openems.common.timedata.DurationUnit; +import io.openems.common.timedata.Resolution; +import io.openems.common.types.ChannelAddress; +import io.openems.common.utils.JsonUtils; + +public final class DbDataUtils { + + private DbDataUtils() { + super(); + } + + /** + * Normalizes the given table by adding null values for missing time stamps. + * + * @param table the data + * @param channels the channels + * @param resolution the resolution + * @param fromDate the starting date + * @param toDate the end date + * @return the normalized table + */ + public static SortedMap> normalizeTable(// + SortedMap> table, // + Set channels, // + Resolution resolution, // + ZonedDateTime fromDate, // + ZonedDateTime toDate // + ) { + // currently only works for days and months otherwise just return the table + if (resolution.getUnit() != ChronoUnit.DAYS // + && resolution.getUnit() != ChronoUnit.MONTHS) { + return table; + } + SortedMap> normalizedTable = new TreeMap<>(); + + var start = fromDate; + while (start.isBefore(toDate)) { + ZonedDateTime end = null; + switch (resolution.getUnit()) { + case CENTURIES: + case DECADES: + case ERAS: + case FOREVER: + case HALF_DAYS: + case HOURS: + case MICROS: + case MILLENNIA: + case MILLIS: + case MINUTES: + case NANOS: + case SECONDS: + case WEEKS: + case YEARS: + // No specific handling required + break; + case DAYS: + end = start.plusDays(resolution.getValue()) // + .truncatedTo(DurationUnit.ofDays(1)); + break; + case MONTHS: + end = start.plusMonths(resolution.getValue()) // + .withDayOfMonth(1); + break; + } + + SortedMap foundData = null; + for (var data : table.entrySet()) { + if (data.getKey().isBefore(start)) { + continue; + } + // end exclusive + if (data.getKey().isAfter(end)) { + continue; + } + if (data.getKey().isEqual(end)) { + continue; + } + foundData = data.getValue(); + break; + } + // fill with null values + if (foundData == null) { + foundData = channels.stream() // + .collect(Collectors.toMap(// + t -> t, // + t -> JsonNull.INSTANCE, // + (oldValue, newValue) -> newValue, // + TreeMap::new // + )); + } + normalizedTable.put(start, foundData); + start = end; + } + + return normalizedTable; + } + + /** + * Calculates the difference of the every values based on the last valid value + * and drops all values which are before the fromDate. + * + * @param data the data + * @param fromDate the starting date + * @return the differences of the values + */ + public static SortedMap> calculateLastMinusFirst(// + final SortedMap> data, // + final ZonedDateTime fromDate // + ) { + final var lastValidValues = new TreeMap(); + final SortedMap> result = data.entrySet().stream() // + .collect(Collectors.toMap(Entry::getKey, entry -> { + if (entry.getValue() == null) { + return Collections.emptySortedMap(); + } + return entry.getValue().entrySet().stream() // + .collect(Collectors.toMap(Entry::getKey, t -> { + final var channel = t.getKey(); + final var value = t.getValue(); + final var lastValue = lastValidValues.get(channel); + + if (!JsonUtils.isNumber(value)) { + return JsonNull.INSTANCE; + } + lastValidValues.put(channel, value); + if (lastValue == null) { + return value; + } + + final var diff = value.getAsDouble() - lastValue.getAsDouble(); + if (diff < 0) { + return JsonNull.INSTANCE; + } + return new JsonPrimitive(diff); + }, (t, u) -> u, TreeMap::new)); + }, (t, u) -> u, TreeMap::new)); + + result.headMap(fromDate).clear(); + return result; + } + +} diff --git a/io.openems.shared.influxdb/src/io/openems/shared/influxdb/ForceMergePointsWorker.java b/io.openems.shared.influxdb/src/io/openems/shared/influxdb/ForceMergePointsWorker.java new file mode 100644 index 00000000000..9ec62aecd6d --- /dev/null +++ b/io.openems.shared.influxdb/src/io/openems/shared/influxdb/ForceMergePointsWorker.java @@ -0,0 +1,22 @@ +package io.openems.shared.influxdb; + +import java.util.List; +import java.util.function.Consumer; + +import com.influxdb.client.write.Point; +import com.influxdb.client.write.WriteParameters; +import com.influxdb.exceptions.BadRequestException; + +public class ForceMergePointsWorker extends AbstractMergePointsWorker implements MergePointsWorker { + + public ForceMergePointsWorker(InfluxConnector parent, String name, WriteParameters writeParameters, + Consumer onWriteError) { + super(parent, name, writeParameters, onWriteError); + } + + @Override + protected List writePoints(List points) { + return points; + } + +} diff --git a/io.openems.shared.influxdb/src/io/openems/shared/influxdb/InfluxConnector.java b/io.openems.shared.influxdb/src/io/openems/shared/influxdb/InfluxConnector.java index 94e05017f34..c3b211fdd9e 100644 --- a/io.openems.shared.influxdb/src/io/openems/shared/influxdb/InfluxConnector.java +++ b/io.openems.shared.influxdb/src/io/openems/shared/influxdb/InfluxConnector.java @@ -2,17 +2,22 @@ import java.net.URI; import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; -import java.util.concurrent.BlockingQueue; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiFunction; import java.util.function.Consumer; +import java.util.regex.Pattern; +import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,43 +28,50 @@ import com.influxdb.client.InfluxDBClientFactory; import com.influxdb.client.InfluxDBClientOptions; import com.influxdb.client.WriteApiBlocking; +import com.influxdb.client.domain.WriteConsistency; +import com.influxdb.client.domain.WritePrecision; import com.influxdb.client.write.Point; +import com.influxdb.client.write.WriteParameters; +import com.influxdb.exceptions.BadRequestException; +import io.openems.common.OpenemsOEM; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; import io.openems.common.timedata.Resolution; import io.openems.common.types.ChannelAddress; -import io.openems.common.utils.StringUtils; import io.openems.common.utils.ThreadPoolUtils; import io.openems.shared.influxdb.proxy.QueryProxy; import okhttp3.OkHttpClient; public class InfluxConnector { - public static final String MEASUREMENT = "data"; + private static final Pattern NAME_NUMBER_PATTERN = Pattern.compile("[^0-9]+([0-9]+)$"); private static final int CONNECT_TIMEOUT = 10; // [s] private static final int READ_TIMEOUT = 60; // [s] private static final int WRITE_TIMEOUT = 10; // [s] - private static final int POINTS_QUEUE_SIZE = 1_000_000; protected final ThreadPoolExecutor executor; - protected final BlockingQueue pointsQueue = new LinkedBlockingQueue<>(POINTS_QUEUE_SIZE); private final Logger log = LoggerFactory.getLogger(InfluxConnector.class); - private final QueryProxy queryProxy; + protected final QueryProxy queryProxy; private final URI url; private final String org; private final String apiKey; private final String bucket; private final boolean isReadOnly; + private final boolean safeWrite; private final ScheduledExecutorService debugLogExecutor = Executors.newSingleThreadScheduledExecutor(); - private final MergePointsWorker mergePointsWorker; + + private final WriteParameters defaultWriteParameters; + private final Map mergePointsWorkerByWriteParameters = new HashMap<>(); + private final AtomicInteger rejectedExecutionCount = new AtomicInteger(); /** * The Constructor. * + * @param componentId ID of the calling OpenEMS Component * @param queryLanguage A {@link QueryLanguageConfig} * @param url URL of the InfluxDB-Server (http://ip:port) * @param org The organisation; '-' for InfluxDB v1 @@ -71,34 +83,79 @@ public class InfluxConnector { * @param poolSize the number of threads dedicated to handle the tasks * @param maxQueueSize queue size limit for executor * @param onWriteError A consumer for write-errors + * @param safeWrite Adds back points to the queue if a write fails + * @param parameters the {@link WriteParameters} to create a + * {@link MergePointsWorker} for. All later used + * {@link WriteParameters} need to be passed here */ - public InfluxConnector(QueryLanguageConfig queryLanguage, URI url, String org, String apiKey, String bucket, - boolean isReadOnly, int poolSize, int maxQueueSize, Consumer onWriteError) { + public InfluxConnector(String componentId, QueryLanguageConfig queryLanguage, URI url, String org, String apiKey, + String bucket, boolean isReadOnly, int poolSize, int maxQueueSize, + Consumer onWriteError, boolean safeWrite, WriteParameters... parameters) { this.queryProxy = QueryProxy.from(queryLanguage); this.url = url; this.org = org; this.apiKey = apiKey; this.bucket = bucket; this.isReadOnly = isReadOnly; + this.safeWrite = safeWrite; this.executor = new ThreadPoolExecutor(poolSize, poolSize, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(maxQueueSize), // - new ThreadFactoryBuilder().setNameFormat("InfluxDB-%d").build()); + new ThreadFactoryBuilder().setNameFormat("InfluxDB-%d").build(), // + (r, executor) -> { + // Custom RejectedExecutionHandler; avoid throwing a RejectedExecutionException + this.rejectedExecutionCount.incrementAndGet(); + }); this.debugLogExecutor.scheduleWithFixedDelay(() -> { - int pointsQueueSize = this.pointsQueue.size(); this.log.info(new StringBuilder("[InfluxDB] [monitor] ") // + .append(componentId).append(" ") // .append(ThreadPoolUtils.debugLog(this.executor)) // - .append(" Queue:") // - .append(pointsQueueSize) // - .append("/") // - .append(POINTS_QUEUE_SIZE) // - .append((pointsQueueSize == POINTS_QUEUE_SIZE) ? " !!!POINTS BACKPRESSURE!!!" : "") // + .append(", MergePointsWorker[") // + .append(this.mergePointsWorkerByWriteParameters.values().stream().map(MergePointsWorker::debugLog) + .collect(Collectors.joining(", "))) + .append("], Limit:") // + .append(this.queryProxy.queryLimit) // + .append(", RejectedExecutions:") // + .append(this.rejectedExecutionCount.get()) // .toString()); }, 10, 10, TimeUnit.SECONDS); - this.mergePointsWorker = new MergePointsWorker(this, onWriteError); - this.mergePointsWorker.activate("TimescaleDB-MergePoints"); + BiFunction mergePointsWorkerFactory; + if (this.isSafeWrite()) { + mergePointsWorkerFactory = (name, params) -> new SafeMergePointsWorker(this, name, params, onWriteError); + } else { + mergePointsWorkerFactory = (name, params) -> new ForceMergePointsWorker(this, name, params, onWriteError); + } + + // initialize default merge points worker + // TODO most of the stuff can be omitted after update + // https://github.com/influxdata/influxdb-client-java/pull/483 + this.defaultWriteParameters = new WriteParameters(this.bucket, this.org, + WriteParameters.DEFAULT_WRITE_PRECISION, WriteConsistency.ALL); + final var defaultMergePointsWorker = mergePointsWorkerFactory.apply("Default", this.defaultWriteParameters); + defaultMergePointsWorker.activate(); + this.mergePointsWorkerByWriteParameters.put(this.defaultWriteParameters, defaultMergePointsWorker); + + final var defaultOptions = InfluxDBClientOptions.builder() // + .url(this.url.toString()) // + .org(this.org) // + .bucket(this.bucket) // + .build(); + // initialize merge points worker for specific write parameters + for (var writeParameters : parameters) { + final var mergePointsWorker = mergePointsWorkerFactory.apply(writeParameters.bucketSafe(defaultOptions), + writeParameters); + mergePointsWorker.activate(); + this.mergePointsWorkerByWriteParameters.put(writeParameters, mergePointsWorker); + } + } + + public InfluxConnector(String componentId, QueryLanguageConfig queryLanguage, URI url, String org, String apiKey, + String bucket, boolean isReadOnly, int poolSize, int maxQueueSize, + Consumer onWriteError, WriteParameters... parameters) { + this(componentId, queryLanguage, url, org, apiKey, bucket, isReadOnly, poolSize, maxQueueSize, onWriteError, + false, parameters); } public static class InfluxConnection { @@ -139,7 +196,6 @@ protected synchronized InfluxConnection getInfluxConnection() { if (this.apiKey != null && !this.apiKey.isBlank()) { options.authenticateToken(String.format(this.apiKey).toCharArray()); // } - var client = InfluxDBClientFactory // .create(options.build()) // .enableGzip(); @@ -156,11 +212,12 @@ protected synchronized InfluxConnection getInfluxConnection() { * Close current {@link InfluxDBClient}. */ public synchronized void deactivate() { - this.mergePointsWorker.deactivate(); ThreadPoolUtils.shutdownAndAwaitTermination(this.debugLogExecutor, 0); if (this.influxConnection != null) { this.influxConnection.client.close(); } + this.mergePointsWorkerByWriteParameters.values() // + .forEach(MergePointsWorker::deactivate); } /** @@ -170,18 +227,47 @@ public synchronized void deactivate() { * @param fromDate the From-Date * @param toDate the To-Date * @param channels the Channels to query + * @param measurement the measurement * @return a map between ChannelAddress and value * @throws OpenemsException on error */ public SortedMap queryHistoricEnergy(Optional influxEdgeId, - ZonedDateTime fromDate, ZonedDateTime toDate, Set channels) throws OpenemsNamedException { + ZonedDateTime fromDate, ZonedDateTime toDate, Set channels, String measurement) + throws OpenemsNamedException { + // handle empty call + if (channels.isEmpty()) { + return new TreeMap<>(); + } + + return this.queryProxy.queryHistoricEnergy(this.getInfluxConnection(), this.bucket, measurement, influxEdgeId, + fromDate, toDate, channels); + } + + /** + * Queries historic energy where only one value per day is saved. + * + * @param influxEdgeId the unique, numeric Edge-ID; or Empty to query all Edges + * @param fromDate the From-Date + * @param toDate the To-Date + * @param channels the Channels to query + * @param measurement the measurement + * @return a map between ChannelAddress and value + * @throws OpenemsException on error + */ + public SortedMap queryHistoricEnergySingleValueInDay(// + Optional influxEdgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels, // + String measurement // + ) throws OpenemsNamedException { // handle empty call if (channels.isEmpty()) { return new TreeMap<>(); } - return this.queryProxy.queryHistoricEnergy(this.getInfluxConnection(), this.bucket, influxEdgeId, fromDate, - toDate, channels); + return this.queryProxy.queryHistoricEnergySingleValueInDay(this.getInfluxConnection(), this.bucket, measurement, + influxEdgeId, fromDate, toDate, channels); } /** @@ -192,19 +278,45 @@ public SortedMap queryHistoricEnergy(Optional> queryHistoricEnergyPerPeriod( Optional influxEdgeId, ZonedDateTime fromDate, ZonedDateTime toDate, Set channels, - Resolution resolution) throws OpenemsNamedException { + Resolution resolution, String measurement) throws OpenemsNamedException { // handle empty call if (channels.isEmpty()) { return new TreeMap<>(); } - return this.queryProxy.queryHistoricEnergyPerPeriod(this.getInfluxConnection(), this.bucket, influxEdgeId, - fromDate, toDate, channels, resolution); + return this.queryProxy.queryHistoricEnergyPerPeriod(this.getInfluxConnection(), this.bucket, measurement, + influxEdgeId, fromDate, toDate, channels, resolution); + } + + /** + * Queries the raw historic values without calculating the difference between + * two values. + * + * @param influxEdgeId the unique, numeric Edge-ID; or Empty to query all Edges + * @param fromDate the From-Date + * @param toDate the To-Date + * @param channels the Channels to query + * @param resolution the resolution in seconds + * @param measurement the measurement + * @return the historic data as Map + * @throws OpenemsException on error + */ + public SortedMap> queryRawHistoricEnergyPerPeriodSinglePerDay( + Optional influxEdgeId, ZonedDateTime fromDate, ZonedDateTime toDate, Set channels, + Resolution resolution, String measurement) throws OpenemsNamedException { + // handle empty call + if (channels.isEmpty()) { + return new TreeMap<>(); + } + + return this.queryProxy.queryRawHistoricEnergyPerPeriodSingleValueInDay(this.getInfluxConnection(), this.bucket, + measurement, influxEdgeId, fromDate, toDate, channels, resolution); } /** @@ -215,35 +327,145 @@ public SortedMap> queryHis * @param toDate the To-Date * @param channels the Channels to query * @param resolution the resolution in seconds + * @param measurement the measurement * @return the historic data as Map * @throws OpenemsException on error */ public SortedMap> queryHistoricData( Optional influxEdgeId, ZonedDateTime fromDate, ZonedDateTime toDate, Set channels, - Resolution resolution) throws OpenemsNamedException { + Resolution resolution, String measurement) throws OpenemsNamedException { // handle empty call if (channels.isEmpty()) { return new TreeMap<>(); } - return this.queryProxy.queryHistoricData(this.getInfluxConnection(), this.bucket, influxEdgeId, fromDate, - toDate, channels, resolution); + return this.queryProxy.queryHistoricData(this.getInfluxConnection(), this.bucket, measurement, influxEdgeId, + fromDate, toDate, channels, resolution); + } + + /** + * Queries the first valid values before the given date. + * + * @param influxEdgeId the unique, numeric Edge-ID; or Empty to query all Edges + * @param date the date + * @param channels the Channels to query + * @param measurement the measurement + * @return the values mapped to their channel + * @throws OpenemsNamedException on error + */ + public SortedMap queryFirstValueBefore(// + Optional influxEdgeId, // + ZonedDateTime date, // + Set channels, // + String measurement // + ) throws OpenemsNamedException { + if (channels.isEmpty()) { + return new TreeMap<>(); + } + + return this.queryProxy.queryFirstValueBefore(// + this.bucket, // + this.getInfluxConnection(), // + measurement, // + influxEdgeId, // + date, // + channels// + ); } /** * Actually write the Point to InfluxDB. * * @param point the InfluxDB Point - * @throws OpenemsException on error */ public void write(Point point) { + this.write(point, this.defaultWriteParameters); + } + + /** + * Actually write the Point to InfluxDB. + * + * @param point the InfluxDB Point + * @param writeParameters the {@link WriteParameters} of the written point. The + * {@link WriteParameters} had to be passed in the + * constructor + */ + public void write(Point point, WriteParameters writeParameters) { + if (!point.hasFields()) { + return; + } if (this.isReadOnly) { - this.log.info("Read-Only-Mode is activated. Not writing points: " - + StringUtils.toShortString(point.toLineProtocol(), 100)); return; } - this.pointsQueue.offer(point); + final var mergePointsWorker = this.mergePointsWorkerByWriteParameters.get(writeParameters); + if (mergePointsWorker == null) { + this.log.info("Unknown write parameters: " + writeParameters); + return; + } + mergePointsWorker.offer(point); + } + + /** + * Gets the edges which already have the available since field set. Mapped from + * edgeId to timestamp of availableSince. The timestamp should be in epoch + * seconds. + * + * @return the map, where the first key is the edge id the second key is the + * channel and the value is the available since timestamp in epoch + * seconds + * @throws OpenemsNamedException on error + */ + public Map> queryAvailableSince() throws OpenemsNamedException { + return this.queryProxy.queryAvailableSince(this.getInfluxConnection(), this.bucket); + } + + /** + * Builds a {@link Point} which set the + * {@link QueryProxy.AVAILABLE_SINCE_COLUMN_NAME} field to the new value. + * + * @param influxEdgeId the id of the edge + * @param availableSinceTimestamp the new timestamp in epoch seconds + * @param channel the channels + * @return the {@link Point} + */ + public static Point buildUpdateAvailableSincePoint(// + int influxEdgeId, // + String channel, // + long availableSinceTimestamp // + ) { + return Point.measurement(QueryProxy.AVAILABLE_SINCE_MEASUREMENT) // + .addTag(OpenemsOEM.INFLUXDB_TAG, String.valueOf(influxEdgeId)) // + .addTag(QueryProxy.CHANNEL_TAG, channel) // + .time(0, WritePrecision.S) // + .addField(QueryProxy.AVAILABLE_SINCE_COLUMN_NAME, availableSinceTimestamp); + } + + public boolean isSafeWrite() { + return this.safeWrite; + } + + /** + * Parses the number of an Edge from its name string. + * + *

    + * e.g. translates "edge0" to "0". + * + * @param name the edge name + * @return the number + * @throws OpenemsException on error + */ + public static Integer parseNumberFromName(String name) throws OpenemsException { + try { + var matcher = NAME_NUMBER_PATTERN.matcher(name); + if (matcher.find()) { + var nameNumberString = matcher.group(1); + return Integer.parseInt(nameNumberString); + } + } catch (NullPointerException e) { + /* ignore */ + } + throw new OpenemsException("Unable to parse number from name [" + name + "]"); } } diff --git a/io.openems.shared.influxdb/src/io/openems/shared/influxdb/MergePointsWorker.java b/io.openems.shared.influxdb/src/io/openems/shared/influxdb/MergePointsWorker.java index 3428fad8f77..bf0db1011ce 100644 --- a/io.openems.shared.influxdb/src/io/openems/shared/influxdb/MergePointsWorker.java +++ b/io.openems.shared.influxdb/src/io/openems/shared/influxdb/MergePointsWorker.java @@ -1,63 +1,34 @@ package io.openems.shared.influxdb; -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.influxdb.client.write.Point; -import io.openems.common.worker.AbstractImmediateWorker; - -public class MergePointsWorker extends AbstractImmediateWorker { - - private static final int MAX_POINTS_PER_WRITE = 1_000; - private static final int MAX_AGGREGATE_WAIT = 10; // [s] - - private final Logger log = LoggerFactory.getLogger(MergePointsWorker.class); - - private final InfluxConnector parent; - private final Consumer onWriteError; - - public MergePointsWorker(InfluxConnector parent, Consumer onWriteError) { - this.parent = parent; - this.onWriteError = onWriteError; - } - - @Override - protected void forever() throws InterruptedException { - /* - * Merge Points. Wait max 10 seconds in total. - */ - final Instant maxWait = Instant.now().plusSeconds(MAX_AGGREGATE_WAIT); - List points = new ArrayList<>(MAX_POINTS_PER_WRITE); - for (int i = 0; i < MAX_POINTS_PER_WRITE; i++) { - var point = this.parent.pointsQueue.poll(MAX_AGGREGATE_WAIT, TimeUnit.SECONDS); - if (point == null) { - break; - } - points.add(point); - if (Instant.now().isAfter(maxWait)) { - break; - } - } - /* - * Write points async. - */ - if (!points.isEmpty()) { - this.parent.executor.execute(() -> { - try { - this.parent.getInfluxConnection().writeApi.writePoints(points); - } catch (Throwable t) { - this.log.warn("Unable to write points. " + t.getMessage()); - this.onWriteError.accept(t); - } - }); - } - } +public interface MergePointsWorker { + + /** + * Activates the worker. + */ + public void activate(); + + /** + * Deactivates the worker. + */ + public void deactivate(); + + /** + * Inserts the specified element into this queue if it is possible to do so + * immediately without violating capacity restrictions, returning true upon + * success and false if no space is currently available. + * + * @param point the {@link Point} to add + * @return true if the point was added to this queue, else false + */ + public boolean offer(Point point); + + /** + * Simple debug log string. + * + * @return the debug string + */ + public String debugLog(); } diff --git a/io.openems.shared.influxdb/src/io/openems/shared/influxdb/SafeMergePointsWorker.java b/io.openems.shared.influxdb/src/io/openems/shared/influxdb/SafeMergePointsWorker.java new file mode 100644 index 00000000000..aeb5fbbe2b4 --- /dev/null +++ b/io.openems.shared.influxdb/src/io/openems/shared/influxdb/SafeMergePointsWorker.java @@ -0,0 +1,50 @@ +package io.openems.shared.influxdb; + +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import com.influxdb.client.write.Point; +import com.influxdb.client.write.WriteParameters; +import com.influxdb.exceptions.BadRequestException; + +import io.openems.shared.influxdb.SafeMergePointsWorker.WritePoint; + +public class SafeMergePointsWorker extends AbstractMergePointsWorker implements MergePointsWorker { + + public SafeMergePointsWorker(InfluxConnector parent, String name, WriteParameters writeParameters, + Consumer onWriteError) { + super(parent, name, writeParameters, onWriteError); + } + + public static class WritePoint { + public final Point point; + private int failedCountDown = 3; + + public WritePoint(Point point) { + super(); + this.point = point; + } + } + + @Override + public boolean offer(Point point) { + return this.offer(new WritePoint(point)); + } + + @Override + protected List writePoints(List points) { + return points.stream().map(t -> t.point) // + .collect(Collectors.toList()); + } + + @Override + protected void onWriteError(Throwable t, List points) { + super.onWriteError(t, points); + points.stream() // + .peek(w -> w.failedCountDown--) // + .filter(w -> w.failedCountDown > 0) // + .forEach(this::offer); + } + +} diff --git a/io.openems.shared.influxdb/src/io/openems/shared/influxdb/proxy/FluxProxy.java b/io.openems.shared.influxdb/src/io/openems/shared/influxdb/proxy/FluxProxy.java index dfb0b77c3a3..5381cf80a36 100644 --- a/io.openems.shared.influxdb/src/io/openems/shared/influxdb/proxy/FluxProxy.java +++ b/io.openems.shared.influxdb/src/io/openems/shared/influxdb/proxy/FluxProxy.java @@ -1,11 +1,10 @@ package io.openems.shared.influxdb.proxy; -import static io.openems.shared.influxdb.InfluxConnector.MEASUREMENT; - import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; import java.time.temporal.TemporalAdjusters; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.SortedMap; @@ -27,6 +26,7 @@ import io.openems.common.exceptions.OpenemsException; import io.openems.common.timedata.Resolution; import io.openems.common.types.ChannelAddress; +import io.openems.common.utils.CollectorUtils; import io.openems.shared.influxdb.InfluxConnector.InfluxConnection; /** @@ -37,36 +37,96 @@ public class FluxProxy extends QueryProxy { private static final Logger LOG = LoggerFactory.getLogger(FluxProxy.class); @Override - public SortedMap queryHistoricEnergy(InfluxConnection influxConnection, String bucket, - Optional influxEdgeId, ZonedDateTime fromDate, ZonedDateTime toDate, Set channels) - throws OpenemsNamedException { - var query = this.buildHistoricEnergyQuery(bucket, influxEdgeId, fromDate, toDate, channels); + public SortedMap queryHistoricEnergy(// + InfluxConnection influxConnection, // + String bucket, // + String measurement, // + Optional influxEdgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels // + ) throws OpenemsNamedException { + var query = this.buildHistoricEnergyQuery(bucket, measurement, influxEdgeId, fromDate, toDate, channels); var queryResult = this.executeQuery(influxConnection, query); return convertHistoricEnergyResult(query, queryResult); } @Override - public SortedMap> queryHistoricData( - InfluxConnection influxConnection, String bucket, Optional influxEdgeId, ZonedDateTime fromDate, - ZonedDateTime toDate, Set channels, Resolution resolution) throws OpenemsNamedException { - var query = this.buildHistoricDataQuery(bucket, influxEdgeId, fromDate, toDate, channels, resolution); + public SortedMap queryHistoricEnergySingleValueInDay(InfluxConnection influxConnection, + String bucket, String measurement, Optional influxEdgeId, ZonedDateTime fromDate, + ZonedDateTime toDate, Set channels) throws OpenemsNamedException { + // TODO Auto-generated method stub + return null; + } + + @Override + public SortedMap> queryHistoricData(// + InfluxConnection influxConnection, // + String bucket, // + String measurement, // + Optional influxEdgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels, // + Resolution resolution // + ) throws OpenemsNamedException { + var query = this.buildHistoricDataQuery(bucket, measurement, influxEdgeId, fromDate, toDate, channels, + resolution); var queryResult = this.executeQuery(influxConnection, query); return convertHistoricDataQueryResult(queryResult, fromDate, resolution); } @Override - public SortedMap> queryHistoricEnergyPerPeriod( - InfluxConnection influxConnection, String bucket, Optional influxEdgeId, ZonedDateTime fromDate, - ZonedDateTime toDate, Set channels, Resolution resolution) throws OpenemsNamedException { - var query = this.buildHistoricEnergyPerPeriodQuery(bucket, influxEdgeId, fromDate, toDate, channels, - resolution); + public SortedMap> queryHistoricEnergyPerPeriod(// + InfluxConnection influxConnection, // + String bucket, // + String measurement, // + Optional influxEdgeId, // + ZonedDateTime fromDate, ZonedDateTime toDate, // + Set channels, // + Resolution resolution // + ) throws OpenemsNamedException { + var query = this.buildHistoricEnergyPerPeriodQuery(bucket, measurement, influxEdgeId, fromDate, toDate, + channels, resolution); var queryResult = this.executeQuery(influxConnection, query); return convertHistoricDataQueryResult(queryResult, fromDate, resolution); } @Override - protected String buildHistoricDataQuery(String bucket, Optional influxEdgeId, ZonedDateTime fromDate, - ZonedDateTime toDate, Set channels, Resolution resolution) { + public SortedMap> queryRawHistoricEnergyPerPeriodSingleValueInDay( + InfluxConnection influxConnection, String bucket, String measurement, Optional influxEdgeId, + ZonedDateTime fromDate, ZonedDateTime toDate, Set channels, Resolution resolution) + throws OpenemsNamedException { + // TODO Auto-generated method stub + return null; + } + + @Override + public Map> queryAvailableSince(InfluxConnection influxConnection, String bucket) + throws OpenemsNamedException { + final var query = this.buildFetchAvailableSinceQuery(bucket); + final var queryResult = this.executeQuery(influxConnection, query); + return convertAvailableSinceQueryResult(queryResult); + } + + @Override + public SortedMap queryFirstValueBefore(String bucket, + InfluxConnection influxConnection, String measurement, Optional influxEdgeId, ZonedDateTime date, + Set channels) throws OpenemsNamedException { + // TODO Auto-generated method stub + return null; + } + + @Override + protected String buildHistoricDataQuery(// + String bucket, // + String measurement, // + Optional influxEdgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels, // + Resolution resolution // + ) { // remove 5 minutes to prevent shifted timeline var fromInstant = fromDate.toInstant().minus(5, ChronoUnit.MINUTES); @@ -74,7 +134,7 @@ protected String buildHistoricDataQuery(String bucket, Optional influxE Flux flux = Flux.from(bucket) // .range(fromInstant, toDate.toInstant()) // // TODO: TO_DATE is wrong, as it is inclusive - .filter(Restrictions.measurement().equal(MEASUREMENT)); + .filter(Restrictions.measurement().equal(measurement)); if (influxEdgeId.isPresent()) { flux = flux.filter(Restrictions.tag(OpenemsOEM.INFLUXDB_TAG).equal(influxEdgeId.get().toString())); @@ -86,15 +146,21 @@ protected String buildHistoricDataQuery(String bucket, Optional influxE } @Override - protected String buildHistoricEnergyQuery(String bucket, Optional influxEdgeId, ZonedDateTime fromDate, - ZonedDateTime toDate, Set channels) { + protected String buildHistoricEnergyQuery(// + String bucket, // + String measurement, // + Optional influxEdgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels // + ) { // prepare query var builder = new StringBuilder() // .append("data = from(bucket: \"").append(bucket).append("\")") // .append("|> range(start: ").append(fromDate.toInstant()) // .append(", stop: ").append(toDate.toInstant()).append(")") // - .append("|> filter(fn: (r) => r._measurement == \"").append(MEASUREMENT).append("\")"); + .append("|> filter(fn: (r) => r._measurement == \"").append(measurement).append("\")"); if (influxEdgeId.isPresent()) { builder.append("|> filter(fn: (r) => r." + OpenemsOEM.INFLUXDB_TAG + " == \"" + influxEdgeId.get() + "\")"); @@ -113,8 +179,23 @@ protected String buildHistoricEnergyQuery(String bucket, Optional influ } @Override - protected String buildHistoricEnergyPerPeriodQuery(String bucket, Optional influxEdgeId, - ZonedDateTime fromDate, ZonedDateTime toDate, Set channels, Resolution resolution) { + protected String buildHistoricEnergyQuerySingleValueInDay(String bucket, String measurement, + Optional influxEdgeId, ZonedDateTime fromDate, ZonedDateTime toDate, Set channels) + throws OpenemsException { + // TODO Auto-generated method stub + return null; + } + + @Override + protected String buildHistoricEnergyPerPeriodQuery(// + String bucket, // + String measurement, // + Optional influxEdgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels, // + Resolution resolution // + ) { if (resolution.getUnit().equals(ChronoUnit.MONTHS)) { fromDate = fromDate.with(TemporalAdjusters.firstDayOfMonth()); if (!toDate.equals(toDate.with(TemporalAdjusters.firstDayOfMonth()))) { @@ -125,7 +206,7 @@ protected String buildHistoricEnergyPerPeriodQuery(String bucket, Optional influxEdgeId, ZonedDateTime fromDate, ZonedDateTime toDate, Set channels, + Resolution resolution) throws OpenemsException { + // TODO Auto-generated method stub + return null; + } + + @Override + protected String buildFetchAvailableSinceQuery(// + String bucket // + ) { + return Flux.from(bucket) // + .range(0L, 1L) // + .filter(Restrictions.measurement().equal(QueryProxy.AVAILABLE_SINCE_MEASUREMENT)) // + .toString(); + } + + @Override + protected String buildFetchFirstValueBefore(String bucket, String measurement, Optional influxEdgeId, + ZonedDateTime date, Set channels) { + // TODO Auto-generated method stub + return null; + } + /** * Converts given {@link Set} of {@link ChannelAddress} to {@link Restrictions} * separated by or. @@ -277,4 +383,19 @@ private static SortedMap convertHistoricEnergyResul return map; } + + private static Map> convertAvailableSinceQueryResult(List queryResult) { + if (queryResult == null || queryResult.isEmpty()) { + return new TreeMap<>(); + } + return queryResult.stream() // + .flatMap(t -> t.getRecords().stream()) // + .collect(CollectorUtils.toDoubleMap(// + record -> Integer.parseInt(// + (String) record.getValueByKey(OpenemsOEM.INFLUXDB_TAG) // + ), // + record -> (String) record.getValueByKey(QueryProxy.CHANNEL_TAG), // + record -> (Long) record.getValue()) // + ); + } } \ No newline at end of file diff --git a/io.openems.shared.influxdb/src/io/openems/shared/influxdb/proxy/InfluxQlProxy.java b/io.openems.shared.influxdb/src/io/openems/shared/influxdb/proxy/InfluxQlProxy.java index 661b7abbf48..b4326931ef4 100644 --- a/io.openems.shared.influxdb/src/io/openems/shared/influxdb/proxy/InfluxQlProxy.java +++ b/io.openems.shared.influxdb/src/io/openems/shared/influxdb/proxy/InfluxQlProxy.java @@ -2,10 +2,17 @@ import java.time.Instant; import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; +import java.util.function.BiFunction; +import java.util.function.Function; import java.util.stream.Collectors; import org.slf4j.Logger; @@ -22,9 +29,13 @@ import io.openems.common.OpenemsOEM; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.timedata.DurationUnit; import io.openems.common.timedata.Resolution; import io.openems.common.types.ChannelAddress; +import io.openems.common.utils.CollectorUtils; +import io.openems.common.utils.JsonUtils; import io.openems.common.utils.StringUtils; +import io.openems.shared.influxdb.DbDataUtils; import io.openems.shared.influxdb.InfluxConnector.InfluxConnection; /** @@ -36,47 +47,208 @@ public class InfluxQlProxy extends QueryProxy { private static final Logger LOG = LoggerFactory.getLogger(InfluxQlProxy.class); @Override - public SortedMap queryHistoricEnergy(InfluxConnection influxConnection, String bucket, - Optional influxEdgeId, ZonedDateTime fromDate, ZonedDateTime toDate, Set channels) - throws OpenemsNamedException { - var query = this.buildHistoricEnergyQuery(bucket, influxEdgeId, fromDate, toDate, channels); + public SortedMap queryHistoricEnergy(// + InfluxConnection influxConnection, // + String bucket, // + String measurement, // + Optional influxEdgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels // + ) throws OpenemsNamedException { + var query = this.buildHistoricEnergyQuery(bucket, measurement, influxEdgeId, fromDate, toDate, channels); var queryResult = this.executeQuery(influxConnection, bucket, query); return convertHistoricEnergyResult(queryResult, influxEdgeId, channels); } @Override - public SortedMap> queryHistoricData( - InfluxConnection influxConnection, String bucket, Optional influxEdgeId, ZonedDateTime fromDate, - ZonedDateTime toDate, Set channels, Resolution resolution) throws OpenemsNamedException { - var query = this.buildHistoricDataQuery(bucket, influxEdgeId, fromDate, toDate, channels, resolution); + public SortedMap queryHistoricEnergySingleValueInDay(// + InfluxConnection influxConnection, // + String bucket, // + String measurement, // + Optional influxEdgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels // + ) throws OpenemsNamedException { + var query = this.buildHistoricEnergyQuerySingleValueInDay(bucket, measurement, influxEdgeId, fromDate, toDate, + channels); var queryResult = this.executeQuery(influxConnection, bucket, query); - return convertHistoricDataQueryResult(queryResult, fromDate); + var firstResult = convertHistoricEnergyResultSingleValueInDay(queryResult, influxEdgeId, channels); + if (firstResult == null) { + // return a map which has for every channel JsonNull.INSTANCE + return channels.stream() // + .collect(Collectors.toMap(Function.identity(), // + t -> JsonNull.INSTANCE, // + (t, u) -> u, TreeMap::new)); + } + + var channelsWithoutOldValues = firstResult.entrySet().stream() // + .filter(t -> t.getValue().first().isJsonNull() && !t.getValue().second().isJsonNull()) // + .map(Entry::getKey) // + .collect(Collectors.toSet()); + + final var beforeValues = this.queryFirstValueBefore(bucket, influxConnection, measurement, influxEdgeId, + fromDate, channelsWithoutOldValues); + + return mergeEnergyValues(firstResult, beforeValues); } @Override - public SortedMap> queryHistoricEnergyPerPeriod( - InfluxConnection influxConnection, String bucket, Optional influxEdgeId, ZonedDateTime fromDate, - ZonedDateTime toDate, Set channels, Resolution resolution) throws OpenemsNamedException { - var query = this.buildHistoricEnergyPerPeriodQuery(bucket, influxEdgeId, fromDate, toDate, channels, + public SortedMap> queryHistoricData(// + InfluxConnection influxConnection, // + String bucket, // + String measurement, // + Optional influxEdgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels, // + Resolution resolution // + ) throws OpenemsNamedException { + var query = this.buildHistoricDataQuery(bucket, measurement, influxEdgeId, fromDate, toDate, channels, resolution); var queryResult = this.executeQuery(influxConnection, bucket, query); - return convertHistoricDataQueryResult(queryResult, fromDate); + return convertHistoricDataQueryResult(queryResult, fromDate, resolution, channels, new Average()); + } + + // TODO maybe remove? + private static class Average implements BiFunction { + + private int count = 0; + + @Override + public JsonElement apply(JsonElement first, JsonElement second) { + if (!first.isJsonPrimitive() || !second.isJsonPrimitive() // + || !first.getAsJsonPrimitive().isNumber() || !second.getAsJsonPrimitive().isNumber()) { + return second; + } + final var numberFirst = first.getAsNumber().longValue(); + final var numberSecond = first.getAsNumber().longValue(); + final var result = (numberFirst * this.count + numberSecond) / ++this.count; + return new JsonPrimitive(result); + } + + } + + @Override + public SortedMap> queryHistoricEnergyPerPeriod(// + InfluxConnection influxConnection, // + String bucket, // + String measurement, // + Optional influxEdgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels, // + Resolution resolution // + ) throws OpenemsNamedException { + var query = this.buildHistoricEnergyPerPeriodQuery(bucket, measurement, influxEdgeId, fromDate, toDate, + channels, resolution); + var queryResult = this.executeQuery(influxConnection, bucket, query); + var result = convertHistoricDataQueryResult(queryResult, fromDate, resolution, channels, InfluxQlProxy::last); + return DbDataUtils.normalizeTable(result, channels, resolution, fromDate, toDate); + } + + @Override + public SortedMap> queryRawHistoricEnergyPerPeriodSingleValueInDay(// + InfluxConnection influxConnection, // + String bucket, // + String measurement, // + Optional influxEdgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels, // + Resolution resolution // + ) throws OpenemsNamedException { + var query = this.buildHistoricEnergyPerPeriodQuerySingleValueInDay(bucket, measurement, influxEdgeId, fromDate, + toDate, channels, resolution); + var queryResult = this.executeQuery(influxConnection, bucket, query); + + final var result = convertHistoricDataQueryResultSingleValue(queryResult, fromDate, resolution, channels, + InfluxQlProxy::last); + + if (result == null || result.isEmpty()) { + return Collections.emptySortedMap(); + } + + final Set channelsForBeforeValues; + if (!result.firstKey().isBefore(fromDate)) { + channelsForBeforeValues = channels; + } else { + final var first = result.get(result.firstKey()); + + channelsForBeforeValues = first.entrySet().stream() // + .filter(entry -> entry.getValue().isJsonNull()) // + .map(Entry::getKey) // + .collect(Collectors.toSet()); + } + + if (!channelsForBeforeValues.isEmpty()) { + final var beforeValues = this.queryFirstValueBefore(bucket, influxConnection, measurement, influxEdgeId, + fromDate, channelsForBeforeValues); + + if (result.firstKey().isBefore(fromDate)) { + result.put(result.firstKey(), beforeValues); + } else { + result.put(fromDate.minusDays(1), beforeValues); + } + } + + return result; + } + + private static JsonElement last(JsonElement first, JsonElement second) { + return second; + } + + @Override + public Map> queryAvailableSince(// + InfluxConnection influxConnection, // + String bucket // + ) throws OpenemsNamedException { + final var query = this.buildFetchAvailableSinceQuery(bucket); + final var queryResult = this.executeQuery(influxConnection, bucket, query); + return convertAvailableSinceResult(queryResult); } @Override - protected String buildHistoricDataQuery(String bucket, Optional influxEdgeId, ZonedDateTime fromDate, - ZonedDateTime toDate, Set channels, Resolution resolution) throws OpenemsException { + public SortedMap queryFirstValueBefore(String bucket, // + InfluxConnection influxConnection, // + String measurement, // + Optional influxEdgeId, // + ZonedDateTime date, // + Set channels // + ) throws OpenemsNamedException { + if (channels.isEmpty()) { + return Collections.emptySortedMap(); + } + final var query = this.buildFetchFirstValueBefore(bucket, measurement, influxEdgeId, date, channels); + final var queryResult = this.executeQuery(influxConnection, bucket, query); + return convertHistoricEnergyResultRaw(queryResult, influxEdgeId, channels); + } + + @Override + protected String buildHistoricDataQuery(// + String bucket, // + String measurement, // + Optional influxEdgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels, // + Resolution resolution // + ) throws OpenemsException { // Prepare query string var b = new StringBuilder("SELECT ") // .append(channels.stream() // .map(c -> "MEAN(\"" + c.toString() + "\") AS \"" + c.toString() + "\"") // .collect(Collectors.joining(", "))) // - .append(" FROM data WHERE "); + .append(" FROM ") // + .append(measurement) // + .append(" WHERE "); if (influxEdgeId.isPresent()) { b.append(OpenemsOEM.INFLUXDB_TAG + " = '" + influxEdgeId.get() + "' AND "); } b // - .append("time > ") // + .append("time >= ") // .append(String.valueOf(fromDate.toEpochSecond())) // .append("s") // .append(" AND time < ") // @@ -84,22 +256,28 @@ protected String buildHistoricDataQuery(String bucket, Optional influxE .append("s") // .append(" GROUP BY time(") // .append(resolution.toSeconds()) // - .append("s) fill(null)"); - - // Execute query + .append("s)"); return b.toString(); } @Override - public String buildHistoricEnergyQuery(String bucket, Optional influxEdgeId, ZonedDateTime fromDate, - ZonedDateTime toDate, Set channels) throws OpenemsException { + public String buildHistoricEnergyQuery(// + String bucket, // + String measurement, // + Optional influxEdgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels // + ) throws OpenemsException { // Prepare query string var b = new StringBuilder("SELECT ") // .append(channels.stream() // .map(c -> "LAST(\"" + c.toString() + "\") - FIRST(\"" + c.toString() + "\") AS \"" + c.toString() + "\"") // .collect(Collectors.joining(", "))) // - .append(" FROM data WHERE "); + .append(" FROM ") // + .append(measurement) // + .append(" WHERE "); if (influxEdgeId.isPresent()) { b.append(OpenemsOEM.INFLUXDB_TAG + " = '" + influxEdgeId.get() + "' AND "); } @@ -114,31 +292,162 @@ public String buildHistoricEnergyQuery(String bucket, Optional influxEd } @Override - public String buildHistoricEnergyPerPeriodQuery(String bucket, Optional influxEdgeId, - ZonedDateTime fromDate, ZonedDateTime toDate, Set channels, Resolution resolution) - throws OpenemsException { + protected String buildHistoricEnergyQuerySingleValueInDay(// + String bucket, // + String measurement, // + Optional influxEdgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels // + ) throws OpenemsException { + fromDate = fromDate.minusDays(1); + // Prepare query string + var b = new StringBuilder("SELECT ") // + .append(channels.stream() // + .map(c -> "LAST(\"" + c.toString() + "\") AS \"LAST(" + c.toString() + ")\"" + ", FIRST(\"" // + + c.toString() + "\") AS \"FIRST(" + c.toString() + ")\"") // + .collect(Collectors.joining(", "))) // + .append(" FROM ") // + .append(measurement) // + .append(" WHERE "); + if (influxEdgeId.isPresent()) { + b.append(OpenemsOEM.INFLUXDB_TAG + " = '" + influxEdgeId.get() + "' AND "); + } + b // + .append("time >= ") // + .append(String.valueOf(fromDate.toEpochSecond())) // + .append("s") // + .append(" AND time < ") // + .append(String.valueOf(toDate.toEpochSecond())) // + .append("s"); + return b.toString(); + } + + @Override + public String buildHistoricEnergyPerPeriodQuery(// + String bucket, // + String measurement, // + Optional influxEdgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels, // + Resolution resolution // + ) throws OpenemsException { // Prepare query string var b = new StringBuilder("SELECT ") // .append(channels.stream() // .map(c -> "NON_NEGATIVE_DIFFERENCE(LAST(\"" + c.toString() + "\")) AS \"" + c.toString() + "\"") // .collect(Collectors.joining(", "))) // - .append(" FROM data WHERE "); + .append(" FROM ") // + .append(measurement) // + .append(" WHERE "); if (influxEdgeId.isPresent()) { b.append(OpenemsOEM.INFLUXDB_TAG + " = '" + influxEdgeId.get() + "' AND "); } + + final long res; + if (resolution.getUnit().isDurationEstimated()) { + res = new Resolution(1, ChronoUnit.DAYS).toSeconds(); + } else { + res = resolution.toSeconds(); + } b // - .append("time > ") // + .append("time >= ") // .append(String.valueOf(fromDate.toEpochSecond())) // .append("s") // .append(" AND time < ") // .append(String.valueOf(toDate.toEpochSecond())) // + .append("s GROUP BY time(") // + .append(res) // + .append("s)"); + return b.toString(); + } + + @Override + protected String buildHistoricEnergyPerPeriodQuerySingleValueInDay(// + String bucket, // + String measurement, // + Optional influxEdgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels, // + Resolution resolution // + ) throws OpenemsException { + fromDate = fromDate.minusDays(1); + // Prepare query string + var b = new StringBuilder("SELECT ") // + .append(channels.stream() // + .map(c -> "LAST(\"" + c.toString() + "\") AS \"" + c.toString() + "\"") // + .collect(Collectors.joining(", "))) // + .append(" FROM ") // + .append(measurement) // + .append(" WHERE "); + if (influxEdgeId.isPresent()) { + b.append(OpenemsOEM.INFLUXDB_TAG + " = '" + influxEdgeId.get() + "' AND "); + } + + final long res; + if (resolution.getUnit().isDurationEstimated()) { + // group by hours to still get the right time stamp if we would group by days it + // would use UTC and the result could be shifted by one day + res = new Resolution(1, ChronoUnit.HOURS).toSeconds(); + } else { + res = resolution.toSeconds(); + } + b.append("time >= ") // + .append(fromDate.toEpochSecond()) // .append("s") // - .append(" GROUP BY time(") // - .append(resolution.toSeconds()) // - .append("s) fill(null)"); + .append(" AND time < ") // + .append(toDate.toEpochSecond()) // + .append("s GROUP BY time(") // + .append(res) // + .append("s) fill(none)"); // return b.toString(); } + @Override + protected String buildFetchAvailableSinceQuery(// + String bucket // + ) { + return new StringBuilder("SELECT ") // + .append(OpenemsOEM.INFLUXDB_TAG) // + .append(", ") // + .append(QueryProxy.CHANNEL_TAG) // + .append(", ") // + .append(QueryProxy.AVAILABLE_SINCE_COLUMN_NAME) // + .append(" FROM ") // + .append(QueryProxy.AVAILABLE_SINCE_MEASUREMENT) // + .toString(); + } + + @Override + protected String buildFetchFirstValueBefore(// + String bucket, // + String measurement, // + Optional influxEdgeId, // + ZonedDateTime date, // + Set channels // + ) { + final var builder = new StringBuilder("SELECT ") // + .append(channels.stream().map(channel -> "LAST(\"" + channel + "\") as \"" + channel + "\"") + .collect(Collectors.joining(", "))) + .append(" FROM ") // + .append(measurement) // + .append(" WHERE time < ") // + .append(date.toEpochSecond()) // + .append("s"); + + influxEdgeId.ifPresent(id -> { + builder.append(" AND ") // + .append(OpenemsOEM.INFLUXDB_TAG) // + .append(" = '") // + .append(id) // + .append("'"); + }); + + return builder.toString(); + } + private InfluxQLQueryResult executeQuery(InfluxConnection influxConnection, String bucket, String query) throws OpenemsException { this.assertQueryLimit(); @@ -162,25 +471,33 @@ private InfluxQLQueryResult executeQuery(InfluxConnection influxConnection, Stri /** * Converts the QueryResult of a Historic-Data query to a properly typed Table. * - * @param queryResult the Query-Result - * @param fromDate the From-Date + * @param queryResult the Query-Result + * @param fromDate the From-Date + * @param resolution the {@link Resolution} + * @param channels the channels + * @param aggregateFunction the aggregation function * @return the historic data as Map * @throws OpenemsException on error */ - private static SortedMap> convertHistoricDataQueryResult( - InfluxQLQueryResult queryResult, ZonedDateTime fromDate) throws OpenemsNamedException { + private static SortedMap> convertHistoricDataQueryResult(// + InfluxQLQueryResult queryResult, // + ZonedDateTime fromDate, // + Resolution resolution, // + Set channels, // + BiFunction aggregateFunction // + ) throws OpenemsNamedException { if (queryResult == null) { - throw new OpenemsException("Historic data values are not available. QueryResult is null"); + return null; } - SortedMap> table = new TreeMap<>(); + final SortedMap> table = new TreeMap<>(); for (var result : queryResult.getResults()) { var seriess = result.getSeries(); if (seriess != null) { for (var series : seriess) { // add all data for (var record : series.getValues()) { - SortedMap tableRow = new TreeMap<>(); + // get timestamp var timestampInstant = Instant .ofEpochMilli(Long.parseLong((String) record.getValueByKey("time"))); @@ -189,17 +506,164 @@ private static SortedMap> // InfluxQL sometimes gives too early timestamps -> ignore continue; } + + SortedMap existingData = null; + if (resolution.getUnit() == ChronoUnit.MONTHS) { + for (var entry : table.entrySet()) { + final var date = entry.getKey(); + if (date.getMonth() == timestamp.getMonth() // + && date.getYear() == timestamp.getYear()) { + existingData = entry.getValue(); + break; + } + } + } + SortedMap tableRow; + if (existingData != null) { + tableRow = existingData; + } else { + tableRow = new TreeMap<>(); + table.put(timestamp, tableRow); + } + for (var column : series.getColumns().keySet()) { if (column.equals("time")) { continue; } // Note: ignoring index '0' here as it is the 'timestamp' + final var channel = ChannelAddress.fromString(column); + var value = convertToJsonElement(record.getValueByKey(column)); + final var existingValue = tableRow.get(channel); + if (existingValue != null) { + value = aggregateFunction.apply(existingValue, value); + } + + tableRow.put(ChannelAddress.fromString(column), value); + } + } + } + } + } + return table; + } + + private static SortedMap> convertHistoricDataQueryResultSingleValue(// + InfluxQLQueryResult queryResult, // + ZonedDateTime fromDate, // + Resolution resolution, // + Set channels, // + BiFunction aggregateFunction // + ) throws OpenemsNamedException { + if (queryResult == null) { + return null; + } + + return queryResult.getResults().stream() // + .flatMap(r -> r.getSeries().stream()) // + .flatMap(s -> s.getValues().stream()) // + .>mapMulti((r, + f) -> channels.forEach(c -> f.accept( + new Pair(c, r)))) + .collect(Collectors.groupingBy(t -> { + var timestampInstant = Instant + .ofEpochMilli(Long.parseLong((String) t.second().getValueByKey("time"))); + var zonedDateTime = ZonedDateTime.ofInstant(timestampInstant, fromDate.getZone()); + if (resolution.getUnit() == ChronoUnit.MONTHS) { + zonedDateTime = zonedDateTime.withDayOfMonth(1); + } + return zonedDateTime.truncatedTo(DurationUnit.ofDays(1)); + }, TreeMap::new, Collectors.toMap(Pair::first, r -> { + final var channel = r.first(); + final var value = convertToJsonElement(r.second().getValueByKey(channel.toString())); + + if (!JsonUtils.isNumber(value)) { + return JsonNull.INSTANCE; + } + + return value; + }, (t, u) -> aggregateFunction.apply(t, u), TreeMap::new))); + } + + private static JsonElement convertToJsonElement(Object valueObj) { + if (valueObj == null) { + return JsonNull.INSTANCE; + } + if (valueObj instanceof Number) { + return new JsonPrimitive((Number) valueObj); + } + + final String str; + if (valueObj instanceof String) { + str = (String) valueObj; + } else { + str = valueObj.toString(); + } + if (str.isEmpty()) { + return JsonNull.INSTANCE; + } + if (StringUtils.matchesFloatPattern(str)) { + return new JsonPrimitive(Double.parseDouble(str)); + } + if (StringUtils.matchesIntegerPattern(str)) { + return new JsonPrimitive(Long.parseLong(str)); + } + + return new JsonPrimitive(valueObj.toString()); + } + + private static SortedMap> convertHistoricEnergyResultSingleValueInDay(// + InfluxQLQueryResult queryResult, // + Optional influxEdgeId, // + Set channels // + ) throws OpenemsNamedException { + if (queryResult == null) { + return null; + } + + return queryResult.getResults().stream() // + .flatMap(t -> t.getSeries().stream()) // + .flatMap(t -> t.getValues().stream()) // + .>mapMulti((r, + f) -> channels.forEach(c -> f.accept( + new Pair(c, r)))) + .collect(Collectors.toMap(Pair::first, t -> { + final var channel = t.first(); + final var record = t.second(); + var first = record.getValueByKey("FIRST(" + channel.toString() + ")"); + final var last = record.getValueByKey("LAST(" + channel.toString() + ")"); + if (Objects.equals(first, last)) { + first = null; + } + return new Pair(convertToJsonElement(first), convertToJsonElement(last)); + }, (t, u) -> u, TreeMap::new)); + } + + // TODO move to common + public static record Pair(A first, B second) { + + } + + private static SortedMap convertHistoricEnergyResultRaw( + InfluxQLQueryResult queryResult, Optional influxEdgeId, Set channels) + throws OpenemsNamedException { + if (queryResult == null) { + return null; + } + + return queryResult.getResults().stream() // + .flatMap(r -> r.getSeries().stream()) // + .>mapMulti((s, m) -> { + for (var record : s.getValues()) { + for (var column : s.getColumns().keySet()) { + if (column.equals("time")) { + continue; + } var valueObj = record.getValueByKey(column); JsonElement value; if (valueObj == null) { value = JsonNull.INSTANCE; - } else if (valueObj instanceof Number) { - value = new JsonPrimitive((Number) valueObj); + } else if (valueObj instanceof Number n) { + value = new JsonPrimitive(n); } else { final String str; if (valueObj instanceof String) { @@ -212,19 +676,21 @@ private static SortedMap> } else if (StringUtils.matchesFloatPattern(str)) { value = new JsonPrimitive(Double.parseDouble(str)); } else if (StringUtils.matchesIntegerPattern(str)) { - value = new JsonPrimitive(Integer.parseInt(str)); + value = new JsonPrimitive(Long.parseLong(str)); } else { value = new JsonPrimitive(valueObj.toString()); } } - tableRow.put(ChannelAddress.fromString(column), value); + try { + m.accept(new Pair<>(ChannelAddress.fromString(column), value)); + } catch (OpenemsNamedException e) { + e.printStackTrace(); + } } - table.put(timestamp, tableRow); } - } - } - } - return table; + }) // + .filter(Objects::nonNull) // + .collect(Collectors.toMap(Pair::first, Pair::second, (t, u) -> u, TreeMap::new)); } /** @@ -239,7 +705,7 @@ private static SortedMap> private static SortedMap convertHistoricEnergyResult(InfluxQLQueryResult queryResult, Optional influxEdgeId, Set channels) throws OpenemsNamedException { if (queryResult == null) { - throw new OpenemsException("Energy values are not available. QueryResult is null"); + return null; } SortedMap map = new TreeMap<>(); @@ -271,7 +737,7 @@ private static SortedMap convertHistoricEnergyResul } else if (StringUtils.matchesFloatPattern(str)) { value = assertPositive(Double.parseDouble(str), influxEdgeId, channels); } else if (StringUtils.matchesIntegerPattern(str)) { - value = assertPositive(Integer.parseInt(str), influxEdgeId, channels); + value = assertPositive(Long.parseLong(str), influxEdgeId, channels); } else { value = new JsonPrimitive(valueObj.toString()); } @@ -300,9 +766,58 @@ private static SortedMap convertHistoricEnergyResul return map; } + private static Map> convertAvailableSinceResult(InfluxQLQueryResult queryResult) + throws OpenemsNamedException { + if (queryResult == null || queryResult.getResults() == null || queryResult.getResults().isEmpty()) { + return new TreeMap<>(); + } + return queryResult.getResults().stream() // + .flatMap(result -> result.getSeries().stream()) // + .flatMap(series -> series.getValues().stream()) // + .collect(CollectorUtils.toDoubleMap(// + record -> Integer.parseInt(// + (String) record.getValueByKey(OpenemsOEM.INFLUXDB_TAG) // + ), // + record -> (String) record.getValueByKey(QueryProxy.CHANNEL_TAG), // + record -> Long.parseLong(// + (String) record.getValueByKey(QueryProxy.AVAILABLE_SINCE_COLUMN_NAME) // + )) // + ); + } + + private static SortedMap mergeEnergyValues(// + SortedMap> firstResult, // + SortedMap beforeValues // + ) { + return firstResult.entrySet().stream() // + .collect(Collectors.toMap(Entry::getKey, t -> { + final var channel = t.getKey(); + final var pair = t.getValue(); + + if (pair.second().isJsonNull()) { + return JsonNull.INSTANCE; + } + var first = t.getValue().first(); + var last = t.getValue().second(); + if (first.isJsonNull() && beforeValues != null) { + first = beforeValues.get(channel); + } + if (first == null || first.isJsonNull()) { + return last; + } + if (!JsonUtils.isNumber(last)) { + return last; + } + if (!JsonUtils.isNumber(first)) { + return last; + } + return new JsonPrimitive(last.getAsDouble() - first.getAsDouble()); + }, (t, u) -> u, TreeMap::new)); + } + private static JsonElement assertPositive(Number number, Optional influxEdgeId, Set channels) { - if (number.intValue() < 0) { + if (number.longValue() < 0) { // do not consider negative values LOG.warn("Got negative Energy value [" + number + "] for [" + influxEdgeId.orElse(0) + "] " + channels.stream().map(ChannelAddress::toString).collect(Collectors.joining(","))); diff --git a/io.openems.shared.influxdb/src/io/openems/shared/influxdb/proxy/QueryProxy.java b/io.openems.shared.influxdb/src/io/openems/shared/influxdb/proxy/QueryProxy.java index 51f25268ec0..6d8f782dd34 100644 --- a/io.openems.shared.influxdb/src/io/openems/shared/influxdb/proxy/QueryProxy.java +++ b/io.openems.shared.influxdb/src/io/openems/shared/influxdb/proxy/QueryProxy.java @@ -1,6 +1,7 @@ package io.openems.shared.influxdb.proxy; import java.time.ZonedDateTime; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.SortedMap; @@ -17,6 +18,10 @@ public abstract class QueryProxy { + public static final String CHANNEL_TAG = "channel"; + public static final String AVAILABLE_SINCE_MEASUREMENT = "availableSince"; + public static final String AVAILABLE_SINCE_COLUMN_NAME = "available_since"; + /** * Builds a {@link QueryProxy} from a {@link QueryLanguageConfig}. * @@ -58,6 +63,7 @@ public static QueryProxy influxQl() { * @param influxConnection a Influx-Connection * @param bucket the bucket name; 'database/retentionPolicy' for * InfluxDB v1 + * @param measurement the influx measurement * @param influxEdgeId the Edge-ID * @param fromDate the From-Date * @param toDate the To-Date @@ -65,9 +71,40 @@ public static QueryProxy influxQl() { * @return the query result * @throws OpenemsNamedException on error */ - public abstract SortedMap queryHistoricEnergy(InfluxConnection influxConnection, - String bucket, Optional influxEdgeId, ZonedDateTime fromDate, ZonedDateTime toDate, - Set channels) throws OpenemsNamedException; + public abstract SortedMap queryHistoricEnergy(// + InfluxConnection influxConnection, // + String bucket, // + String measurement, // + Optional influxEdgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels // + ) throws OpenemsNamedException; + + /** + * Queries the historic energy values with a measurement which only has one + * value saved per day. + * + * @param influxConnection a Influx-Connection + * @param bucket the bucket name; 'database/retentionPolicy' for + * InfluxDB v1 + * @param measurement the influx measurement + * @param influxEdgeId the Edge-ID + * @param fromDate the From-Date + * @param toDate the To-Date + * @param channels the {@link ChannelAddress ChannelAddresses} + * @return the query result + * @throws OpenemsNamedException on error + */ + public abstract SortedMap queryHistoricEnergySingleValueInDay(// + InfluxConnection influxConnection, // + String bucket, // + String measurement, // + Optional influxEdgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels // + ) throws OpenemsNamedException; /** * {@link CommonTimedataService#queryHistoricData(String, io.openems.common.jsonrpc.request.QueryHistoricTimeseriesDataRequest)}. @@ -75,6 +112,7 @@ public abstract SortedMap queryHistoricEnergy(Influ * @param influxConnection a Influx-Connection * @param bucket the bucket name; 'database/retentionPolicy' for * InfluxDB v1 + * @param measurement the influx measurement * @param influxEdgeId the Edge-ID * @param fromDate the From-Date * @param toDate the To-Date @@ -83,9 +121,16 @@ public abstract SortedMap queryHistoricEnergy(Influ * @return the query result * @throws OpenemsNamedException on error */ - public abstract SortedMap> queryHistoricData( - InfluxConnection influxConnection, String bucket, Optional influxEdgeId, ZonedDateTime fromDate, - ZonedDateTime toDate, Set channels, Resolution resolution) throws OpenemsNamedException; + public abstract SortedMap> queryHistoricData(// + InfluxConnection influxConnection, // + String bucket, // + String measurement, // + Optional influxEdgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels, // + Resolution resolution // + ) throws OpenemsNamedException; /** * {@link CommonTimedataService#queryHistoricEnergyPerPeriod(String, ZonedDateTime, ZonedDateTime, Set, Resolution)}. @@ -93,6 +138,35 @@ public abstract SortedMap> * @param influxConnection a Influx-Connection * @param bucket the bucket name; 'database/retentionPolicy' for * InfluxDB v1 + * @param measurement the influx measurement + * @param influxEdgeId the Edge-ID + * @param fromDate the From-Date + * @param toDate the To-Date + * @param channels the {@link ChannelAddress}es + * @param resolution the {@link Resolution} + * @return the query result + * @throws OpenemsNamedException on error + */ + public abstract SortedMap> queryHistoricEnergyPerPeriod(// + InfluxConnection influxConnection, // + String bucket, // + String measurement, // + Optional influxEdgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels, // + Resolution resolution // + ) throws OpenemsNamedException; + + /** + * Queries the raw historic values without calculating the difference between + * two values also includes the first value before the time range to help + * calculating the differences. + * + * @param influxConnection a Influx-Connection + * @param bucket the bucket name; 'database/retentionPolicy' for + * InfluxDB v1 + * @param measurement the influx measurement * @param influxEdgeId the Edge-ID * @param fromDate the From-Date * @param toDate the To-Date @@ -101,26 +175,75 @@ public abstract SortedMap> * @return the query result * @throws OpenemsNamedException on error */ - public abstract SortedMap> queryHistoricEnergyPerPeriod( - InfluxConnection influxConnection, String bucket, Optional influxEdgeId, ZonedDateTime fromDate, - ZonedDateTime toDate, Set channels, Resolution resolution) throws OpenemsNamedException; + public abstract SortedMap> queryRawHistoricEnergyPerPeriodSingleValueInDay(// + InfluxConnection influxConnection, // + String bucket, // + String measurement, // + Optional influxEdgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels, // + Resolution resolution // + ) throws OpenemsNamedException; + + /** + * Queries the available since fields from the database. + * + * @param influxConnection a Influx-Connection + * @param bucket the bucket name; 'database/retentionPolicy' for + * InfluxDB v1 + * @return the map where the key is the edgeId and the value the timestamp of + * available since + */ + public abstract Map> queryAvailableSince(// + InfluxConnection influxConnection, // + String bucket // + ) throws OpenemsNamedException; + + /** + * Queries the first values before the given date. + * + * @param bucket the bucket name; 'database/retentionPolicy' for + * InfluxDB v1 + * @param influxConnection a Influx-Connection + * @param measurement the influx measurement + * @param influxEdgeId the Edge-ID + * @param date the bounding date exclusive + * @param channels the {@link ChannelAddress ChannelAddresses} + * @return the values + */ + public abstract SortedMap queryFirstValueBefore(// + String bucket, // + InfluxConnection influxConnection, // + String measurement, // + Optional influxEdgeId, // + ZonedDateTime date, // + Set channels // + ) throws OpenemsNamedException; - protected static class RandomLimit { + public static class RandomLimit { private static final double MAX_LIMIT = 0.95; private static final double MIN_LIMIT = 0; - private static final double STEP = 0.01; + private static final double STEP_UP = 0.10; + private static final double STEP_DOWN = 0.01; private double limit = 0; - protected synchronized void increase() { - this.limit += STEP; + /** + * Increases the current limit. + */ + public synchronized void increase() { + this.limit += STEP_UP; if (this.limit > MAX_LIMIT) { this.limit = MAX_LIMIT; } } - protected synchronized void decrease() { - this.limit -= STEP; + /** + * Decreases the current limit. + */ + public synchronized void decrease() { + this.limit -= STEP_DOWN; if (this.limit <= MIN_LIMIT) { this.limit = MIN_LIMIT; } @@ -136,23 +259,77 @@ public String toString() { } } - protected final RandomLimit queryLimit = new RandomLimit(); + public final RandomLimit queryLimit = new RandomLimit(); + + public boolean isLimitReached() { + return Math.random() < this.queryLimit.getLimit(); + } protected void assertQueryLimit() throws OpenemsException { - if (Math.random() < this.queryLimit.getLimit()) { + if (this.isLimitReached()) { throw new OpenemsException("InfluxDB read is temporarily blocked [" + this.queryLimit + "]."); } } - protected abstract String buildHistoricDataQuery(String bucket, Optional influxEdgeId, - ZonedDateTime fromDate, ZonedDateTime toDate, Set channels, Resolution resolution) - throws OpenemsException; + // TODO refactor to single parameter + protected abstract String buildHistoricDataQuery(// + String bucket, // + String measurement, // + Optional influxEdgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels, // + Resolution resolution // + ) throws OpenemsException; + + protected abstract String buildHistoricEnergyQuery(// + String bucket, // + String measurement, // + Optional influxEdgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels // + ) throws OpenemsException; + + protected abstract String buildHistoricEnergyQuerySingleValueInDay(// + String bucket, // + String measurement, // + Optional influxEdgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels // + ) throws OpenemsException; + + protected abstract String buildHistoricEnergyPerPeriodQuery(// + String bucket, // + String measurement, // + Optional influxEdgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels, // + Resolution resolution // + ) throws OpenemsException; + + protected abstract String buildHistoricEnergyPerPeriodQuerySingleValueInDay(// + String bucket, // + String measurement, // + Optional influxEdgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels, // + Resolution resolution // + ) throws OpenemsException; - protected abstract String buildHistoricEnergyQuery(String bucket, Optional influxEdgeId, - ZonedDateTime fromDate, ZonedDateTime toDate, Set channels) throws OpenemsException; + protected abstract String buildFetchAvailableSinceQuery(// + String bucket // + ); - protected abstract String buildHistoricEnergyPerPeriodQuery(String bucket, Optional influxEdgeId, - ZonedDateTime fromDate, ZonedDateTime toDate, Set channels, Resolution resolution) - throws OpenemsException; + protected abstract String buildFetchFirstValueBefore(// + String bucket, // + String measurement, // + Optional influxEdgeId, // + ZonedDateTime date, // + Set channels // + ); } diff --git a/io.openems.shared.influxdb/src/io/openems/shared/influxdb/proxy/package-info.java b/io.openems.shared.influxdb/src/io/openems/shared/influxdb/proxy/package-info.java new file mode 100644 index 00000000000..bdac641af45 --- /dev/null +++ b/io.openems.shared.influxdb/src/io/openems/shared/influxdb/proxy/package-info.java @@ -0,0 +1,3 @@ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package io.openems.shared.influxdb.proxy; diff --git a/io.openems.shared.influxdb/test/io/openems/shared/influxdb/proxy/QueryBuilderTest.java b/io.openems.shared.influxdb/test/io/openems/shared/influxdb/proxy/QueryBuilderTest.java index 001ba41e9de..a841dd97380 100644 --- a/io.openems.shared.influxdb/test/io/openems/shared/influxdb/proxy/QueryBuilderTest.java +++ b/io.openems.shared.influxdb/test/io/openems/shared/influxdb/proxy/QueryBuilderTest.java @@ -15,6 +15,7 @@ public class QueryBuilderTest { private static final String BUCKET = "db/default"; + private static final String MEASUREMENT = "data"; private static final Optional EDGE_ID = Optional.of(888); private static final ZoneId ZONE = ZoneId.of("Europe/Berlin"); private static final ZonedDateTime FROM_DATE = ZonedDateTime.of(2022, 12, 29, 0, 0, 0, 0, ZONE); @@ -31,32 +32,66 @@ public class QueryBuilderTest { @Test public void testFluxBuildHistoricDataQuery() throws OpenemsNamedException { - FLUX.buildHistoricDataQuery(BUCKET, EDGE_ID, FROM_DATE, TO_DATE, POWER_CHANNELS, RESOLUTION); + FLUX.buildHistoricDataQuery(BUCKET, MEASUREMENT, EDGE_ID, FROM_DATE, TO_DATE, POWER_CHANNELS, RESOLUTION); } @Test public void testInfluxqlBuildHistoricDataQuery() throws OpenemsNamedException { - INFLUX_QL.buildHistoricDataQuery(BUCKET, EDGE_ID, FROM_DATE, TO_DATE, POWER_CHANNELS, RESOLUTION); + INFLUX_QL.buildHistoricDataQuery(BUCKET, MEASUREMENT, EDGE_ID, FROM_DATE, TO_DATE, POWER_CHANNELS, RESOLUTION); } @Test public void testFluxBuildHistoricEnergyQuery() throws OpenemsNamedException { - FLUX.buildHistoricEnergyQuery(BUCKET, EDGE_ID, FROM_DATE, TO_DATE, ENERGY_CHANNELS); + FLUX.buildHistoricEnergyQuery(BUCKET, MEASUREMENT, EDGE_ID, FROM_DATE, TO_DATE, ENERGY_CHANNELS); } @Test public void testInfluxqlBuildHistoricEnergyQuery() throws OpenemsNamedException { - INFLUX_QL.buildHistoricEnergyQuery(BUCKET, EDGE_ID, FROM_DATE, TO_DATE, ENERGY_CHANNELS); + INFLUX_QL.buildHistoricEnergyQuery(BUCKET, MEASUREMENT, EDGE_ID, FROM_DATE, TO_DATE, ENERGY_CHANNELS); } @Test public void testFluxBuildHistoricEnergyQueryPerPeriod() throws OpenemsNamedException { - FLUX.buildHistoricEnergyPerPeriodQuery(BUCKET, EDGE_ID, FROM_DATE, TO_DATE, ENERGY_CHANNELS, RESOLUTION); + FLUX.buildHistoricEnergyPerPeriodQuery(BUCKET, MEASUREMENT, EDGE_ID, FROM_DATE, TO_DATE, ENERGY_CHANNELS, + RESOLUTION); } @Test public void testInfluxqlBuildHistoricEnergyPerPeriodQuery() throws OpenemsNamedException { - INFLUX_QL.buildHistoricEnergyPerPeriodQuery(BUCKET, EDGE_ID, FROM_DATE, TO_DATE, ENERGY_CHANNELS, RESOLUTION); + INFLUX_QL.buildHistoricEnergyPerPeriodQuery(BUCKET, MEASUREMENT, EDGE_ID, FROM_DATE, TO_DATE, ENERGY_CHANNELS, + RESOLUTION); + } + + @Test + public void testFluxBuildHistoricEnergyPerPeriodQuerySingleValueInDay() throws OpenemsNamedException { + FLUX.buildHistoricEnergyPerPeriodQuerySingleValueInDay(BUCKET, MEASUREMENT, EDGE_ID, TO_DATE, FROM_DATE, + ENERGY_CHANNELS, RESOLUTION); + } + + @Test + public void testInfluxqlBuildHistoricEnergyPerPeriodQuerySingleValueInDay() throws OpenemsNamedException { + INFLUX_QL.buildHistoricEnergyPerPeriodQuerySingleValueInDay(BUCKET, MEASUREMENT, EDGE_ID, TO_DATE, FROM_DATE, + ENERGY_CHANNELS, RESOLUTION); + } + + @Test + public void testFluxFetchAvailableSinceQuery() throws OpenemsNamedException { + FLUX.buildFetchAvailableSinceQuery(BUCKET); + } + + @Test + public void testInlfuxqlFetchAvailableSinceQuery() throws OpenemsNamedException { + INFLUX_QL.buildFetchAvailableSinceQuery(BUCKET); + } + + @Test + public void testFluxBuildFetchFirstValueBefore() throws OpenemsNamedException { + FLUX.buildFetchFirstValueBefore(BUCKET, MEASUREMENT, EDGE_ID, FROM_DATE, ENERGY_CHANNELS); + } + + @Test + public void testInfluxqlBuildFetchFirstValueBefore() throws OpenemsNamedException { + INFLUX_QL.buildFetchFirstValueBefore(BUCKET, MEASUREMENT, EDGE_ID, FROM_DATE, ENERGY_CHANNELS); } } From 74293eab9f44c6ade9ae9c3688e6268e3c88808a Mon Sep 17 00:00:00 2001 From: Sebastian Asen <47855186+sebastianasen@users.noreply.github.com> Date: Fri, 11 Aug 2023 14:22:41 +0200 Subject: [PATCH 11/32] Edge: Fix typo in EssFeneconCommercial40Pv2Impl (#2310) * Validate, that fix actually works, via JUnit test * Fix typo --------- Co-authored-by: Stefan Feilmeier --- .../EssFeneconCommercial40Pv2Impl.java | 2 +- .../EssFeneconCommercial40Pv1ImplTest.java | 2 +- .../EssFeneconCommercial40Pv2ImplTest.java | 46 ++++++++++++ .../{MyConfigPV1.java => MyConfigPv1.java} | 8 +- .../commercial40/charger/MyConfigPv2.java | 74 +++++++++++++++++++ 5 files changed, 126 insertions(+), 6 deletions(-) create mode 100644 io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/EssFeneconCommercial40Pv2ImplTest.java rename io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/{MyConfigPV1.java => MyConfigPv1.java} (88%) create mode 100644 io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/MyConfigPv2.java diff --git a/io.openems.edge.ess.fenecon.commercial40/src/io/openems/edge/ess/fenecon/commercial40/charger/EssFeneconCommercial40Pv2Impl.java b/io.openems.edge.ess.fenecon.commercial40/src/io/openems/edge/ess/fenecon/commercial40/charger/EssFeneconCommercial40Pv2Impl.java index 6814d79c977..2d8a966ef68 100644 --- a/io.openems.edge.ess.fenecon.commercial40/src/io/openems/edge/ess/fenecon/commercial40/charger/EssFeneconCommercial40Pv2Impl.java +++ b/io.openems.edge.ess.fenecon.commercial40/src/io/openems/edge/ess/fenecon/commercial40/charger/EssFeneconCommercial40Pv2Impl.java @@ -55,7 +55,7 @@ public EssFeneconCommercial40Pv2Impl() { } @Activate - private void activate(ComponentContext context, ConfigPv1 config) throws OpenemsException { + private void activate(ComponentContext context, ConfigPv2 config) throws OpenemsException { if (super.activate(context, config.id(), config.alias(), config.enabled(), this.ess.getUnitId(), this.cm, "Modbus", this.ess.getModbusBridgeId())) { return; diff --git a/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/EssFeneconCommercial40Pv1ImplTest.java b/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/EssFeneconCommercial40Pv1ImplTest.java index 4e406e5f225..317c043debe 100644 --- a/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/EssFeneconCommercial40Pv1ImplTest.java +++ b/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/EssFeneconCommercial40Pv1ImplTest.java @@ -35,7 +35,7 @@ public void test() throws Exception { .addReference("cm", new DummyConfigurationAdmin()) // .addReference("ess", ess) // .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // - .activate(MyConfigPV1.create() // + .activate(MyConfigPv1.create() // .setId(CHARGER_ID) // .setModbusId(MODBUS_ID) // .setEssId(ESS_ID) // diff --git a/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/EssFeneconCommercial40Pv2ImplTest.java b/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/EssFeneconCommercial40Pv2ImplTest.java new file mode 100644 index 00000000000..652b0363b74 --- /dev/null +++ b/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/EssFeneconCommercial40Pv2ImplTest.java @@ -0,0 +1,46 @@ +package io.openems.edge.ess.fenecon.commercial40.charger; + +import org.junit.Test; + +import io.openems.edge.bridge.modbus.test.DummyModbusBridge; +import io.openems.edge.common.test.ComponentTest; +import io.openems.edge.common.test.DummyConfigurationAdmin; +import io.openems.edge.ess.fenecon.commercial40.EssFeneconCommercial40Impl; +import io.openems.edge.ess.test.ManagedSymmetricEssTest; + +public class EssFeneconCommercial40Pv2ImplTest { + + private static final String CHARGER_ID = "charger1"; + private static final String ESS_ID = "ess0"; + private static final String MODBUS_ID = "modbus0"; + + @Test + public void test() throws Exception { + var ess = new EssFeneconCommercial40Impl(); + new ManagedSymmetricEssTest(ess) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .activate(io.openems.edge.ess.fenecon.commercial40.MyConfig.create() // + .setId(ESS_ID) // + .setModbusId(MODBUS_ID) // + .setSurplusFeedInSocLimit(90) // + .setSurplusFeedInAllowedChargePowerLimit(-8000) // + .setSurplusFeedInIncreasePowerFactor(1.1) // + .setSurplusFeedInMaxIncreasePowerFactor(2000) // + .setSurplusFeedInPvLimitOnPowerDecreaseCausedByOvertemperature(5000) // + .setSurplusFeedInOffTime("17:00:00") // + .build()); + + new ComponentTest(new EssFeneconCommercial40Pv2Impl()) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("ess", ess) // + .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .activate(MyConfigPv2.create() // + .setId(CHARGER_ID) // + .setModbusId(MODBUS_ID) // + .setEssId(ESS_ID) // + .setMaxActualPower(0) // + .build()) // + ; + } +} diff --git a/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/MyConfigPV1.java b/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/MyConfigPv1.java similarity index 88% rename from io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/MyConfigPV1.java rename to io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/MyConfigPv1.java index 632d8734d98..58a3190d8d8 100644 --- a/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/MyConfigPV1.java +++ b/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/MyConfigPv1.java @@ -4,7 +4,7 @@ import io.openems.common.utils.ConfigUtils; @SuppressWarnings("all") -public class MyConfigPV1 extends AbstractComponentConfig implements ConfigPv1 { +public class MyConfigPv1 extends AbstractComponentConfig implements ConfigPv1 { protected static class Builder { private String id; @@ -35,8 +35,8 @@ public Builder setEssId(String essId) { return this; } - public MyConfigPV1 build() { - return new MyConfigPV1(this); + public MyConfigPv1 build() { + return new MyConfigPv1(this); } } @@ -51,7 +51,7 @@ public static Builder create() { private final Builder builder; - private MyConfigPV1(Builder builder) { + private MyConfigPv1(Builder builder) { super(ConfigPv1.class, builder.id); this.builder = builder; } diff --git a/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/MyConfigPv2.java b/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/MyConfigPv2.java new file mode 100644 index 00000000000..fbeda0d9d56 --- /dev/null +++ b/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/MyConfigPv2.java @@ -0,0 +1,74 @@ +package io.openems.edge.ess.fenecon.commercial40.charger; + +import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.utils.ConfigUtils; + +@SuppressWarnings("all") +public class MyConfigPv2 extends AbstractComponentConfig implements ConfigPv2 { + + protected static class Builder { + private String id; + private String modbusId; + private int maxActualPower; + private String essId; + + private Builder() { + } + + public Builder setId(String id) { + this.id = id; + return this; + } + + public Builder setModbusId(String modbusId) { + this.modbusId = modbusId; + return this; + } + + public Builder setMaxActualPower(int maxActualPower) { + this.maxActualPower = maxActualPower; + return this; + } + + public Builder setEssId(String essId) { + this.essId = essId; + return this; + } + + public MyConfigPv2 build() { + return new MyConfigPv2(this); + } + } + + /** + * Create a Config builder. + * + * @return a {@link Builder} + */ + public static Builder create() { + return new Builder(); + } + + private final Builder builder; + + private MyConfigPv2(Builder builder) { + super(ConfigPv2.class, builder.id); + this.builder = builder; + } + + @Override + public String ess_id() { + return this.builder.essId; + } + + @Override + public String Modbus_target() { + return ConfigUtils.generateReferenceTargetFilter(this.id(), this.builder.modbusId); + } + + @Override + public String Ess_target() { + return ConfigUtils.generateReferenceTargetFilter(this.id(), this.ess_id()); + } + +} \ No newline at end of file From 230d0985ab5a05bb4d2d9711c32c23c4c1b35692 Mon Sep 17 00:00:00 2001 From: AnasShetla <141644226+AnasShetla@users.noreply.github.com> Date: Wed, 16 Aug 2023 01:30:50 +0200 Subject: [PATCH 12/32] UI: storage icon in evcs; infinity sign (#2309) * change color of evcs modal priorization storage icon * fix bug of showing infinity sign in production history due to undefined values --- ui/src/app/edge/live/Controller/Evcs/modal/modal.html | 4 ++-- ui/src/app/shared/service/utils.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/src/app/edge/live/Controller/Evcs/modal/modal.html b/ui/src/app/edge/live/Controller/Evcs/modal/modal.html index 7a0029f8612..eec17e8c51e 100644 --- a/ui/src/app/edge/live/Controller/Evcs/modal/modal.html +++ b/ui/src/app/edge/live/Controller/Evcs/modal/modal.html @@ -114,7 +114,7 @@ [component]="component" [buttons]="[ { name: ('Edge.Index.Widgets.EVCS.OptimizedChargeMode.ChargingPriority.car' | translate), value: 'CAR', icon: {color:'success', name: 'oe-evcs'}}, - { name: ('Edge.Index.Widgets.EVCS.OptimizedChargeMode.ChargingPriority.storage' | translate), value: 'STORAGE', icon: {color:'primary', name: 'oe-storage'}}]"> + { name: ('Edge.Index.Widgets.EVCS.OptimizedChargeMode.ChargingPriority.storage' | translate), value: 'STORAGE', icon: {color:'success', name: 'oe-storage'}}]"> @@ -155,4 +155,4 @@ - \ No newline at end of file + diff --git a/ui/src/app/shared/service/utils.ts b/ui/src/app/shared/service/utils.ts index 4a9e2abb071..854fab91084 100644 --- a/ui/src/app/shared/service/utils.ts +++ b/ui/src/app/shared/service/utils.ts @@ -277,7 +277,7 @@ export class Utils { * @returns converted value */ public static CONVERT_TO_KILO_WATTHOURS = (value: any): string => { - return formatNumber(value / 1000, 'de', '1.0-1') + ' kWh'; + return formatNumber(Utils.divideSafely(value, 1000), 'de', '1.0-1') + ' kWh'; }; /** From 756094201925509eff4b9f8ba7494473077b1852 Mon Sep 17 00:00:00 2001 From: iammahdi96 <54065538+mahdiataie@users.noreply.github.com> Date: Wed, 16 Aug 2023 01:35:13 +0200 Subject: [PATCH 13/32] Docs: Fix typing error in backend-to-backend section (#2315) --- doc/modules/ROOT/pages/backend/backend-to-backend.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/modules/ROOT/pages/backend/backend-to-backend.adoc b/doc/modules/ROOT/pages/backend/backend-to-backend.adoc index 918c4cb0738..1fbac885f48 100644 --- a/doc/modules/ROOT/pages/backend/backend-to-backend.adoc +++ b/doc/modules/ROOT/pages/backend/backend-to-backend.adoc @@ -121,7 +121,7 @@ https://github.com/OpenEMS/openems/blob/develop/io.openems.backend.b2bwebsocket/ === SubscribeToChannels -Registers a subscription for regular updates of channel values. Request is acknowledged by an empty success Response and followed by regular JSON-RPC Notifications. Du stop the subscription, an empty 'subscribeEdgesChannels' Request needs to be sent. +Registers a subscription for regular updates of channel values. Request is acknowledged by an empty success Response and followed by regular JSON-RPC Notifications. To stop the subscription, an empty 'subscribeEdgesChannels' Request needs to be sent. The parameter "count" must be increased with each new Request. Only the Request with the highest "count" value is active. From aeb999aa63c2d48af241967c6a7ecd50a3a6a19d Mon Sep 17 00:00:00 2001 From: Lukas Rieger <73471197+lukasrgr@users.noreply.github.com> Date: Thu, 17 Aug 2023 23:25:53 +0200 Subject: [PATCH 14/32] UI: Reduce RAM usage in VSC (#2306) --- ui/.vscode/settings.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ui/.vscode/settings.json b/ui/.vscode/settings.json index 7d300328b25..1c1145b5cf5 100644 --- a/ui/.vscode/settings.json +++ b/ui/.vscode/settings.json @@ -14,5 +14,9 @@ ".angular", "target" ], - "formatFiles.excludePattern": "**/{*.svg,*.png,*.ico,package-lock.json}" + "formatFiles.excludePattern": "**/{*.svg,*.png,*.ico,package-lock.json}", + "files.watcherExclude": { + "**/.git/objects/**": true, + "**/node_modules/**": true + } } \ No newline at end of file From 94c71114380732dc4907155ab765d9095179f44a Mon Sep 17 00:00:00 2001 From: Christian Lehne <51822163+clehne@users.noreply.github.com> Date: Fri, 18 Aug 2023 18:22:46 +0200 Subject: [PATCH 15/32] Add Backend Timedata AggregatedInflux (#2313) Co-authored-by: Michael Grill <59126309+michaelgrill@users.noreply.github.com> Initially added bundle timedata aggregatedinflux. Note, this code was made available to me by @michaelgrill. I just applied some code formatting. --- .../BackendApp.bndrun | 2 + .../.classpath | 12 + .../.gitignore | 3 + .../.project | 23 + .../org.eclipse.core.resources.prefs | 2 + .../bnd.bnd | 15 + .../aggregatedinflux/AggregatedInflux.java | 399 ++++++++++++++++++ .../aggregatedinflux/AllowedChannels.java | 201 +++++++++ .../timedata/aggregatedinflux/Config.java | 55 +++ .../QueryWithCurrentData.java | 129 ++++++ .../test/.gitignore | 0 .../aggregatedinflux/AllowedChannelsTest.java | 60 +++ 12 files changed, 901 insertions(+) create mode 100644 io.openems.backend.timedata.aggregatedinflux/.classpath create mode 100644 io.openems.backend.timedata.aggregatedinflux/.gitignore create mode 100644 io.openems.backend.timedata.aggregatedinflux/.project create mode 100644 io.openems.backend.timedata.aggregatedinflux/.settings/org.eclipse.core.resources.prefs create mode 100644 io.openems.backend.timedata.aggregatedinflux/bnd.bnd create mode 100644 io.openems.backend.timedata.aggregatedinflux/src/io/openems/backend/timedata/aggregatedinflux/AggregatedInflux.java create mode 100644 io.openems.backend.timedata.aggregatedinflux/src/io/openems/backend/timedata/aggregatedinflux/AllowedChannels.java create mode 100644 io.openems.backend.timedata.aggregatedinflux/src/io/openems/backend/timedata/aggregatedinflux/Config.java create mode 100644 io.openems.backend.timedata.aggregatedinflux/src/io/openems/backend/timedata/aggregatedinflux/QueryWithCurrentData.java create mode 100644 io.openems.backend.timedata.aggregatedinflux/test/.gitignore create mode 100644 io.openems.backend.timedata.aggregatedinflux/test/io/openems/backend/timedata/aggregatedinflux/AllowedChannelsTest.java diff --git a/io.openems.backend.application/BackendApp.bndrun b/io.openems.backend.application/BackendApp.bndrun index 10333e90c4f..a7fceec5267 100644 --- a/io.openems.backend.application/BackendApp.bndrun +++ b/io.openems.backend.application/BackendApp.bndrun @@ -51,6 +51,7 @@ bnd.identity;id='io.openems.backend.metadata.dummy',\ bnd.identity;id='io.openems.backend.metadata.file',\ bnd.identity;id='io.openems.backend.metadata.odoo',\ + bnd.identity;id='io.openems.backend.timedata.aggregatedinflux',\ bnd.identity;id='io.openems.backend.timedata.dummy',\ bnd.identity;id='io.openems.backend.timedata.influx',\ bnd.identity;id='io.openems.backend.timedata.timescaledb',\ @@ -74,6 +75,7 @@ io.openems.backend.metadata.dummy;version=snapshot,\ io.openems.backend.metadata.file;version=snapshot,\ io.openems.backend.metadata.odoo;version=snapshot,\ + io.openems.backend.timedata.aggregatedinflux;version=snapshot,\ io.openems.backend.timedata.dummy;version=snapshot,\ io.openems.backend.timedata.influx;version=snapshot,\ io.openems.backend.timedata.timescaledb;version=snapshot,\ diff --git a/io.openems.backend.timedata.aggregatedinflux/.classpath b/io.openems.backend.timedata.aggregatedinflux/.classpath new file mode 100644 index 00000000000..bbfbdbe40e7 --- /dev/null +++ b/io.openems.backend.timedata.aggregatedinflux/.classpath @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/io.openems.backend.timedata.aggregatedinflux/.gitignore b/io.openems.backend.timedata.aggregatedinflux/.gitignore new file mode 100644 index 00000000000..90dde36e4ac --- /dev/null +++ b/io.openems.backend.timedata.aggregatedinflux/.gitignore @@ -0,0 +1,3 @@ +/bin/ +/bin_test/ +/generated/ diff --git a/io.openems.backend.timedata.aggregatedinflux/.project b/io.openems.backend.timedata.aggregatedinflux/.project new file mode 100644 index 00000000000..70b5b35d9d0 --- /dev/null +++ b/io.openems.backend.timedata.aggregatedinflux/.project @@ -0,0 +1,23 @@ + + + io.openems.backend.timedata.aggregatedinflux + + + + + + org.eclipse.jdt.core.javabuilder + + + + + bndtools.core.bndbuilder + + + + + + org.eclipse.jdt.core.javanature + bndtools.core.bndnature + + diff --git a/io.openems.backend.timedata.aggregatedinflux/.settings/org.eclipse.core.resources.prefs b/io.openems.backend.timedata.aggregatedinflux/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000000..99f26c0203a --- /dev/null +++ b/io.openems.backend.timedata.aggregatedinflux/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/io.openems.backend.timedata.aggregatedinflux/bnd.bnd b/io.openems.backend.timedata.aggregatedinflux/bnd.bnd new file mode 100644 index 00000000000..dbd7dfd1f1f --- /dev/null +++ b/io.openems.backend.timedata.aggregatedinflux/bnd.bnd @@ -0,0 +1,15 @@ +Bundle-Name: OpenEMS Backend Timedata Aggregated InfluxDB +Bundle-Vendor: FENECON GmbH +Bundle-License: https://opensource.org/licenses/EPL-2.0 +Bundle-Version: 1.0.0.${tstamp} + +-buildpath: \ + ${buildpath},\ + io.openems.backend.common,\ + io.openems.common,\ + io.openems.shared.influxdb,\ + io.openems.wrapper.influxdb-client-core,\ + io.openems.wrapper.influxdb-client-java,\ + +-testpath: \ + ${testpath} diff --git a/io.openems.backend.timedata.aggregatedinflux/src/io/openems/backend/timedata/aggregatedinflux/AggregatedInflux.java b/io.openems.backend.timedata.aggregatedinflux/src/io/openems/backend/timedata/aggregatedinflux/AggregatedInflux.java new file mode 100644 index 00000000000..cb425cc893c --- /dev/null +++ b/io.openems.backend.timedata.aggregatedinflux/src/io/openems/backend/timedata/aggregatedinflux/AggregatedInflux.java @@ -0,0 +1,399 @@ +package io.openems.backend.timedata.aggregatedinflux; + +import static java.util.Collections.emptyList; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; + +import java.net.URI; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.OptionalLong; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; + +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ConfigurationPolicy; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicyOption; +import org.osgi.service.metatype.annotations.Designate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonElement; +import com.influxdb.client.domain.WriteConsistency; +import com.influxdb.client.domain.WritePrecision; +import com.influxdb.client.write.Point; +import com.influxdb.client.write.WriteParameters; + +import io.openems.backend.common.component.AbstractOpenemsBackendComponent; +import io.openems.backend.common.timedata.InternalTimedataException; +import io.openems.backend.common.timedata.Timedata; +import io.openems.backend.timedata.aggregatedinflux.AllowedChannels.ChannelType; +import io.openems.common.OpenemsOEM; +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.jsonrpc.notification.AbstractDataNotification; +import io.openems.common.jsonrpc.notification.AggregatedDataNotification; +import io.openems.common.jsonrpc.notification.ResendDataNotification; +import io.openems.common.jsonrpc.notification.TimestampedDataNotification; +import io.openems.common.timedata.DurationUnit; +import io.openems.common.timedata.Resolution; +import io.openems.common.types.ChannelAddress; +import io.openems.shared.influxdb.DbDataUtils; +import io.openems.shared.influxdb.InfluxConnector; + +@Designate(ocd = Config.class, factory = false) +@Component(// + name = "Timedata.AggregatedInfluxDB", // + configurationPolicy = ConfigurationPolicy.REQUIRE // +) +public class AggregatedInflux extends AbstractOpenemsBackendComponent implements Timedata { + + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + private WriteParameters writeParametersAvgPoints; + private WriteParameters writeParametersMaxPoints; + + private Config config; + private InfluxConnector influxConnector; + + private final Map zoneToMeasurement = new HashMap<>(); + + // map from edgeId to channelName and availableSince time stamp + private final Map> availableSinceForEdge = new ConcurrentHashMap<>(); + + @Reference(// + cardinality = ReferenceCardinality.OPTIONAL, // + policyOption = ReferencePolicyOption.GREEDY // + ) + private volatile QueryWithCurrentData queryWithCurrentData; + + public AggregatedInflux() { + super("AggregatedInflux"); + } + + @Activate + private void activate(Config config) throws OpenemsNamedException, IllegalArgumentException { + this.config = config; + this.logInfo(this.log, "Activate [" // + + "url=" + config.url() + ";"// + + "bucket=" + config.bucket() + ";"// + + "apiKey=" + (config.apiKey() != null ? "ok" : "NOT_SET") + ";"// + + "measurementAvg=" + config.measurementAvg() // + + "measurementMax=" + config.measurementsMax() // + + (config.isReadOnly() ? ";READ_ONLY_MODE" : "") // + + "]"); + + this.zoneToMeasurement.clear(); + this.zoneToMeasurement.putAll(parseMeasurementsByZone(config.measurementsMax())); + + this.writeParametersAvgPoints = new WriteParameters(config.bucket() + "/" + config.retentionPolicyAvg(), + config.org(), WritePrecision.S, WriteConsistency.ALL); + this.writeParametersMaxPoints = new WriteParameters(config.bucket() + "/" + config.retentionPolicyMax(), + config.org(), WritePrecision.S, WriteConsistency.ALL); + + this.influxConnector = new InfluxConnector(config.id(), config.queryLanguage(), URI.create(config.url()), + config.org(), config.apiKey(), config.bucket(), config.isReadOnly(), config.poolSize(), + config.maxQueueSize(), // + (throwable) -> { + this.logError(this.log, "Unable to write to InfluxDB. " + throwable.getClass().getSimpleName() + + ": " + throwable.getMessage()); + }, true /* enable safe write */, this.writeParametersAvgPoints, this.writeParametersMaxPoints); + + // load available since for edges which already wrote in the new database + this.availableSinceForEdge.clear(); + this.availableSinceForEdge.putAll(this.influxConnector.queryAvailableSince().entrySet().stream().map(entry -> { + return Map.entry(entry.getKey(), new ConcurrentHashMap<>(entry.getValue())); + }).collect(toMap(Entry::getKey, Entry::getValue))); + } + + @Deactivate + private void deactivate() { + this.influxConnector.deactivate(); + } + + @Override + public SortedMap> queryHistoricData(// + String edgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels, // + Resolution resolution // + ) throws OpenemsNamedException { + var influxEdgeId = InfluxConnector.parseNumberFromName(edgeId); + + this.checkDataAvailable(influxEdgeId, fromDate, channels); + + return this.influxConnector.queryHistoricData(Optional.of(influxEdgeId), // + fromDate, toDate, channels, resolution, + this.config.retentionPolicyAvg() + "." + this.config.measurementAvg()); + } + + @Override + public SortedMap queryHistoricEnergy(// + String edgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels // + ) throws OpenemsNamedException { + if (channels.isEmpty()) { + return new TreeMap<>(); + } + var influxEdgeId = InfluxConnector.parseNumberFromName(edgeId); + this.checkDataAvailable(influxEdgeId, fromDate, channels); + + if (isTodayOrAfter(toDate)) { + if (this.queryWithCurrentData == null) { + throw new InternalTimedataException("Missing 'queryWithCurrentData' object"); + } + return this.queryWithCurrentData.queryHistoricEnergy(edgeId, fromDate, toDate, channels); + } + final var measurement = this.getMeasurement(fromDate.getZone()); + return this.influxConnector.queryHistoricEnergySingleValueInDay(Optional.of(influxEdgeId), // + fromDate, toDate, channels, this.config.retentionPolicyMax() + "." + measurement); + } + + @Override + public SortedMap> queryHistoricEnergyPerPeriod(// + String edgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels, // + Resolution resolution // + ) throws OpenemsNamedException { + // parse the numeric EdgeId + var influxEdgeId = InfluxConnector.parseNumberFromName(edgeId); + this.checkDataAvailable(influxEdgeId, fromDate, channels); + final var measurement = this.getMeasurement(fromDate.getZone()); + final var rawData = this.influxConnector.queryRawHistoricEnergyPerPeriodSinglePerDay(Optional.of(influxEdgeId), // + fromDate, toDate, channels, resolution, this.config.retentionPolicyMax() + "." + measurement); + if (isTodayOrAfter(toDate)) { + if (this.queryWithCurrentData == null) { + throw new InternalTimedataException("Missing 'queryWithCurrentData' object"); + } + return this.queryWithCurrentData.queryHistoricEnergyPerPeriod(edgeId, fromDate, toDate, channels, + resolution, rawData); + } + final var result = DbDataUtils.calculateLastMinusFirst(rawData, fromDate); + return DbDataUtils.normalizeTable(result, channels, resolution, fromDate, toDate); + } + + @Override + public SortedMap queryFirstValueBefore(// + final String edgeId, // + final ZonedDateTime date, // + final Set channels // + ) throws OpenemsNamedException { + var influxEdgeId = InfluxConnector.parseNumberFromName(edgeId); + final var measurement = this.getMeasurement(date.getZone()); + // TODO determine measurement based on channel or separate in two methods + return this.influxConnector.queryFirstValueBefore(Optional.of(influxEdgeId), // + date, channels, this.config.retentionPolicyMax() + "." + measurement); + } + + @Override + public String id() { + return this.config.id(); + } + + @Override + public void write(String edgeId, AggregatedDataNotification notification) { + this.writeNotificationData(edgeId, notification); + } + + @Override + public void write(String edgeId, ResendDataNotification notification) { + this.writeNotificationData(edgeId, notification); + } + + @Override + public void write(String edgeId, TimestampedDataNotification notification) { + // empty + } + + private void writeNotificationData(String edgeId, AbstractDataNotification notification) { + final var data = notification.getData().rowMap(); + if (data.isEmpty()) { + return; + } + int influxEdgeId; + try { + influxEdgeId = InfluxConnector.parseNumberFromName(edgeId); + } catch (OpenemsException e) { + e.printStackTrace(); + return; + } + + for (var dataEntry : data.entrySet()) { + var channelEntries = dataEntry.getValue().entrySet(); + if (channelEntries.isEmpty()) { + // no points to add + continue; + } + + final var channelPerType = channelEntries.stream() // + .collect(groupingBy(// + entry -> AllowedChannels.getChannelType(entry.getKey()), // + () -> new EnumMap<>(ChannelType.class), // + toList() // + )); + + final var timestamp = dataEntry.getKey(); + final var timestampSeconds = timestamp / 1_000; + record AddValuesToPoint(String channel, JsonElement value, long timestamp) { + } + + BiConsumer addEntryToPoint = (entry, point) -> { + if (!this.hasAvailableSince(influxEdgeId, entry.channel())) { + this.setAvailableSince(influxEdgeId, entry.channel(), entry.timestamp()); + } + AllowedChannels.addWithSpecificChannelType(point, entry.channel(), entry.value()); + }; + + final var point = Point // + .measurement(this.config.measurementAvg()) // + .addTag(OpenemsOEM.INFLUXDB_TAG, String.valueOf(influxEdgeId)) // + .time(timestampSeconds, WritePrecision.S); + + channelPerType.getOrDefault(ChannelType.AVG, emptyList()).stream() // + .forEach(entry -> addEntryToPoint + .accept(new AddValuesToPoint(entry.getKey(), entry.getValue(), timestampSeconds), point)); + this.influxConnector.write(point, this.writeParametersAvgPoints); + + for (final var measurementEntry : this.getDayChangeMeasurements(timestamp).entrySet()) { + final var zonedDateTime = ZonedDateTime + .ofInstant(Instant.ofEpochMilli(timestamp), measurementEntry.getKey()) // + .truncatedTo(DurationUnit.ofDays(1)); + final var truncatedTimestamp = zonedDateTime.toEpochSecond(); + + final var maxPoint = Point // + .measurement(measurementEntry.getValue()) // + .addTag(OpenemsOEM.INFLUXDB_TAG, String.valueOf(influxEdgeId)) // + .time(truncatedTimestamp, WritePrecision.S); + + channelPerType.getOrDefault(ChannelType.MAX, emptyList()).stream() // + .forEach(entry -> addEntryToPoint.accept( + new AddValuesToPoint(entry.getKey(), entry.getValue(), truncatedTimestamp), maxPoint)); + this.influxConnector.write(maxPoint, this.writeParametersMaxPoints); + } + } + } + + private Map getDayChangeMeasurements(long timestamp) { + final var instant = Instant.ofEpochMilli(timestamp); + return this.zoneToMeasurement.entrySet().stream() // + .filter(entry -> { + final var time = instant.atZone(entry.getKey()); + return time.getHour() == 23 && time.getMinute() >= 55; + }) // + .collect(toMap(Entry::getKey, Entry::getValue)); + } + + private void setAvailableSince(int influxEdgeId, String channelName, long availableSince) { + this.influxConnector + .write(InfluxConnector.buildUpdateAvailableSincePoint(influxEdgeId, channelName, availableSince)); + this.availableSinceForEdge.computeIfAbsent(influxEdgeId, key -> new ConcurrentHashMap<>()) // + .put(channelName, availableSince); + } + + private void checkDataAvailable(// + int influxEdgeId, // + ZonedDateTime time, // + Set channels // + ) throws OpenemsNamedException { + final var errorMessage = this.checkDataAvailableOrErrorMessage(influxEdgeId, time, channels); + if (errorMessage == null) { + return; + } + throw new InternalTimedataException(errorMessage); + } + + /** + * Checks if the data is available. If true returns null otherwise returns the + * reason why the data is not available. + * + * @param influxEdgeId the id of the edge + * @param time the requested time + * @param channels the requested channels + * @return the reason why no data is available or null if the data is available + */ + private String checkDataAvailableOrErrorMessage(// + int influxEdgeId, // + ZonedDateTime time, // + Set channels // + ) { + final var edgeChannels = this.availableSinceForEdge.get(influxEdgeId); + if (edgeChannels == null) { + return null; + } + final var seconds = time.toEpochSecond(); + for (var channel : channels) { + var availableSince = edgeChannels.get(channel.toString()); + if (availableSince == null) { + return "No availableSince %5d for %sdefined channel %s".formatted(// + influxEdgeId, AllowedChannels.isChannelDefined(channel.toString()) ? "" : "un", + channel.toString()); + } + if (seconds < availableSince) { + return "AvailableSince %5d for channel %s too early got: %d, needed %d".formatted(// + influxEdgeId, channel.toString(), availableSince, seconds); + } + } + return null; + } + + private OptionalLong getAvailableSince(int influxEdgeId, String channelName) { + final var channelMap = this.availableSinceForEdge.get(influxEdgeId); + if (channelMap == null) { + return OptionalLong.empty(); + } + final var value = channelMap.get(channelName); + if (value == null) { + return OptionalLong.empty(); + } + return OptionalLong.of(value); + } + + private boolean hasAvailableSince(int influxEdgeId, String channelName) { + return this.getAvailableSince(influxEdgeId, channelName).isPresent(); + } + + private String getMeasurement(ZoneId zoneId) throws OpenemsNamedException { + final var measurement = this.zoneToMeasurement.get(zoneId); + if (measurement != null) { + return measurement; + } + this.log.warn("No measurement provided for zoneId " + zoneId); + for (var entry : this.zoneToMeasurement.entrySet()) { + return entry.getValue(); + } + throw new InternalTimedataException("No measurement provided for zoneId " + zoneId); + } + + private static boolean isTodayOrAfter(ZonedDateTime time) { + return ZonedDateTime.now(time.getZone()).truncatedTo(DurationUnit.ofDays(1)).isBefore(time); + } + + private static Map parseMeasurementsByZone(String[] strings) { + return Arrays.stream(strings) // + .map(t -> t.split("=")) // + .collect(toMap(// + parts -> ZoneId.of(parts[0]), // + parts -> parts[1])); + } + +} diff --git a/io.openems.backend.timedata.aggregatedinflux/src/io/openems/backend/timedata/aggregatedinflux/AllowedChannels.java b/io.openems.backend.timedata.aggregatedinflux/src/io/openems/backend/timedata/aggregatedinflux/AllowedChannels.java new file mode 100644 index 00000000000..57c5290d7c9 --- /dev/null +++ b/io.openems.backend.timedata.aggregatedinflux/src/io/openems/backend/timedata/aggregatedinflux/AllowedChannels.java @@ -0,0 +1,201 @@ +package io.openems.backend.timedata.aggregatedinflux; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import com.google.common.collect.ImmutableMap; +import com.google.gson.JsonElement; +import com.influxdb.client.write.Point; + +public final class AllowedChannels { + + public static final Map ALLOWED_AVERAGE_CHANNELS; + public static final Map ALLOWED_CUMULATED_CHANNELS; + + private AllowedChannels() { + } + + static { + ALLOWED_AVERAGE_CHANNELS = ImmutableMap.builder() // + .put("_sum/EssSoc", DataType.LONG) // + .put("_sum/EssActivePower", DataType.LONG) // + .put("_sum/EssActivePowerL1", DataType.LONG) // + .put("_sum/EssActivePowerL2", DataType.LONG) // + .put("_sum/EssActivePowerL3", DataType.LONG) // + .put("_sum/GridActivePower", DataType.LONG) // + .put("_sum/GridActivePowerL1", DataType.LONG) // + .put("_sum/GridActivePowerL2", DataType.LONG) // + .put("_sum/GridActivePowerL3", DataType.LONG) // + .put("_sum/ProductionActivePower", DataType.LONG) // + .put("_sum/ProductionAcActivePower", DataType.LONG) // + .put("_sum/ProductionAcActivePowerL1", DataType.LONG) // + .put("_sum/ProductionAcActivePowerL2", DataType.LONG) // + .put("_sum/ProductionAcActivePowerL3", DataType.LONG) // + .put("_sum/ProductionDcActualPower", DataType.LONG) // + .put("_sum/ConsumptionActivePower", DataType.LONG) // + .put("_sum/ConsumptionActivePowerL1", DataType.LONG) // + .put("_sum/ConsumptionActivePowerL2", DataType.LONG) // + .put("_sum/ConsumptionActivePowerL3", DataType.LONG) // + .put("_sum/EssDischargePower", DataType.LONG) // used for xlsx export + .putAll(multiChannels("io", 0, 9, "Relay", 1, 9, DataType.LONG)) // + .putAll(multiChannels("evcs", 0, 9, "ActualPower", DataType.LONG)) // + .putAll(multiChannels("meter", 0, 9, "ActivePower", DataType.LONG)) // + .putAll(multiChannels("meter", 0, 9, "ActivePowerL", 1, 4, DataType.LONG)) // + .put("ess0/Soc", DataType.LONG) // + .put("ess0/ActivePower", DataType.LONG) // + .put("ctrlIoHeatPump0/Status", DataType.LONG) // + .put("ctrlIoHeatingElement0/Level", DataType.LONG) // + .put("ctrlGridOptimizedCharge0/DelayChargeMaximumChargeLimit", DataType.LONG) // + .put("ctrlGridOptimizedCharge0/SellToGridLimitMinimumChargeLimit", DataType.LONG) // + .put("ctrlGridOptimizedCharge0/_PropertyMaximumSellToGridPower", DataType.LONG) // + .build(); + + ALLOWED_CUMULATED_CHANNELS = ImmutableMap.builder() // + .put("_sum/EssDcChargeEnergy", DataType.LONG) // + .put("_sum/EssDcDischargeEnergy", DataType.LONG) // + .put("_sum/GridSellActiveEnergy", DataType.LONG) // + .put("_sum/ProductionActiveEnergy", DataType.LONG) // + .put("_sum/ConsumptionActiveEnergy", DataType.LONG) // + .put("_sum/GridBuyActiveEnergy", DataType.LONG) // + .put("_sum/EssActiveChargeEnergy", DataType.LONG) // + .put("_sum/EssActiveDischargeEnergy", DataType.LONG) // + .put("_sum/ProductionAcActiveEnergy", DataType.LONG) // + .put("_sum/ProductionDcActiveEnergy", DataType.LONG) // + .putAll(multiChannels("evcs", 0, 9, "ActiveConsumptionEnergy", DataType.LONG)) // + .putAll(multiChannels("meter", 0, 9, "ActiveProductionEnergy", DataType.LONG)) // + .putAll(multiChannels("io", 0, 9, "ActiveProductionEnergy", DataType.LONG)) // + .putAll(multiChannels("pvInverter", 0, 9, "ActiveProductionEnergy", DataType.LONG)) // + .put("ctrlGridOptimizedCharge0/AvoidLowChargingTime", DataType.LONG) // + .put("ctrlGridOptimizedCharge0/NoLimitationTime", DataType.LONG) // + .put("ctrlGridOptimizedCharge0/SellToGridLimitTime", DataType.LONG) // + .put("ctrlGridOptimizedCharge0/DelayChargeTime", DataType.LONG) // + .put("ctrlIoHeatingElement0/Level1CumulatedTime", DataType.LONG) // + .put("ctrlIoHeatingElement0/Level2CumulatedTime", DataType.LONG) // + .put("ctrlIoHeatingElement0/Level3CumulatedTime", DataType.LONG) // + .put("ctrlIoHeatPump0/RegularStateTime", DataType.LONG) // + .put("ctrlIoHeatPump0/RecommendationStateTime", DataType.LONG) // + .put("ctrlIoHeatPump0/ForceOnStateTime", DataType.LONG) // + .put("ctrlIoHeatPump0/LockStateTime", DataType.LONG) // + .build(); + } + + public static enum ChannelType { + AVG, // + MAX, // + UNDEFINED, // + ; + } + + /** + * Checks if the given channel is a allowed channel. + * + * @param channel the to check + * @return true if defined otherwise false + */ + public static boolean isChannelDefined(String channel) { + return getChannelType(channel) != ChannelType.UNDEFINED; + } + + /** + * Gets the type of the given channel. + * + * @param channel the Channel-Address + * @return the {@link ChannelType} + */ + public static ChannelType getChannelType(String channel) { + if (ALLOWED_AVERAGE_CHANNELS.containsKey(channel)) { + return ChannelType.AVG; + } + if (ALLOWED_CUMULATED_CHANNELS.containsKey(channel)) { + return ChannelType.MAX; + } + return ChannelType.UNDEFINED; + } + + /** + * Adds the given value to the builder at the specified field parsed to the + * predefined type of the channel. + * + * @param builder a {@link Point} builder + * @param field the field name + * @param value the {@link JsonElement} value + * @return true on success + */ + public static boolean addWithSpecificChannelType(Point builder, String field, JsonElement value) { + if (value == null) { + return false; + } + if (!value.isJsonPrimitive()) { + return false; + } + if (!value.getAsJsonPrimitive().isNumber()) { + return false; + } + final var type = typeOf(field); + if (type == null) { + return false; + } + final var number = value.getAsNumber(); + switch (type) { + case DOUBLE: + builder.addField(field, number.doubleValue()); + return true; + case LONG: + builder.addField(field, number.longValue()); + return true; + } + return false; + } + + protected static enum DataType { + LONG, // + DOUBLE, // + ; + } + + private static DataType typeOf(String channel) { + var type = ALLOWED_AVERAGE_CHANNELS.get(channel); + if (type != null) { + return type; + } + type = ALLOWED_CUMULATED_CHANNELS.get(channel); + if (type != null) { + return type; + } + return null; + } + + protected static Iterable> multiChannels(// + final String component, // + final int from, // + final int to, // + final String channelOfComponent, // + final DataType type // + ) { + return IntStream.range(from, to) // + .mapToObj(componentNumber -> { + return component + componentNumber + "/" + channelOfComponent; + }).collect(Collectors.toMap(t -> t, t -> type)).entrySet(); + } + + protected static Iterable> multiChannels(// + final String component, // + final int from, // + final int to, // + final String channelOfComponent, // + final int fromChannel, // + final int toChannel, // + final DataType type // + ) { + return IntStream.range(from, to) // + .mapToObj(componentNumber -> { + return IntStream.range(fromChannel, toChannel) // + .mapToObj(channelNumber -> { + return component + componentNumber + "/" + channelOfComponent + channelNumber; + }); + }).flatMap(t -> t).collect(Collectors.toMap(t -> t, t -> type)).entrySet(); + } + +} diff --git a/io.openems.backend.timedata.aggregatedinflux/src/io/openems/backend/timedata/aggregatedinflux/Config.java b/io.openems.backend.timedata.aggregatedinflux/src/io/openems/backend/timedata/aggregatedinflux/Config.java new file mode 100644 index 00000000000..3755247b945 --- /dev/null +++ b/io.openems.backend.timedata.aggregatedinflux/src/io/openems/backend/timedata/aggregatedinflux/Config.java @@ -0,0 +1,55 @@ +package io.openems.backend.timedata.aggregatedinflux; + +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +import io.openems.shared.influxdb.QueryLanguageConfig; + +@ObjectClassDefinition(// + name = "Timedata.AggregatedInfluxDB", // + description = "Configures the InfluxDB timedata provider" // +) +@interface Config { + + @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component") + String id() default "timedata0"; + + @AttributeDefinition(name = "Query language", description = "Query language Flux or InfluxQL") + QueryLanguageConfig queryLanguage() default QueryLanguageConfig.INFLUX_QL; + + @AttributeDefinition(name = "URL", description = "The InfluxDB URL, e.g.: http://ip:port") + String url(); + + @AttributeDefinition(name = "Org", description = "The Organisation; '-' for InfluxDB v1") + String org() default "-"; + + @AttributeDefinition(name = "ApiKey", description = "The ApiKey; 'username:password' for InfluxDB v1") + String apiKey(); + + @AttributeDefinition(name = "Bucket", description = "The bucket name; 'database' for InfluxDB v1") + String bucket(); + + @AttributeDefinition(name = "Retention policy name for average values", description = "The retention policy name for InfluxDB v1 for daily values") + String retentionPolicyAvg() default "rp_avg"; + + @AttributeDefinition(name = "Retention policy name for max values", description = "The retention policy name for InfluxDB v1 for monthly values") + String retentionPolicyMax() default "rp_max"; + + @AttributeDefinition(name = "Measurement avg", description = "The InfluxDB measurement for average values") + String measurementAvg() default "avg"; + + @AttributeDefinition(name = "Measurements for max values", description = "Measurements for max values for each timezone. Format: \"(timezone)=(measurement)\"") + String[] measurementsMax() default { "Europe/Berlin=max" }; + + @AttributeDefinition(name = "Read-Only mode", description = "Activates the read-only mode. Then no data is written to InfluxDB.") + boolean isReadOnly() default false; + + @AttributeDefinition(name = "Number of Threads", description = "Pool-Size: the number of threads dedicated to handle the tasks") + int poolSize() default 10; + + @AttributeDefinition(name = "Number of max scheduled tasks", description = "Max-Size of Queued tasks.") + int maxQueueSize() default 5000; + + String webconsole_configurationFactory_nameHint() default "Timedata Aggregated InfluxDB"; + +} diff --git a/io.openems.backend.timedata.aggregatedinflux/src/io/openems/backend/timedata/aggregatedinflux/QueryWithCurrentData.java b/io.openems.backend.timedata.aggregatedinflux/src/io/openems/backend/timedata/aggregatedinflux/QueryWithCurrentData.java new file mode 100644 index 00000000000..6230378e576 --- /dev/null +++ b/io.openems.backend.timedata.aggregatedinflux/src/io/openems/backend/timedata/aggregatedinflux/QueryWithCurrentData.java @@ -0,0 +1,129 @@ +package io.openems.backend.timedata.aggregatedinflux; + +import java.time.ZonedDateTime; +import java.util.Collections; +import java.util.Map.Entry; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.stream.Collectors; + +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; + +import io.openems.backend.common.edgewebsocket.EdgeWebsocket; +import io.openems.backend.common.timedata.InternalTimedataException; +import io.openems.backend.common.timedata.Timedata; +import io.openems.backend.common.timedata.TimedataManager; +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.timedata.DurationUnit; +import io.openems.common.timedata.Resolution; +import io.openems.common.types.ChannelAddress; +import io.openems.common.utils.JsonUtils; +import io.openems.shared.influxdb.DbDataUtils; + +@Component(// + service = { QueryWithCurrentData.class } // +) +public class QueryWithCurrentData { + + @Reference + private TimedataManager timedataManager; + + @Reference + private EdgeWebsocket edgeWebsocket; + + /** + * {@link Timedata#queryHistoricEnergy(String, ZonedDateTime, ZonedDateTime, Set)}. + * + * @param edgeId the id of the edge + * @param fromDate the starting date + * @param toDate the stop date + * @param channels the channels + * @return the values + * @throws OpenemsNamedException on error + */ + public SortedMap queryHistoricEnergy(// + String edgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels // + ) throws OpenemsNamedException { + final var channelValues = this.edgeWebsocket.getChannelValues(edgeId, channels); + var missingChannels = channelValues.entrySet().stream().filter(entry -> entry.getValue().isJsonNull()) + .map(Entry::getKey).toList(); + if (!missingChannels.isEmpty()) { + throw new InternalTimedataException("Missing current values for edge[" + edgeId + "] for channels: " + + missingChannels.stream() // + .map(ChannelAddress::toString) // + .collect(Collectors.joining(", "))); + } + SortedMap previousValuesTemp; + try { + previousValuesTemp = this.timedataManager.queryFirstValueBefore(edgeId, fromDate, channels); + } catch (OpenemsNamedException e) { + previousValuesTemp = Collections.emptySortedMap(); + } + final SortedMap previousValues = previousValuesTemp; + return channelValues.entrySet().stream() // + .collect(Collectors.toMap(Entry::getKey, t -> { + var previousValue = previousValues.get(t.getKey()); + if (previousValue == null || previousValue.isJsonNull()) { + previousValue = new JsonPrimitive(0); + } + final var currentValue = t.getValue(); + if (!JsonUtils.isNumber(previousValue)) { + return currentValue; + } + if (!JsonUtils.isNumber(currentValue)) { + return currentValue; + } + return new JsonPrimitive(currentValue.getAsDouble() - (previousValue.getAsDouble())); + }, (t, u) -> u, TreeMap::new)); + } + + /** + * {@link Timedata#queryHistoricEnergyPerPeriod(String, ZonedDateTime, ZonedDateTime, Set, Resolution)}. + * + * @param edgeId the id of the edge + * @param fromDate the starting date + * @param toDate the stop date + * @param channels the channels + * @param resolution the resolution + * @param rawExistingData the already existing date which should be filled up + * with the current data + * @return the values + * @throws OpenemsNamedException on error + */ + public SortedMap> queryHistoricEnergyPerPeriod(// + String edgeId, // + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels, // + Resolution resolution, // + SortedMap> rawExistingData // + ) throws OpenemsNamedException { + final var channelValues = this.edgeWebsocket.getChannelValues(edgeId, channels); + var missingChannels = channelValues.entrySet().stream().filter(entry -> entry.getValue().isJsonNull()) + .map(Entry::getKey).toList(); + if (!missingChannels.isEmpty()) { + throw new InternalTimedataException("Missing current values for edge[" + edgeId + "] for channels: " + + missingChannels.stream() // + .map(ChannelAddress::toString) // + .collect(Collectors.joining(", "))); + } + + // add current values + final var now = ZonedDateTime.now(fromDate.getZone()) // + .truncatedTo(DurationUnit.ofDays(1)); + final var data = new TreeMap<>(rawExistingData); + data.put(now, channelValues); + + final var result = DbDataUtils.calculateLastMinusFirst(data, fromDate); + return DbDataUtils.normalizeTable(result, channels, resolution, fromDate, toDate); + } + +} diff --git a/io.openems.backend.timedata.aggregatedinflux/test/.gitignore b/io.openems.backend.timedata.aggregatedinflux/test/.gitignore new file mode 100644 index 00000000000..e69de29bb2d diff --git a/io.openems.backend.timedata.aggregatedinflux/test/io/openems/backend/timedata/aggregatedinflux/AllowedChannelsTest.java b/io.openems.backend.timedata.aggregatedinflux/test/io/openems/backend/timedata/aggregatedinflux/AllowedChannelsTest.java new file mode 100644 index 00000000000..ae702acdac7 --- /dev/null +++ b/io.openems.backend.timedata.aggregatedinflux/test/io/openems/backend/timedata/aggregatedinflux/AllowedChannelsTest.java @@ -0,0 +1,60 @@ +package io.openems.backend.timedata.aggregatedinflux; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import com.google.common.collect.Lists; + +import io.openems.backend.timedata.aggregatedinflux.AllowedChannels.DataType; + +public class AllowedChannelsTest { + + @Test + public void testInitialization() { + // to avoid initialization errors for duplicated channels + final var type = AllowedChannels.getChannelType("_sum/ConsumptionActiveEnergy"); + assertEquals(AllowedChannels.ChannelType.MAX, type); + } + + @Test + public void testMultiChannels() { + final var mutliChannels = AllowedChannels.multiChannels("component", 0, 5, "channel", DataType.DOUBLE); + + final var expectedChannels = Lists.newArrayList(// + "component0/channel", // + "component1/channel", // + "component2/channel", // + "component3/channel", // + "component4/channel" // + ); + for (var entry : mutliChannels) { + assertTrue(expectedChannels.remove(entry.getKey())); + } + assertTrue(expectedChannels.isEmpty()); + } + + @Test + public void testMultiChannelsWithChannelNumber() { + final var mutliChannels = AllowedChannels.multiChannels("component", 0, 5, "channel", 1, 3, DataType.DOUBLE); + + final var expectedChannels = Lists.newArrayList(// + "component0/channel1", // + "component1/channel1", // + "component2/channel1", // + "component3/channel1", // + "component4/channel1", // + "component0/channel2", // + "component1/channel2", // + "component2/channel2", // + "component3/channel2", // + "component4/channel2" // + ); + for (var entry : mutliChannels) { + assertTrue(entry.getKey(), expectedChannels.remove(entry.getKey())); + } + assertTrue(expectedChannels.isEmpty()); + } + +} From ca867a85396d356c2c35c87fb25c6c067e4ce270 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 23:51:51 +0200 Subject: [PATCH 16/32] Bump info.faljse:SDNotify from 1.3 to 1.5 in /cnf (#2328) * Bump info.faljse:SDNotify from 1.3 to 1.5 in /cnf Bumps [info.faljse:SDNotify](https://github.com/faljse/SDNotify) from 1.3 to 1.5. - [Commits](https://github.com/faljse/SDNotify/commits) --- updated-dependencies: - dependency-name: info.faljse:SDNotify dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Update bnd --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Stefan Feilmeier --- cnf/pom.xml | 2 +- io.openems.wrapper/bnd.bnd | 2 +- io.openems.wrapper/sdnotify.bnd | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cnf/pom.xml b/cnf/pom.xml index f80dc9dd508..36e179535d8 100644 --- a/cnf/pom.xml +++ b/cnf/pom.xml @@ -148,7 +148,7 @@ info.faljse SDNotify - 1.3 + 1.5 diff --git a/io.openems.wrapper/bnd.bnd b/io.openems.wrapper/bnd.bnd index d17d33b1981..9a03568364a 100644 --- a/io.openems.wrapper/bnd.bnd +++ b/io.openems.wrapper/bnd.bnd @@ -18,7 +18,7 @@ Bundle-Description: This wraps external java libraries that do not have OSGi hea eu.chargetime.ocpp:OCPP-J;version='1.0.2',\ eu.chargetime.ocpp:common;version='1.0.2',\ eu.chargetime.ocpp:v1_6;version='1.1.0',\ - info.faljse:SDNotify;version='1.3',\ + info.faljse:SDNotify;version='1.5',\ io.reactivex.rxjava3.rxjava;version='3.1.6',\ com.google.gson;version='2.10.1',\ de.bytefish:pgbulkinsert;version='8.1',\ diff --git a/io.openems.wrapper/sdnotify.bnd b/io.openems.wrapper/sdnotify.bnd index ec486a090ce..8470272e1ea 100644 --- a/io.openems.wrapper/sdnotify.bnd +++ b/io.openems.wrapper/sdnotify.bnd @@ -2,9 +2,9 @@ Bundle-Name: SDNotify Bundle-Description: SDNotify implements the systemd notification protocol in Java. Bundle-DocURL: https://github.com/faljse/SDNotify Bundle-License: https://opensource.org/licenses/LGPL-2.1 -Bundle-Version: 1.3 +Bundle-Version: 1.5 -Include-Resource: @SDNotify-1.3.jar +Include-Resource: @SDNotify-1.5.jar -dsannotations: * From 92539ce8faba385c7732a99584c00c8c21f89de3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Aug 2023 00:59:24 +0200 Subject: [PATCH 17/32] Bump org.rrd4j:rrd4j from 3.8.2 to 3.9 in /cnf (#2327) * Bump org.rrd4j:rrd4j from 3.8.2 to 3.9 in /cnf Bumps [org.rrd4j:rrd4j](https://github.com/rrd4j/rrd4j) from 3.8.2 to 3.9. - [Changelog](https://github.com/rrd4j/rrd4j/blob/master/changelog.txt) - [Commits](https://github.com/rrd4j/rrd4j/commits) --- updated-dependencies: - dependency-name: org.rrd4j:rrd4j dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Update bndrun --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Stefan Feilmeier --- cnf/pom.xml | 2 +- io.openems.edge.application/EdgeApp.bndrun | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cnf/pom.xml b/cnf/pom.xml index 36e179535d8..f570b23b8a1 100644 --- a/cnf/pom.xml +++ b/cnf/pom.xml @@ -406,7 +406,7 @@ org.rrd4j rrd4j - 3.8.2 + 3.9 diff --git a/io.openems.edge.application/EdgeApp.bndrun b/io.openems.edge.application/EdgeApp.bndrun index 82d7fcd8ebf..48205032d32 100644 --- a/io.openems.edge.application/EdgeApp.bndrun +++ b/io.openems.edge.application/EdgeApp.bndrun @@ -395,4 +395,4 @@ org.osgi.util.function;version='[1.2.0,1.2.1)',\ org.osgi.util.promise;version='[1.3.0,1.3.1)',\ reactive-streams;version='[1.0.4,1.0.5)',\ - rrd4j;version='[3.8.2,3.8.3)' \ No newline at end of file + rrd4j;version='[3.9.0,3.9.1)' \ No newline at end of file From b8e5747bdf07c6917661d98cc3621853e201183d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Aug 2023 01:22:25 +0200 Subject: [PATCH 18/32] Bump org.apache.felix:org.apache.felix.http.jetty from 5.0.4 to 5.0.6 in /cnf (#2326) * Bump org.apache.felix:org.apache.felix.http.jetty in /cnf Bumps org.apache.felix:org.apache.felix.http.jetty from 5.0.4 to 5.0.6. --- updated-dependencies: - dependency-name: org.apache.felix:org.apache.felix.http.jetty dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Update bndrun --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Stefan Feilmeier --- cnf/pom.xml | 2 +- io.openems.backend.application/BackendApp.bndrun | 2 +- io.openems.edge.application/EdgeApp.bndrun | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cnf/pom.xml b/cnf/pom.xml index f570b23b8a1..c3b66681c7a 100644 --- a/cnf/pom.xml +++ b/cnf/pom.xml @@ -208,7 +208,7 @@ org.apache.felix org.apache.felix.http.jetty - 5.0.4 + 5.0.6 diff --git a/io.openems.backend.application/BackendApp.bndrun b/io.openems.backend.application/BackendApp.bndrun index a7fceec5267..c21fbcb65db 100644 --- a/io.openems.backend.application/BackendApp.bndrun +++ b/io.openems.backend.application/BackendApp.bndrun @@ -105,7 +105,7 @@ org.apache.felix.configadmin;version='[1.9.26,1.9.27)',\ org.apache.felix.eventadmin;version='[1.6.4,1.6.5)',\ org.apache.felix.fileinstall;version='[3.7.4,3.7.5)',\ - org.apache.felix.http.jetty;version='[5.0.4,5.0.5)',\ + org.apache.felix.http.jetty;version='[5.0.6,5.0.7)',\ org.apache.felix.http.servlet-api;version='[2.1.0,2.1.1)',\ org.apache.felix.inventory;version='[1.1.0,1.1.1)',\ org.apache.felix.metatype;version='[1.2.4,1.2.5)',\ diff --git a/io.openems.edge.application/EdgeApp.bndrun b/io.openems.edge.application/EdgeApp.bndrun index 48205032d32..ef8e17d0c4e 100644 --- a/io.openems.edge.application/EdgeApp.bndrun +++ b/io.openems.edge.application/EdgeApp.bndrun @@ -372,7 +372,7 @@ org.apache.felix.configadmin;version='[1.9.26,1.9.27)',\ org.apache.felix.eventadmin;version='[1.6.4,1.6.5)',\ org.apache.felix.fileinstall;version='[3.7.4,3.7.5)',\ - org.apache.felix.http.jetty;version='[5.0.4,5.0.5)',\ + org.apache.felix.http.jetty;version='[5.0.6,5.0.7)',\ org.apache.felix.http.servlet-api;version='[2.1.0,2.1.1)',\ org.apache.felix.inventory;version='[1.1.0,1.1.1)',\ org.apache.felix.metatype;version='[1.2.4,1.2.5)',\ From 4c8f3a67cab91088be90dd761b2fb6db5061afb3 Mon Sep 17 00:00:00 2001 From: Stefan Feilmeier Date: Thu, 24 Aug 2023 01:41:28 +0200 Subject: [PATCH 19/32] ModbusBridge: Fix ClassCastException to Register[] (#2320) This fixes the issue reported in https://community.openems.io/t/modbus-tcp-gerat-mit-eclipse-debuggen/1839/2 * Add JUnit test that shows previous implementation error * Parse FC4 response to Register[] Tested on private setup --- ...sk.java => AbstractReadRegistersTask.java} | 16 ++--- .../modbus/api/task/FC3ReadRegistersTask.java | 6 +- .../api/task/FC4ReadInputRegistersTask.java | 16 +++-- .../bridge/modbus/DummyModbusComponent.java | 4 +- .../element/SignedDoublewordElementTest.java | 1 - .../api/element/StringWordElementTest.java | 3 - .../modbus/api/task/FC1ReadCoilsTaskTest.java | 23 ++++++- .../api/task/FC2ReadInputsTaskTest.java | 23 ++++++- .../api/task/FC3ReadRegistersTaskTest.java | 27 ++++++-- .../task/FC4ReadInputRegistersTaskTest.java | 63 +++++++++++++++++-- 10 files changed, 149 insertions(+), 33 deletions(-) rename io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/{AbstractReadInputRegistersTask.java => AbstractReadRegistersTask.java} (61%) diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadInputRegistersTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadRegistersTask.java similarity index 61% rename from io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadInputRegistersTask.java rename to io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadRegistersTask.java index 939fad4ddfb..936971a7464 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadInputRegistersTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadRegistersTask.java @@ -4,7 +4,7 @@ import com.ghgande.j2mod.modbus.msg.ModbusRequest; import com.ghgande.j2mod.modbus.msg.ModbusResponse; -import com.ghgande.j2mod.modbus.procimg.InputRegister; +import com.ghgande.j2mod.modbus.procimg.Register; import io.openems.common.exceptions.OpenemsException; import io.openems.edge.bridge.modbus.api.element.ModbusElement; @@ -12,23 +12,25 @@ import io.openems.edge.common.taskmanager.Priority; @SuppressWarnings("rawtypes") -public abstract class AbstractReadInputRegistersTask - extends AbstractReadTask { +public abstract class AbstractReadRegistersTask // + extends AbstractReadTask { - public AbstractReadInputRegistersTask(String name, Class responseClazz, int startAddress, - Priority priority, ModbusElement... elements) { + public AbstractReadRegistersTask(String name, Class responseClazz, int startAddress, Priority priority, + ModbusElement... elements) { super(name, responseClazz, ModbusRegisterElement.class, startAddress, priority, elements); } @SuppressWarnings("unchecked") @Override - protected void handleResponse(ModbusRegisterElement element, int position, InputRegister[] response) + protected final void handleResponse(ModbusRegisterElement element, int position, Register[] response) throws OpenemsException { element.setInputValue(Arrays.copyOfRange(response, position, position + element.length)); } @Override - protected int calculateNextPosition(ModbusElement modbusElement, int position) { + protected final int calculateNextPosition(ModbusElement modbusElement, int position) { return position + modbusElement.length; } } \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC3ReadRegistersTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC3ReadRegistersTask.java index 8b32d90e58f..d1d104a28fd 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC3ReadRegistersTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC3ReadRegistersTask.java @@ -2,7 +2,7 @@ import com.ghgande.j2mod.modbus.msg.ReadMultipleRegistersRequest; import com.ghgande.j2mod.modbus.msg.ReadMultipleRegistersResponse; -import com.ghgande.j2mod.modbus.procimg.InputRegister; +import com.ghgande.j2mod.modbus.procimg.Register; import io.openems.common.exceptions.OpenemsException; import io.openems.edge.bridge.modbus.api.ModbusUtils; @@ -14,7 +14,7 @@ * (http://www.simplymodbus.ca/FC03.htm). */ public class FC3ReadRegistersTask - extends AbstractReadInputRegistersTask { + extends AbstractReadRegistersTask { public FC3ReadRegistersTask(int startAddress, Priority priority, ModbusElement... elements) { super("FC3ReadHoldingRegisters", ReadMultipleRegistersResponse.class, startAddress, priority, elements); @@ -26,7 +26,7 @@ protected ReadMultipleRegistersRequest createModbusRequest() { } @Override - protected InputRegister[] parseResponse(ReadMultipleRegistersResponse response) throws OpenemsException { + protected Register[] parseResponse(ReadMultipleRegistersResponse response) throws OpenemsException { return response.getRegisters(); } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC4ReadInputRegistersTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC4ReadInputRegistersTask.java index 568971022b7..e05190fd759 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC4ReadInputRegistersTask.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC4ReadInputRegistersTask.java @@ -1,8 +1,11 @@ package io.openems.edge.bridge.modbus.api.task; +import java.util.stream.Stream; + import com.ghgande.j2mod.modbus.msg.ReadInputRegistersRequest; import com.ghgande.j2mod.modbus.msg.ReadInputRegistersResponse; -import com.ghgande.j2mod.modbus.procimg.InputRegister; +import com.ghgande.j2mod.modbus.procimg.Register; +import com.ghgande.j2mod.modbus.procimg.SimpleRegister; import io.openems.common.exceptions.OpenemsException; import io.openems.edge.bridge.modbus.api.ModbusUtils; @@ -14,7 +17,7 @@ * (http://www.simplymodbus.ca/FC04.htm). */ public class FC4ReadInputRegistersTask - extends AbstractReadInputRegistersTask { + extends AbstractReadRegistersTask { public FC4ReadInputRegistersTask(int startAddress, Priority priority, ModbusElement... elements) { super("FC4ReadInputRegisters", ReadInputRegistersResponse.class, startAddress, priority, elements); @@ -26,8 +29,13 @@ protected ReadInputRegistersRequest createModbusRequest() { } @Override - protected InputRegister[] parseResponse(ReadInputRegistersResponse response) throws OpenemsException { - return response.getRegisters(); + protected Register[] parseResponse(ReadInputRegistersResponse response) throws OpenemsException { + return Stream.of(response.getRegisters()) // + .map(r -> { + var bs = r.toBytes(); + return new SimpleRegister(bs[0], bs[1]); + }) // + .toArray(Register[]::new); } @Override diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/DummyModbusComponent.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/DummyModbusComponent.java index 48120802422..f0b8ee7d3e6 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/DummyModbusComponent.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/DummyModbusComponent.java @@ -4,8 +4,8 @@ import io.openems.common.exceptions.OpenemsException; import io.openems.common.utils.ConfigUtils; +import io.openems.edge.bridge.modbus.api.AbstractModbusBridge; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; -import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; import io.openems.edge.bridge.modbus.api.ModbusProtocol; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; @@ -29,7 +29,7 @@ public DummyModbusComponent(String id, String bridgeId) throws OpenemsException this(id, new DummyModbusBridge(bridgeId), DEFAULT_UNIT_ID, new io.openems.edge.common.channel.ChannelId[0]); } - public DummyModbusComponent(String id, BridgeModbus bridge, int unitId, + public DummyModbusComponent(String id, AbstractModbusBridge bridge, int unitId, io.openems.edge.common.channel.ChannelId[] additionalChannelIds) throws OpenemsException { super(// OpenemsComponent.ChannelId.values(), // diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/SignedDoublewordElementTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/SignedDoublewordElementTest.java index eee2d847bb3..96272d628dd 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/SignedDoublewordElementTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/SignedDoublewordElementTest.java @@ -37,7 +37,6 @@ public void testReadBigEndianLswMsw() throws OpenemsException { new SimpleRegister((byte) 0xAB, (byte) 0xCD), // new SimpleRegister((byte) 0x12, (byte) 0x34) // }); - System.out.println(sut.channel.getNextValue().get()); assertEquals(0x1234_ABCDL, sut.channel.getNextValue().get()); } diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/StringWordElementTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/StringWordElementTest.java index 80881998f4f..99d1d15c658 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/StringWordElementTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/StringWordElementTest.java @@ -73,9 +73,6 @@ public void testWriteBigEndian() throws IllegalArgumentException, OpenemsNamedEx var sut = new ModbusTest.FC16WriteRegisters<>(new StringWordElement(0, 6), STRING); sut.channel.setNextWriteValueFromObject("OpenEMS"); var registers = sut.element.getNextWriteValueAndReset(); - for (var reg : registers) { - System.out.println(Integer.toHexString(reg.toBytes()[0]) + " " + Integer.toHexString(reg.toBytes()[1])); - } assertEquals(6, registers.length); assertArrayEquals(new byte[] { (byte) 0x4F, (byte) 0x70 }, registers[0].toBytes()); assertArrayEquals(new byte[] { (byte) 0x65, (byte) 0x6E }, registers[1].toBytes()); diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC1ReadCoilsTaskTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC1ReadCoilsTaskTest.java index 9bccb7feb3f..596edf36f38 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC1ReadCoilsTaskTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC1ReadCoilsTaskTest.java @@ -1,6 +1,10 @@ package io.openems.edge.bridge.modbus.api.task; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.IntStream; import org.junit.Test; @@ -15,9 +19,14 @@ public class FC1ReadCoilsTaskTest { @Test - public void testToLogMessage() throws OpenemsException { + public void test() throws OpenemsException { var component = new DummyModbusComponent(); - var task = new FC1ReadCoilsTask(10, Priority.HIGH, new CoilElement(10), new CoilElement(11)); + var value = new AtomicReference(); + var element10 = new CoilElement(10); + element10.onUpdateCallback(v -> value.set(v)); + var element11 = new CoilElement(11); + element11.onUpdateCallback(v -> value.set(v)); + var task = new FC1ReadCoilsTask(10, Priority.HIGH, element10, element11); task.setParent(component); var request = task.createModbusRequest(); var response = (ReadCoilsResponse) request.getResponse(); @@ -26,5 +35,15 @@ public void testToLogMessage() throws OpenemsException { assertEquals("FC1ReadCoils [device0;unitid=1;priority=HIGH;ref=10/0xa;length=2;response=10]", task.toLogMessage(LogVerbosity.READS_AND_WRITES_VERBOSE, request, response)); + + var coils = response.getCoils(); + var values = IntStream.range(0, coils.size()) // + .mapToObj(i -> Boolean.valueOf(coils.getBit(i))) // + .toArray(Boolean[]::new); + assertNull(value.get()); + task.handleResponse(element10, 0, values); + assertEquals(Boolean.valueOf(true), value.get()); + task.handleResponse(element11, 1, values); + assertEquals(Boolean.valueOf(false), value.get()); } } diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC2ReadInputsTaskTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC2ReadInputsTaskTest.java index 10dd5f3585e..d5fce0d6b82 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC2ReadInputsTaskTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC2ReadInputsTaskTest.java @@ -1,6 +1,10 @@ package io.openems.edge.bridge.modbus.api.task; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.IntStream; import org.junit.Test; @@ -15,9 +19,14 @@ public class FC2ReadInputsTaskTest { @Test - public void testToLogMessage() throws OpenemsException { + public void test() throws OpenemsException { var component = new DummyModbusComponent(); - var task = new FC2ReadInputsTask(10, Priority.HIGH, new CoilElement(10), new CoilElement(11)); + var value = new AtomicReference(); + var element10 = new CoilElement(10); + element10.onUpdateCallback(v -> value.set(v)); + var element11 = new CoilElement(11); + element11.onUpdateCallback(v -> value.set(v)); + var task = new FC2ReadInputsTask(10, Priority.HIGH, element10, element11); task.setParent(component); var request = task.createModbusRequest(); var response = (ReadInputDiscretesResponse) request.getResponse(); @@ -26,5 +35,15 @@ public void testToLogMessage() throws OpenemsException { assertEquals("FC2ReadCoils [device0;unitid=1;priority=HIGH;ref=10/0xa;length=2;response=10]", task.toLogMessage(LogVerbosity.READS_AND_WRITES_VERBOSE, request, response)); + + var discretes = response.getDiscretes(); + var values = IntStream.range(0, discretes.size()) // + .mapToObj(i -> Boolean.valueOf(discretes.getBit(i))) // + .toArray(Boolean[]::new); + assertNull(value.get()); + task.handleResponse(element10, 0, values); + assertEquals(Boolean.valueOf(true), value.get()); + task.handleResponse(element11, 1, values); + assertEquals(Boolean.valueOf(false), value.get()); } } diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC3ReadRegistersTaskTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC3ReadRegistersTaskTest.java index 061a2590d59..0edc51a082a 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC3ReadRegistersTaskTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC3ReadRegistersTaskTest.java @@ -1,6 +1,9 @@ package io.openems.edge.bridge.modbus.api.task; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.util.concurrent.atomic.AtomicReference; import org.junit.Test; @@ -17,15 +20,31 @@ public class FC3ReadRegistersTaskTest { @Test - public void testToLogMessage() throws OpenemsException { + public void test() throws OpenemsException { var component = new DummyModbusComponent(); - var task = new FC3ReadRegistersTask(20, Priority.LOW, new UnsignedDoublewordElement(20)); + var value = new AtomicReference(); + var element20 = new UnsignedDoublewordElement(20); + element20.onUpdateCallback(v -> value.set(v)); + var element22 = new UnsignedDoublewordElement(22); + element22.onUpdateCallback(v -> value.set(v)); + + var task = new FC3ReadRegistersTask(20, Priority.LOW, element20, element22); task.setParent(component); var request = task.createModbusRequest(); var response = (ReadMultipleRegistersResponse) request.getResponse(); - response.setRegisters(new Register[] { new SimpleRegister(100), new SimpleRegister(200) }); + response.setRegisters(new Register[] { // + new SimpleRegister(100), new SimpleRegister(200), // + new SimpleRegister(300), new SimpleRegister(400) }); - assertEquals("FC3ReadHoldingRegisters [device0;unitid=1;priority=LOW;ref=20/0x14;length=2;response=0064 00c8]", + assertEquals( + "FC3ReadHoldingRegisters [device0;unitid=1;priority=LOW;ref=20/0x14;length=4;response=0064 00c8 012c 0190]", task.toLogMessage(LogVerbosity.READS_AND_WRITES_VERBOSE, request, response)); + + var registers = task.parseResponse(response); + assertNull(value.get()); + task.handleResponse(element20, 0, registers); + assertEquals(Long.valueOf(6553800), value.get()); + task.handleResponse(element22, 2, registers); + assertEquals(Long.valueOf(19661200), value.get()); } } diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC4ReadInputRegistersTaskTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC4ReadInputRegistersTaskTest.java index 1bc27a12d6e..04a63b525ce 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC4ReadInputRegistersTaskTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/task/FC4ReadInputRegistersTaskTest.java @@ -1,11 +1,15 @@ package io.openems.edge.bridge.modbus.api.task; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.util.concurrent.atomic.AtomicReference; import org.junit.Test; +import com.ghgande.j2mod.modbus.procimg.InputRegister; import com.ghgande.j2mod.modbus.procimg.Register; -import com.ghgande.j2mod.modbus.procimg.SimpleRegister; +import com.ghgande.j2mod.modbus.procimg.SimpleInputRegister; import io.openems.common.exceptions.OpenemsException; import io.openems.edge.bridge.modbus.DummyModbusComponent; @@ -15,16 +19,65 @@ public class FC4ReadInputRegistersTaskTest { + /** + * Custom implementation that only implements {@link InputRegister} and not + * {@link Register}. This is the difference to {@link SimpleInputRegister}. + */ + public static class MyInputRegister implements InputRegister { + + private final SimpleInputRegister delegate; + + public MyInputRegister(int value) { + this.delegate = new SimpleInputRegister(value); + } + + @Override + public int getValue() { + return this.delegate.getValue(); + } + + @Override + public int toUnsignedShort() { + return this.delegate.toUnsignedShort(); + } + + @Override + public short toShort() { + return this.delegate.toShort(); + } + + @Override + public byte[] toBytes() { + return this.delegate.toBytes(); + } + + } + @Test - public void testToLogMessage() throws OpenemsException { + public void test() throws OpenemsException { var component = new DummyModbusComponent(); - var task = new FC4ReadInputRegistersTask(20, Priority.LOW, new UnsignedDoublewordElement(20)); + var value = new AtomicReference(); + var element20 = new UnsignedDoublewordElement(20); + element20.onUpdateCallback(v -> value.set(v)); + var element22 = new UnsignedDoublewordElement(22); + element22.onUpdateCallback(v -> value.set(v)); + var task = new FC4ReadInputRegistersTask(20, Priority.LOW, element20, element22); task.setParent(component); var request = task.createModbusRequest(); var response = request.getResponse(); - response.setRegisters(new Register[] { new SimpleRegister(987), new SimpleRegister(654) }); + response.setRegisters(new InputRegister[] { // + new MyInputRegister(987), new MyInputRegister(654), // + new MyInputRegister(321), new MyInputRegister(0) }); - assertEquals("FC4ReadInputRegisters [device0;unitid=1;priority=LOW;ref=20/0x14;length=2;response=03db 028e]", + assertEquals( + "FC4ReadInputRegisters [device0;unitid=1;priority=LOW;ref=20/0x14;length=4;response=03db 028e 0141 0000]", task.toLogMessage(LogVerbosity.READS_AND_WRITES_VERBOSE, request, response)); + + var registers = task.parseResponse(response); + assertNull(value.get()); + task.handleResponse(element20, 0, registers); + assertEquals(Long.valueOf(64684686), value.get()); + task.handleResponse(element22, 2, registers); + assertEquals(Long.valueOf(21037056), value.get()); } } From f0aba8a39307ac94fe6391955c0376f6c11a9abb Mon Sep 17 00:00:00 2001 From: Stefan Feilmeier Date: Thu, 24 Aug 2023 12:34:35 +0200 Subject: [PATCH 20/32] Update Gradle to 8.3 (#2330) See https://github.com/gradle/gradle/releases/tag/v8.3.0 --- .gradle-wrapper/gradle-wrapper.jar | Bin 62076 -> 63375 bytes .gradle-wrapper/gradle-wrapper.properties | 3 ++- gradlew | 5 ++++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.gradle-wrapper/gradle-wrapper.jar b/.gradle-wrapper/gradle-wrapper.jar index c1962a79e29d3e0ab67b14947c167a862655af9b..033e24c4cdf41af1ab109bc7f253b2b887023340 100644 GIT binary patch delta 16170 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 delta 14892 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 diff --git a/.gradle-wrapper/gradle-wrapper.properties b/.gradle-wrapper/gradle-wrapper.properties index a3638774392..ac72c34e8ac 100644 --- a/.gradle-wrapper/gradle-wrapper.properties +++ b/.gradle-wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index e8b642ad625..fff4e06f1f1 100755 --- a/gradlew +++ b/gradlew @@ -130,10 +130,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. From 63c0ce67e8914da32020e2de2b5805f2115ce032 Mon Sep 17 00:00:00 2001 From: Stefan Feilmeier Date: Fri, 25 Aug 2023 15:03:22 +0200 Subject: [PATCH 21/32] Implement ENTSO-E, OpenEMS Currency and exchangerate.host (#2207) This PR adds: - ENTSO-E as Time-of-Use Tariff provider for day-ahead prices in European power grids. These tariffs can then be used e.g. in combination with "ESS Time-of-Use Tariff Discharge Controller" (https://github.com/OpenEMS/openems/tree/develop/io.openems.edge.controller.ess.timeofusetariff.discharge) or its future replacement in #2238. - Global configuration for "Currency" in the Core.Meta Component. Every monetary value in OpenEMS is inherently expressed in this Currency. Values obtained in a different currency (e.g. energy prices from a web service) have to be internally converted to this Currency. - "exchangerate.host" (Exchange rates API is a free service for current and historical foreign exchange rates & crypto currencies rates). The implementation is currently only local to the ENTSO-E bundle, because we currently do not need this feature anywhere else yet. In future this could be extracted to a separate service. _If you or your company use this project or like what [exchangerate.host] is doing, please consider backing [them] so [they] can continue maintaining and evolving this project." -> "https://exchangerate.host/#/donate" To request a (free) authentication token for the ENTSO-E transparency platform, please see chapter "2. Authentication and Authorisation" in the official API documentation: https://transparency.entsoe.eu/content/static_content/Static%20content/web%20api/Guide.html#_authentication_and_authorisation Co-authored-by: Sagar Bandi Venu --- .../src/io/openems/common/utils/XmlUtils.java | 47 ++ io.openems.edge.application/EdgeApp.bndrun | 2 + .../edge/common/currency/Currency.java | 34 ++ .../edge/common/currency/CurrencyConfig.java | 33 + .../edge/common/currency/package-info.java | 3 + .../src/io/openems/edge/common/meta/Meta.java | 41 +- .../openems/edge/common/test/DummyMeta.java | 22 + .../src/io/openems/edge/core/meta/Config.java | 6 + .../io/openems/edge/core/meta/MetaImpl.java | 9 +- .../.classpath | 12 + .../.gitignore | 2 + .../.project | 23 + .../org.eclipse.core.resources.prefs | 2 + .../bnd.bnd | 16 + .../readme.adoc | 11 + .../timeofusetariff/entsoe/BiddingZone.java | 38 ++ .../edge/timeofusetariff/entsoe/Config.java | 28 + .../timeofusetariff/entsoe/EntsoeApi.java | 82 +++ .../entsoe/ExchangeRateApi.java | 44 ++ .../timeofusetariff/entsoe/TouEntsoe.java | 27 + .../timeofusetariff/entsoe/TouEntsoeImpl.java | 162 +++++ .../edge/timeofusetariff/entsoe/Utils.java | 174 ++++++ .../test/.gitignore | 0 .../timeofusetariff/entsoe/EntsoeApiTest.java | 25 + .../entsoe/ExchangeRateApiTest.java | 214 +++++++ .../edge/timeofusetariff/entsoe/MyConfig.java | 63 ++ .../timeofusetariff/entsoe/ParserTest.java | 564 ++++++++++++++++++ .../timeofusetariff/entsoe/TouEntsoeTest.java | 25 + 28 files changed, 1706 insertions(+), 3 deletions(-) create mode 100644 io.openems.edge.common/src/io/openems/edge/common/currency/Currency.java create mode 100644 io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyConfig.java create mode 100644 io.openems.edge.common/src/io/openems/edge/common/currency/package-info.java create mode 100644 io.openems.edge.common/src/io/openems/edge/common/test/DummyMeta.java create mode 100644 io.openems.edge.timeofusetariff.entsoe/.classpath create mode 100644 io.openems.edge.timeofusetariff.entsoe/.gitignore create mode 100644 io.openems.edge.timeofusetariff.entsoe/.project create mode 100644 io.openems.edge.timeofusetariff.entsoe/.settings/org.eclipse.core.resources.prefs create mode 100644 io.openems.edge.timeofusetariff.entsoe/bnd.bnd create mode 100644 io.openems.edge.timeofusetariff.entsoe/readme.adoc create mode 100644 io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/BiddingZone.java create mode 100644 io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Config.java create mode 100644 io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/EntsoeApi.java create mode 100644 io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApi.java create mode 100644 io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoe.java create mode 100644 io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java create mode 100644 io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Utils.java create mode 100644 io.openems.edge.timeofusetariff.entsoe/test/.gitignore create mode 100644 io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/EntsoeApiTest.java create mode 100644 io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApiTest.java create mode 100644 io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/MyConfig.java create mode 100644 io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ParserTest.java create mode 100644 io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java diff --git a/io.openems.common/src/io/openems/common/utils/XmlUtils.java b/io.openems.common/src/io/openems/common/utils/XmlUtils.java index 6431897bda9..b921670e906 100644 --- a/io.openems.common/src/io/openems/common/utils/XmlUtils.java +++ b/io.openems.common/src/io/openems/common/utils/XmlUtils.java @@ -1,7 +1,11 @@ package io.openems.common.utils; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; +import java.util.NoSuchElementException; +import java.util.stream.IntStream; +import java.util.stream.Stream; import org.w3c.dom.DOMException; import org.w3c.dom.NamedNodeMap; @@ -207,4 +211,47 @@ public static String getContentAsString(Node node) { public static int getContentAsInt(Node node) { return Integer.parseInt(node.getTextContent()); } + + /** + * Iterates through a {@link Node}. + * + *

    + * Source: https://stackoverflow.com/a/48153597/4137113 + * + * @param node the {@link Node} + * @return the {@link Iterable} + */ + public static Iterable list(final Node node) { + return () -> new Iterator() { + + private int index = 0; + + @Override + public boolean hasNext() { + return this.index < node.getChildNodes().getLength(); + } + + @Override + public Node next() { + if (!this.hasNext()) { + throw new NoSuchElementException(); + } + return node.getChildNodes().item(this.index++); + } + }; + } + + /** + * Iterates over {@link Node} through {@link Stream}. + * + *

    + * Source: https://stackoverflow.com/a/62171621/4137113 + * + * @param node the {@link Node} + * @return the {@link Stream} + */ + public static Stream stream(final Node node) { + var childNodes = node.getChildNodes(); + return IntStream.range(0, childNodes.getLength()).boxed().map(childNodes::item); + } } diff --git a/io.openems.edge.application/EdgeApp.bndrun b/io.openems.edge.application/EdgeApp.bndrun index ef8e17d0c4e..8de72d9193d 100644 --- a/io.openems.edge.application/EdgeApp.bndrun +++ b/io.openems.edge.application/EdgeApp.bndrun @@ -177,6 +177,7 @@ bnd.identity;id='io.openems.edge.timedata.rrd4j',\ bnd.identity;id='io.openems.edge.timeofusetariff.awattar',\ bnd.identity;id='io.openems.edge.timeofusetariff.corrently',\ + bnd.identity;id='io.openems.edge.timeofusetariff.entsoe',\ bnd.identity;id='io.openems.edge.timeofusetariff.tibber',\ -runbundles: \ @@ -342,6 +343,7 @@ io.openems.edge.timeofusetariff.api;version=snapshot,\ io.openems.edge.timeofusetariff.awattar;version=snapshot,\ io.openems.edge.timeofusetariff.corrently;version=snapshot,\ + io.openems.edge.timeofusetariff.entsoe;version=snapshot,\ io.openems.edge.timeofusetariff.tibber;version=snapshot,\ io.openems.shared.influxdb;version=snapshot,\ io.openems.wrapper.eu.chargetime.ocpp;version=snapshot,\ diff --git a/io.openems.edge.common/src/io/openems/edge/common/currency/Currency.java b/io.openems.edge.common/src/io/openems/edge/common/currency/Currency.java new file mode 100644 index 00000000000..fca2ebca5b5 --- /dev/null +++ b/io.openems.edge.common/src/io/openems/edge/common/currency/Currency.java @@ -0,0 +1,34 @@ +package io.openems.edge.common.currency; + +import io.openems.common.types.OptionsEnum; + +public enum Currency implements OptionsEnum { + UNDEFINED(-1, "-"), // + EUR(0, "€"), // + SEK(1, "kr"), // + ; + + private final String name; + private final int value; + + private Currency(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return Currency.UNDEFINED; + } + +} diff --git a/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyConfig.java b/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyConfig.java new file mode 100644 index 00000000000..aef402c2f16 --- /dev/null +++ b/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyConfig.java @@ -0,0 +1,33 @@ +package io.openems.edge.common.currency; + +import io.openems.edge.common.meta.Meta; +import io.openems.edge.common.meta.Meta.ChannelId; + +/** + * The {@link ChannelId#CURRENCY} mandates the selection of the 'currency' + * configuration property of this specific type. Subsequently, this selected + * property is transformed into the corresponding {@link Currency} type before + * being written through {@link Meta#_setCurrency(Currency)}. + */ +public enum CurrencyConfig { + /** + * Euro. + */ + EUR, + /** + * Swedish Krona. + */ + SEK; + + /** + * Converts the {@link CurrencyConfig} to the {@link Currency}. + * + * @return The {@link Currency}. + */ + public Currency toCurrency() { + return switch (this) { + case EUR -> Currency.EUR; + case SEK -> Currency.SEK; + }; + } +} diff --git a/io.openems.edge.common/src/io/openems/edge/common/currency/package-info.java b/io.openems.edge.common/src/io/openems/edge/common/currency/package-info.java new file mode 100644 index 00000000000..af89b5b5e53 --- /dev/null +++ b/io.openems.edge.common/src/io/openems/edge/common/currency/package-info.java @@ -0,0 +1,3 @@ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package io.openems.edge.common.currency; diff --git a/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java b/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java index 7d2c007b7a4..dd7c72b89fb 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java +++ b/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java @@ -4,6 +4,9 @@ import io.openems.common.channel.AccessMode; import io.openems.common.types.OpenemsType; import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.channel.EnumReadChannel; +import io.openems.edge.common.channel.value.Value; +import io.openems.edge.common.currency.Currency; import io.openems.edge.common.modbusslave.ModbusSlave; import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable; import io.openems.edge.common.modbusslave.ModbusSlaveTable; @@ -22,7 +25,17 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { *

  • Type: String * */ - VERSION(Doc.of(OpenemsType.STRING)); + VERSION(Doc.of(OpenemsType.STRING)), + + /** + * Edge currency. + * + *
      + *
    • Interface: Meta + *
    • Type: Currency + *
    + */ + CURRENCY(Doc.of(Currency.values())); private final Doc doc; @@ -52,4 +65,30 @@ public default ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { .build()); } + /** + * Gets the Channel for {@link ChannelId#CURRENCY}. + * + * @return the Channel + */ + public default EnumReadChannel getCurrencyChannel() { + return this.channel(ChannelId.CURRENCY); + } + + /** + * Gets the Capacity in [Wh]. See {@link ChannelId#CURRENCY}. + * + * @return the Channel {@link Value} + */ + public default Currency getCurrency() { + return this.getCurrencyChannel().value().asEnum(); + } + + /** + * Internal method to set the 'nextValue' on {@link ChannelId#CURRENCY} Channel. + * + * @param value the next value + */ + public default void _setCurrency(Currency value) { + this.getCurrencyChannel().setNextValue(value); + } } diff --git a/io.openems.edge.common/src/io/openems/edge/common/test/DummyMeta.java b/io.openems.edge.common/src/io/openems/edge/common/test/DummyMeta.java new file mode 100644 index 00000000000..eed1aebcb0f --- /dev/null +++ b/io.openems.edge.common/src/io/openems/edge/common/test/DummyMeta.java @@ -0,0 +1,22 @@ +package io.openems.edge.common.test; + +import io.openems.edge.common.channel.Channel; +import io.openems.edge.common.component.AbstractOpenemsComponent; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.currency.Currency; +import io.openems.edge.common.meta.Meta; + +public class DummyMeta extends AbstractOpenemsComponent implements Meta { + + public DummyMeta(String id, Currency currency) { + super(// + OpenemsComponent.ChannelId.values(), // + Meta.ChannelId.values() // + ); + for (Channel channel : this.channels()) { + channel.nextProcessImage(); + } + this._setCurrency(currency); + super.activate(null, id, "", true); + } +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/meta/Config.java b/io.openems.edge.core/src/io/openems/edge/core/meta/Config.java index 3838315a22a..41b9dd0f734 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/meta/Config.java +++ b/io.openems.edge.core/src/io/openems/edge/core/meta/Config.java @@ -1,7 +1,10 @@ package io.openems.edge.core.meta; +import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; +import io.openems.edge.common.currency.CurrencyConfig; + @ObjectClassDefinition(// name = "Core Meta", // description = "The global manager for Metadata.") @@ -9,4 +12,7 @@ String webconsole_configurationFactory_nameHint() default "Core Meta"; + @AttributeDefinition(name = "Currency", description = "Every monetary value is inherently expressed in this Currency. Values obtained in a different currency (e.g. energy prices from a web service) are internally converted to this Currency using the current exchange rate.") + CurrencyConfig currency() default CurrencyConfig.EUR; + } \ No newline at end of file diff --git a/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java b/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java index 54af2dcffc3..0f47201664f 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java @@ -36,18 +36,20 @@ public MetaImpl() { } @Activate - private void activate(ComponentContext context) { + private void activate(ComponentContext context, Config config) { super.activate(context, SINGLETON_COMPONENT_ID, Meta.SINGLETON_SERVICE_PID, true); + this.applyConfig(config); if (OpenemsComponent.validateSingleton(this.cm, Meta.SINGLETON_SERVICE_PID, SINGLETON_COMPONENT_ID)) { return; } } @Modified - private void modified(ComponentContext context) { + private void modified(ComponentContext context, Config config) { super.modified(context, SINGLETON_COMPONENT_ID, Meta.SINGLETON_SERVICE_PID, true); + this.applyConfig(config); if (OpenemsComponent.validateSingleton(this.cm, Meta.SINGLETON_SERVICE_PID, SINGLETON_COMPONENT_ID)) { return; } @@ -59,4 +61,7 @@ protected void deactivate() { super.deactivate(); } + private void applyConfig(Config config) { + this._setCurrency(config.currency().toCurrency()); + } } diff --git a/io.openems.edge.timeofusetariff.entsoe/.classpath b/io.openems.edge.timeofusetariff.entsoe/.classpath new file mode 100644 index 00000000000..bbfbdbe40e7 --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/.classpath @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/io.openems.edge.timeofusetariff.entsoe/.gitignore b/io.openems.edge.timeofusetariff.entsoe/.gitignore new file mode 100644 index 00000000000..c2b941a96de --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/.gitignore @@ -0,0 +1,2 @@ +/bin_test/ +/generated/ diff --git a/io.openems.edge.timeofusetariff.entsoe/.project b/io.openems.edge.timeofusetariff.entsoe/.project new file mode 100644 index 00000000000..2423c50c37c --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/.project @@ -0,0 +1,23 @@ + + + io.openems.edge.timeofusetariff.entsoe + + + + + + org.eclipse.jdt.core.javabuilder + + + + + bndtools.core.bndbuilder + + + + + + org.eclipse.jdt.core.javanature + bndtools.core.bndnature + + diff --git a/io.openems.edge.timeofusetariff.entsoe/.settings/org.eclipse.core.resources.prefs b/io.openems.edge.timeofusetariff.entsoe/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000000..99f26c0203a --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/io.openems.edge.timeofusetariff.entsoe/bnd.bnd b/io.openems.edge.timeofusetariff.entsoe/bnd.bnd new file mode 100644 index 00000000000..e1809acc25a --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/bnd.bnd @@ -0,0 +1,16 @@ +Bundle-Name: OpenEMS Edge Time-Of-Use ENTSO-E +Bundle-Vendor: FENECON GmbH +Bundle-License: https://opensource.org/licenses/EPL-2.0 +Bundle-Version: 1.0.0.${tstamp} + +-buildpath: \ + ${buildpath},\ + com.squareup.okio,\ + io.openems.common,\ + io.openems.edge.common,\ + io.openems.edge.timeofusetariff.api,\ + io.openems.wrapper.okhttp,\ + +-testpath: \ + org.jetbrains.kotlin.osgi-bundle,\ + ${testpath} diff --git a/io.openems.edge.timeofusetariff.entsoe/readme.adoc b/io.openems.edge.timeofusetariff.entsoe/readme.adoc new file mode 100644 index 00000000000..aea3aee6223 --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/readme.adoc @@ -0,0 +1,11 @@ += Time-Of-Use Tariff ENTSO-E + +This implementation uses the ENTSO-E transparency platform to receive day-ahead prices in European power grids. + +To request a (free) authentication token, please see chapter "2. Authentication and Authorisation" in the official API documentation: https://transparency.entsoe.eu/content/static_content/Static%20content/web%20api/Guide.html#_authentication_and_authorisation + +Prices retrieved from ENTSO-E are subsequently converted to the user's currency (defined in Core.Meta Component) using the Exchange Rates API. + +For detailed information about the Exchange Rates API, please refer to: https://exchangerate.host/#/docs + +https://github.com/OpenEMS/openems/tree/develop/io.openems.edge.timeofusetariff.entsoe[Source Code icon:github[]] \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/BiddingZone.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/BiddingZone.java new file mode 100644 index 00000000000..1ade97fa231 --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/BiddingZone.java @@ -0,0 +1,38 @@ +package io.openems.edge.timeofusetariff.entsoe; + +/** + * https://transparency.entsoe.eu/content/static_content/Static%20content/web%20api/Guide.html#_areas. + */ +public enum BiddingZone { + /** + * BZN|DE-LU. + */ + GERMANY("10Y1001A1001A82H"), // + /** + * BZN|AT. + */ + AUSTRIA("10YAT-APG------L"), // + /** + * BZN|SE1. + */ + SWEDEN_SE1("10Y1001A1001A44P"), // + /** + * BZN|SE2. + */ + SWEDEN_SE2("10Y1001A1001A45N"), // + /** + * BZN|SE3. + */ + SWEDEN_SE3("10Y1001A1001A46L"), // + /** + * BZN|SE4. + */ + SWEDEN_SE4("10Y1001A1001A47J"), // + ; + + public final String code; + + private BiddingZone(String code) { + this.code = code; + } +} diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Config.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Config.java new file mode 100644 index 00000000000..dfc74112e38 --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Config.java @@ -0,0 +1,28 @@ +package io.openems.edge.timeofusetariff.entsoe; + +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.AttributeType; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +@ObjectClassDefinition(// + name = "Time-Of-Use Tariff ENTSO-E", // + description = "Time-Of-Use Tariff implementation that uses the ENTSO-E transparency platform.") +@interface Config { + + @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component") + String id() default "timeOfUseTariff0"; + + @AttributeDefinition(name = "Alias", description = "Human-readable name of this Component; defaults to Component-ID") + String alias() default ""; + + @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?") + boolean enabled() default true; + + @AttributeDefinition(name = "Security Token", description = "Security token for the ENTSO-E Transparency Platform", type = AttributeType.PASSWORD) + String securityToken(); + + @AttributeDefinition(name = "Bidding Zone", description = "Zone corresponding to the customer's location") + BiddingZone biddingZone(); + + String webconsole_configurationFactory_nameHint() default "Time-Of-Use Tariff ENTSO-E [{id}]"; +} \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/EntsoeApi.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/EntsoeApi.java new file mode 100644 index 00000000000..1ea088b09d1 --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/EntsoeApi.java @@ -0,0 +1,82 @@ +package io.openems.edge.timeofusetariff.entsoe; + +import java.io.IOException; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; + +public class EntsoeApi { + + public static final DateTimeFormatter FORMATTER_MINUTES = DateTimeFormatter.ofPattern("u-MM-dd'T'HH:mmX"); + public static final DateTimeFormatter FORMATTER_SECONDS = DateTimeFormatter.ofPattern("u-MM-dd'T'HH:mm:ssX"); + public static final ZoneId UTC = ZoneId.of("UTC"); + public static final String URI = "https://web-api.tp.entsoe.eu/api"; + + /** + * Queries the ENTSO-E API for day-ahead prices. + * + * @param token the Security Token + * @param areaCode Area EIC code; see + * https://transparency.entsoe.eu/content/static_content/Static%20content/web%20api/Guide.html#_areas + * @param fromDate the From-Date + * @param toDate the To-Date + * @return The response string. + * @throws IOException on error + */ + protected static String query(String token, String areaCode, ZonedDateTime fromDate, ZonedDateTime toDate) + throws IOException { + var client = new OkHttpClient(); + var request = new Request.Builder() // + .url(URI) // + .header("SECURITY_TOKEN", token) // + .post(RequestBody + // ProcessType A01 -> Day ahead + // DocumentType A44 -> Price Document + .create(""" + + SampleCallToRestfulApi + A59 + 10X1001A1001A450 + A07 + 10X1001A1001A450 + A32 + %s + + DocumentType + A44 + + + In_Domain + %s + + + Out_Domain + %s + + + TimeInterval + %s/%s + + """ + .formatted(// + FORMATTER_SECONDS.format(ZonedDateTime.now(UTC)), // + areaCode, areaCode, // + FORMATTER_MINUTES.format(fromDate.withZoneSameInstant(UTC)), // + FORMATTER_MINUTES.format(toDate.withZoneSameInstant(UTC)) // + ), MediaType.parse("application/xml"))) // + .build(); + + try (var response = client.newCall(request).execute()) { + if (!response.isSuccessful()) { + throw new IOException("Unable to get response from ENTSO-E API: " + response); + } + + return response.body().string(); + } + } +} diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApi.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApi.java new file mode 100644 index 00000000000..569c851fcea --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApi.java @@ -0,0 +1,44 @@ +package io.openems.edge.timeofusetariff.entsoe; + +import java.io.IOException; + +import okhttp3.OkHttpClient; +import okhttp3.Request; + +/** + * A utility class for fetching exchange rates from a web API. + * + *

    + * Day ahead prices retrieved from ENTSO-E are usually in EUR and might have to + * be converted to the user's currency using the exchange rates provided by + * Exchange Rate API. For more information on the ExchangeRate API, visit: + * https://exchangerate.host/#/docs + */ +public class ExchangeRateApi { + + private static final String BASE_URL = "https://api.exchangerate.host/latest?base=%s"; + private static final OkHttpClient client = new OkHttpClient(); + private static final String URL = String.format(BASE_URL, "EUR"); + + /** + * Fetches the exchange rates from base currency EUR. + * + * @return the Response string. + * @throws IOException on error. + */ + protected static String getExchangeRates() throws IOException { + var request = new Request.Builder() // + .url(URL) // + .build(); + + try (var response = client.newCall(request).execute()) { + if (!response.isSuccessful()) { + throw new IOException("Failed to fetch exchange rate. HTTP status code: " + response.code()); + } + + return response.body().string(); + } + } + +} diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoe.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoe.java new file mode 100644 index 00000000000..f3bab7a1533 --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoe.java @@ -0,0 +1,27 @@ +package io.openems.edge.timeofusetariff.entsoe; + +import io.openems.common.channel.Level; +import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.timeofusetariff.api.TimeOfUseTariff; + +public interface TouEntsoe extends OpenemsComponent, TimeOfUseTariff { + + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + UNABLE_TO_UPDATE_PRICES(Doc.of(Level.WARNING) // + .text("Unable to update prices from ENTSO-E API")), // + ; + + private final Doc doc; + + private ChannelId(Doc doc) { + this.doc = doc; + } + + @Override + public Doc doc() { + return this.doc; + } + } + +} diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java new file mode 100644 index 00000000000..cea8876928d --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java @@ -0,0 +1,162 @@ +package io.openems.edge.timeofusetariff.entsoe; + +import java.io.IOException; +import java.time.Clock; +import java.time.Duration; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; + +import javax.xml.parsers.ParserConfigurationException; + +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ConfigurationPolicy; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.metatype.annotations.Designate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.SAXException; + +import com.google.common.collect.ImmutableSortedMap; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.utils.ThreadPoolUtils; +import io.openems.edge.common.channel.value.Value; +import io.openems.edge.common.component.AbstractOpenemsComponent; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.meta.Meta; +import io.openems.edge.timeofusetariff.api.TimeOfUsePrices; +import io.openems.edge.timeofusetariff.api.TimeOfUseTariff; +import io.openems.edge.timeofusetariff.api.utils.TimeOfUseTariffUtils; + +@Designate(ocd = Config.class, factory = true) +@Component(// + name = "TimeOfUseTariff.ENTSO-E", // + immediate = true, // + configurationPolicy = ConfigurationPolicy.REQUIRE // +) +public class TouEntsoeImpl extends AbstractOpenemsComponent implements TouEntsoe, OpenemsComponent, TimeOfUseTariff { + + private static final int API_EXECUTE_HOUR = 14; + + private final Logger log = LoggerFactory.getLogger(TouEntsoeImpl.class); + private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + private final AtomicReference> prices = new AtomicReference<>( + ImmutableSortedMap.of()); + + @Reference + private Meta meta; + + private Config config = null; + private ZonedDateTime updateTimeStamp = null; + + public TouEntsoeImpl() { + super(// + OpenemsComponent.ChannelId.values(), // + TouEntsoe.ChannelId.values() // + ); + } + + private final BiConsumer, Value> onCurrencyChange = (a, b) -> { + this.scheduleTask(0); + }; + + @Activate + private void activate(ComponentContext context, Config config) { + super.activate(context, config.id(), config.alias(), config.enabled()); + + if (!config.enabled()) { + return; + } + + if (config.securityToken() == null || config.securityToken().isBlank()) { + this.logError(this.log, "Please configure Security Token to access ENTSO-E"); + return; + } + this.config = config; + + // React on updates to Currency. + this.meta.getCurrencyChannel().onChange(this.onCurrencyChange); + } + + @Deactivate + protected void deactivate() { + super.deactivate(); + this.meta.getCurrencyChannel().removeOnChangeCallback(this.onCurrencyChange); + ThreadPoolUtils.shutdownAndAwaitTermination(this.executor, 0); + } + + /** + * Schedules execution the the update Task. + * + * @param seconds execute task in seconds + */ + private void scheduleTask(long seconds) { + this.executor.schedule(this.task, seconds, TimeUnit.SECONDS); + } + + private final Runnable task = () -> { + var token = this.config.securityToken(); + var areaCode = this.config.biddingZone().code; + var fromDate = ZonedDateTime.now().truncatedTo(ChronoUnit.HOURS); + var toDate = fromDate.plusDays(1); + var unableToUpdatePrices = false; + + try { + final var result = EntsoeApi.query(token, areaCode, fromDate, toDate); + final var entsoeCurrency = Utils.parseCurrency(result); + final var globalCurrency = this.meta.getCurrency(); + final var exchangeRate = globalCurrency.name() == entsoeCurrency // + ? 1 // No need to fetch exchange rate from API. + : Utils.exchangeRateParser(ExchangeRateApi.getExchangeRates(), globalCurrency); + + // Parse the response for the prices + this.prices.set(Utils.parsePrices(result, "PT60M", exchangeRate)); + + // store the time stamp + this.updateTimeStamp = ZonedDateTime.now(); + + } catch (IOException | ParserConfigurationException | SAXException | OpenemsNamedException e) { + this.logWarn(this.log, "Unable to Update Entsoe Time-Of-Use Price: " + e.getMessage()); + e.printStackTrace(); + unableToUpdatePrices = true; + } + + this.channel(TouEntsoe.ChannelId.UNABLE_TO_UPDATE_PRICES).setNextValue(unableToUpdatePrices); + + /* + * Schedule next price update at 2 o clock every day. + */ + var now = ZonedDateTime.now(); + var nextRun = now.withHour(API_EXECUTE_HOUR).truncatedTo(ChronoUnit.HOURS); + if (unableToUpdatePrices) { + // If the prices are not updated, try again in next minute. + nextRun = now.plusMinutes(1).truncatedTo(ChronoUnit.MINUTES); + this.logWarn(this.log, "Unable to Update the prices, Trying again at: " + nextRun); + } else if (now.isAfter(nextRun)) { + nextRun = nextRun.plusDays(1); + } + + var delay = Duration.between(now, nextRun).getSeconds(); + this.scheduleTask(delay); + }; + + @Override + public TimeOfUsePrices getPrices() { + // return empty TimeOfUsePrices if data is not yet available. + if (!this.config.enabled() || this.updateTimeStamp == null) { + return TimeOfUsePrices.empty(ZonedDateTime.now()); + } + + return TimeOfUseTariffUtils.getNext24HourPrices(Clock.systemDefaultZone() /* can be mocked for testing */, + this.prices.get(), this.updateTimeStamp); + } + +} diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Utils.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Utils.java new file mode 100644 index 00000000000..e1938e71021 --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Utils.java @@ -0,0 +1,174 @@ +package io.openems.edge.timeofusetariff.entsoe; + +import static io.openems.common.utils.XmlUtils.stream; + +import java.io.IOException; +import java.io.StringReader; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.TreeMap; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import com.google.common.collect.ImmutableSortedMap; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.utils.JsonUtils; +import io.openems.common.utils.XmlUtils; +import io.openems.edge.common.currency.Currency; + +public class Utils { + + private static final DateTimeFormatter FORMATTER_MINUTES = DateTimeFormatter.ofPattern("u-MM-dd'T'HH:mmX"); + + private static record QueryResult(ZonedDateTime start, List prices) { + protected static class Builder { + private ZonedDateTime start; + private List prices = new ArrayList<>(); + + public Builder start(ZonedDateTime start) { + this.start = start; + return this; + } + + public Builder prices(List prices) { + this.prices.addAll(prices); + return this; + } + + public ImmutableSortedMap toMap() { + + var result = new TreeMap(); + var timestamp = this.start.withZoneSameInstant(ZoneId.systemDefault()); + var quarterHourIncrements = this.prices.size() * 4; + + for (int i = 0; i < quarterHourIncrements; i++) { + result.put(timestamp, this.prices.get(i / 4)); + timestamp = timestamp.plusMinutes(15); + } + + return ImmutableSortedMap.copyOf(result); + } + } + + public static Builder create() { + return new Builder(); + } + } + + /** + * Parses the XML response from the Entso-E API to get the Day-Ahead prices. + * + * @param xml The XML string to be parsed. + * @param resolution PT15M or PT60M + * @param exchangeRate The exchange rate of user currency to EUR. + * @return The {@link ImmutableSortedMap} + * @throws ParserConfigurationException on error. + * @throws SAXException on error + * @throws IOException on error + */ + protected static ImmutableSortedMap parsePrices(String xml, String resolution, double exchangeRate) + throws ParserConfigurationException, SAXException, IOException { + var dbFactory = DocumentBuilderFactory.newInstance(); + var dBuilder = dbFactory.newDocumentBuilder(); + var is = new InputSource(new StringReader(xml)); + var doc = dBuilder.parse(is); + var root = doc.getDocumentElement(); + var result = QueryResult.create(); + + stream(root) // + // + .filter(n -> n.getNodeName() == "TimeSeries") // + .flatMap(XmlUtils::stream) // + // + .filter(n -> n.getNodeName() == "Period") // + // Find Period with correct resolution + .filter(p -> stream(p) // + .filter(n -> n.getNodeName() == "resolution") // + .map(XmlUtils::getContentAsString) // + .anyMatch(r -> r.equals(resolution))) // + .forEach(period -> { + + var start = ZonedDateTime.parse(// + stream(period) // + // + .filter(n -> n.getNodeName() == "timeInterval") // + .flatMap(XmlUtils::stream) // + // + .filter(n -> n.getNodeName() == "start") // + .map(XmlUtils::getContentAsString) // + .findFirst().get(), + FORMATTER_MINUTES).withZoneSameInstant(ZoneId.of("UTC")); + + if (result.start == null) { + // Avoiding overwriting of start due to multiple periods. + result.start(start); + } + + result.prices(stream(period) // + // + .filter(n -> n.getNodeName() == "Point") // + .flatMap(XmlUtils::stream) // + // + .filter(n -> n.getNodeName() == "price.amount") // + .map(XmlUtils::getContentAsString) // + .map(s -> Float.parseFloat(s) * (float) exchangeRate) // + .toList()); + }); + + return result.toMap(); + } + + /** + * Parses the XML response from the Entso-E API to extract the currency + * associated with the prices. + * + * @param xml The XML string to be parsed. + * @return The currency string. + * @throws ParserConfigurationException on error. + * @throws SAXException on error + * @throws IOException on error + */ + protected static String parseCurrency(String xml) throws ParserConfigurationException, SAXException, IOException { + var dbFactory = DocumentBuilderFactory.newInstance(); + var dBuilder = dbFactory.newDocumentBuilder(); + var is = new InputSource(new StringReader(xml)); + var doc = dBuilder.parse(is); + var root = doc.getDocumentElement(); + + var result = stream(root) // + // + .filter(n -> n.getNodeName() == "TimeSeries") // + .flatMap(XmlUtils::stream) // + // + .filter(n -> n.getNodeName() == "currency_Unit.name") // + .map(XmlUtils::getContentAsString) // + .findFirst().get(); + + return result; + } + + /** + * Parses the response string from Exchange rate API. + * + * @param response The Response string from ExcahngeRate API. + * @param currency The {@link Currency} selected by User. + * @return the exchange rate. + * @throws OpenemsNamedException on error. + */ + protected static Double exchangeRateParser(String response, Currency currency) throws OpenemsNamedException { + + var line = JsonUtils.parseToJsonObject(response); + var data = JsonUtils.getAsJsonObject(line, "rates"); + + return JsonUtils.getAsDouble(data, currency.toString()); + } + +} diff --git a/io.openems.edge.timeofusetariff.entsoe/test/.gitignore b/io.openems.edge.timeofusetariff.entsoe/test/.gitignore new file mode 100644 index 00000000000..e69de29bb2d diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/EntsoeApiTest.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/EntsoeApiTest.java new file mode 100644 index 00000000000..b8e950f4c11 --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/EntsoeApiTest.java @@ -0,0 +1,25 @@ +package io.openems.edge.timeofusetariff.entsoe; + +import java.io.IOException; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; + +import javax.xml.parsers.ParserConfigurationException; + +import org.junit.Ignore; +import org.junit.Test; +import org.xml.sax.SAXException; + +public class EntsoeApiTest { + + @Test + @Ignore + public void testQuery() throws IOException, ParserConfigurationException, SAXException { + var token = ""; // Fill personal security token and remove 'Ignore' tag while testing. + var areaCode = BiddingZone.SWEDEN_SE3.code; + var fromDate = ZonedDateTime.now().truncatedTo(ChronoUnit.HOURS).withZoneSameLocal(ZoneId.systemDefault()); + var toDate = fromDate.plusDays(1); + EntsoeApi.query(token, areaCode, fromDate, toDate); + } +} diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApiTest.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApiTest.java new file mode 100644 index 00000000000..819d30d74f1 --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApiTest.java @@ -0,0 +1,214 @@ +package io.openems.edge.timeofusetariff.entsoe; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; + +import org.junit.Ignore; +import org.junit.Test; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.edge.common.currency.Currency; + +public class ExchangeRateApiTest { + + private static final String EXCHANGE_DATA = """ + { + "success": true, + "base": "EUR", + "date": "2023-07-27", + "rates": { + "AED": 4.074263, + "AFN": 96.546557, + "ALL": 102.049538, + "AMD": 431.402948, + "ANG": 2.00055, + "AOA": 916.112472, + "ARS": 302.239867, + "AUD": 1.629808, + "AWG": 1.998636, + "AZN": 1.886792, + "BAM": 1.958547, + "BBD": 2.219307, + "BDT": 120.446245, + "BGN": 1.955529, + "BHD": 0.418333, + "BIF": 3141.458211, + "BMD": 1.109917, + "BND": 1.473742, + "BOB": 7.669034, + "BRL": 5.256284, + "BSD": 1.109668, + "BTC": 0.000038, + "BTN": 91.002709, + "BWP": 14.488226, + "BYN": 2.802141, + "BZD": 2.237335, + "CAD": 1.46224, + "CDF": 2828.550254, + "CHF": 0.954345, + "CLF": 0.033979, + "CLP": 914.830807, + "CNH": 7.91491, + "CNY": 7.914035, + "COP": 4400.287374, + "CRC": 594.125915, + "CUC": 1.110171, + "CUP": 28.563636, + "CVE": 110.417189, + "CZK": 24.025043, + "DJF": 197.373766, + "DKK": 7.448692, + "DOP": 62.137242, + "DZD": 149.842018, + "EGP": 34.274712, + "ERN": 16.639118, + "ETB": 60.709379, + "EUR": 1, + "FJD": 2.461322, + "FKP": 0.857521, + "GBP": 0.857065, + "GEL": 2.879232, + "GGP": 0.856846, + "GHS": 12.563656, + "GIP": 0.857444, + "GMD": 66.111378, + "GNF": 9545.125955, + "GTQ": 8.711891, + "GYD": 232.184942, + "HKD": 8.646912, + "HNL": 27.325849, + "HRK": 7.53181, + "HTG": 152.038911, + "HUF": 381.18337, + "IDR": 16642.2715, + "ILS": 4.089653, + "IMP": 0.856722, + "INR": 90.963493, + "IQD": 1453.098308, + "IRR": 46879.045396, + "ISK": 145.620797, + "JEP": 0.857125, + "JMD": 171.374964, + "JOD": 0.787314, + "JPY": 155.307802, + "KES": 157.73419, + "KGS": 97.449403, + "KHR": 4577.516015, + "KMF": 493.194214, + "KPW": 998.312097, + "KRW": 1415.953271, + "KWD": 0.340683, + "KYD": 0.924915, + "KZT": 493.937153, + "LAK": 21383.452835, + "LBP": 16776.637925, + "LKR": 366.792902, + "LRD": 205.652382, + "LSL": 19.598023, + "LYD": 5.294188, + "MAD": 10.787573, + "MDL": 19.474446, + "MGA": 4984.515277, + "MKD": 61.641818, + "MMK": 2330.490351, + "MNT": 3903.398698, + "MOP": 8.922849, + "MRU": 37.929989, + "MUR": 50.325954, + "MVR": 17.027553, + "MWK": 1168.023721, + "MXN": 18.676827, + "MYR": 5.024866, + "MZN": 70.714101, + "NAD": 19.579235, + "NGN": 874.632255, + "NIO": 40.574721, + "NOK": 11.166941, + "NPR": 145.603541, + "NZD": 1.775313, + "OMR": 0.427697, + "PAB": 1.110007, + "PEN": 3.990703, + "PGK": 3.980789, + "PHP": 60.551875, + "PKR": 318.444017, + "PLN": 4.41964, + "PYG": 8080.356808, + "QAR": 4.039405, + "RON": 4.923793, + "RSD": 117.135798, + "RUB": 99.859233, + "RWF": 1302.494419, + "SAR": 4.160461, + "SBD": 9.298905, + "SCR": 14.821421, + "SDG": 667.205497, + "SEK": 11.504976, + "SGD": 1.469025, + "SHP": 0.857181, + "SLL": 19594.637867, + "SOS": 631.983688, + "SRD": 42.561687, + "SSP": 144.489173, + "STD": 25317.170823, + "STN": 24.847059, + "SVC": 9.710443, + "SYP": 2786.987107, + "SZL": 19.625727, + "THB": 37.81308, + "TJS": 12.130966, + "TMT": 3.882521, + "TND": 3.428976, + "TOP": 2.599614, + "TRY": 29.846988, + "TTD": 7.539697, + "TWD": 34.634021, + "TZS": 2717.626941, + "UAH": 40.986807, + "UGX": 4049.121605, + "USD": 1.109987, + "UYU": 42.02786, + "UZS": 12905.949812, + "VES": 32.209006, + "VND": 26260.304612, + "VUV": 131.976192, + "WST": 3.024415, + "XAF": 655.638919, + "XAG": 0.044704, + "XAU": 0.001581, + "XCD": 2.997771, + "XDR": 0.825364, + "XOF": 655.639063, + "XPD": 0.001494, + "XPF": 119.274762, + "XPT": 0.001957, + "YER": 277.641934, + "ZAR": 19.492845, + "ZMW": 21.436533, + "ZWL": 357.173757 + } + } + """; + + @Test + @Ignore + public void testExchangeRateApi() throws IOException { + ExchangeRateApi.getExchangeRates(); + } + + @Test + public void testExchangeRateParser() throws OpenemsNamedException { + + var currency = Currency.EUR; + var response = Utils.exchangeRateParser(EXCHANGE_DATA, currency); + + assertTrue(response == 1.0); + + currency = Currency.SEK; + response = Utils.exchangeRateParser(EXCHANGE_DATA, currency); + + assertFalse(response == 1.0); + } +} diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/MyConfig.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/MyConfig.java new file mode 100644 index 00000000000..6627c663921 --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/MyConfig.java @@ -0,0 +1,63 @@ +package io.openems.edge.timeofusetariff.entsoe; + +import io.openems.common.test.AbstractComponentConfig; +import io.openems.edge.common.currency.CurrencyConfig; + +@SuppressWarnings("all") +public class MyConfig extends AbstractComponentConfig implements Config { + + protected static class Builder { + private String id; + private String securityToken; + private BiddingZone biddingZone; + + private Builder() { + } + + public Builder setId(String id) { + this.id = id; + return this; + } + + public Builder setSecurityToken(String securityToken) { + this.securityToken = securityToken; + return this; + } + + public Builder setBiddingZone(BiddingZone biddingZone) { + this.biddingZone = biddingZone; + return this; + } + + public MyConfig build() { + return new MyConfig(this); + } + } + + /** + * Create a Config builder. + * + * @return a {@link Builder} + */ + public static Builder create() { + return new Builder(); + } + + private final Builder builder; + + private MyConfig(Builder builder) { + super(Config.class, builder.id); + this.builder = builder; + } + + @Override + public String securityToken() { + return this.builder.securityToken; + } + + @Override + public BiddingZone biddingZone() { + return this.builder.biddingZone; + } + +} \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ParserTest.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ParserTest.java new file mode 100644 index 00000000000..b5f8b250900 --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ParserTest.java @@ -0,0 +1,564 @@ +package io.openems.edge.timeofusetariff.entsoe; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; + +import javax.xml.parsers.ParserConfigurationException; + +import org.junit.Test; +import org.xml.sax.SAXException; + +import io.openems.edge.common.currency.Currency; + +public class ParserTest { + + private static String XML = """ + + + 946edcf0cf33426aa666e1069ececbe7 + 1 + A44 + 10X1001A1001A450 + A32 + 10X1001A1001A450 + A33 + 2023-06-01T12:40:44Z + + 2023-05-31T22:00Z + 2023-06-01T22:00Z + + + 1 + A62 + 10Y1001A1001A82H + 10Y1001A1001A82H + EUR + MWH + A01 + + + 2023-05-31T22:00Z + 2023-06-01T22:00Z + + PT15M + + 1 + 109.93 + + + 2 + 85.84 + + + 3 + 65.09 + + + 4 + 55.07 + + + 5 + 90.10 + + + 6 + 78.30 + + + 7 + 71.20 + + + 8 + 60.80 + + + 9 + 79.70 + + + 10 + 70.60 + + + 11 + 75.10 + + + 12 + 66.14 + + + 13 + 74.00 + + + 14 + 70.60 + + + 15 + 70.20 + + + 16 + 71.34 + + + 17 + 52.70 + + + 18 + 62.10 + + + 19 + 77.40 + + + 20 + 93.40 + + + 21 + 40.60 + + + 22 + 56.20 + + + 23 + 87.20 + + + 24 + 144.06 + + + 25 + 50.68 + + + 26 + 96.20 + + + 27 + 117.60 + + + 28 + 121.50 + + + 29 + 125.10 + + + 30 + 111.80 + + + 31 + 91.80 + + + 32 + 77.46 + + + 33 + 179.06 + + + 34 + 111.90 + + + 35 + 69.50 + + + 36 + 30.10 + + + 37 + 151.70 + + + 38 + 96.20 + + + 39 + 69.91 + + + 40 + 7.20 + + + 41 + 114.91 + + + 42 + 75.20 + + + 43 + 49.90 + + + 44 + 1.20 + + + 45 + 89.91 + + + 46 + 64.90 + + + 47 + 29.20 + + + 48 + -11.47 + + + 49 + 69.80 + + + 50 + 34.90 + + + 51 + 6.10 + + + 52 + -20.00 + + + 53 + 44.20 + + + 54 + 24.91 + + + 55 + -12.03 + + + 56 + -19.94 + + + 57 + -25.30 + + + 58 + -13.50 + + + 59 + 18.83 + + + 60 + 39.90 + + + 61 + -49.90 + + + 62 + -10.80 + + + 63 + 21.40 + + + 64 + 61.62 + + + 65 + -61.69 + + + 66 + 7.16 + + + 67 + 58.60 + + + 68 + 109.92 + + + 69 + -35.10 + + + 70 + 39.93 + + + 71 + 109.93 + + + 72 + 139.94 + + + 73 + 29.91 + + + 74 + 54.10 + + + 75 + 105.11 + + + 76 + 149.91 + + + 77 + 75.05 + + + 78 + 93.33 + + + 79 + 130.10 + + + 80 + 139.96 + + + 81 + 139.92 + + + 82 + 110.22 + + + 83 + 95.08 + + + 84 + 95.05 + + + 85 + 139.98 + + + 86 + 114.80 + + + 87 + 90.09 + + + 88 + 80.07 + + + 89 + 134.70 + + + 90 + 100.68 + + + 91 + 80.03 + + + 92 + 70.02 + + + 93 + 119.70 + + + 94 + 82.41 + + + 95 + 75.10 + + + 96 + 65.07 + + + + + 2 + A62 + 10Y1001A1001A82H + 10Y1001A1001A82H + EUR + MWH + A01 + + + 2023-05-31T22:00Z + 2023-06-01T22:00Z + + PT60M + + 1 + 84.15 + + + 2 + 74.30 + + + 3 + 70.10 + + + 4 + 66.72 + + + 5 + 67.70 + + + 6 + 80.45 + + + 7 + 97.04 + + + 8 + 108.23 + + + 9 + 99.07 + + + 10 + 87.30 + + + 11 + 68.19 + + + 12 + 59.92 + + + 13 + 38.86 + + + 14 + 9.35 + + + 15 + 3.01 + + + 16 + 13.35 + + + 17 + 56.14 + + + 18 + 72.00 + + + 19 + 87.86 + + + 20 + 100.46 + + + 21 + 120.04 + + + 22 + 103.43 + + + 23 + 95.41 + + + 24 + 86.53 + + + + + """; + + @Test + public void testParse() throws IOException, ParserConfigurationException, SAXException { + var currencyExchangeValue = 1.0; + var result = Utils.parsePrices(XML, "PT15M", currencyExchangeValue); + + assertTrue(result.firstEntry().getValue() == 109.93f); + assertTrue(result.lastEntry().getValue() == 65.07f); + + result = Utils.parsePrices(XML, "PT60M", currencyExchangeValue); + + assertFalse(result.firstEntry().getValue() == 109.93f); + assertTrue(result.lastEntry().getValue() == 86.53f); + + var res = Utils.parseCurrency(XML); + assertEquals(res, Currency.EUR.toString()); + } +} diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java new file mode 100644 index 00000000000..719a35fc61d --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java @@ -0,0 +1,25 @@ +package io.openems.edge.timeofusetariff.entsoe; + +import org.junit.Test; + +import io.openems.edge.common.currency.Currency; +import io.openems.edge.common.test.ComponentTest; +import io.openems.edge.common.test.DummyMeta; + +public class TouEntsoeTest { + + private static final String COMPONENT_ID = "tou0"; + + @Test + public void test() throws Exception { + var entsoe = new TouEntsoeImpl(); + var dummyMeta = new DummyMeta("foo0", Currency.EUR); + new ComponentTest(entsoe) // + .addReference("meta", dummyMeta)// + .activate(MyConfig.create() // + .setId(COMPONENT_ID) // + .setSecurityToken("foo-bar") // + .setBiddingZone(BiddingZone.GERMANY) // + .build()); + } +} From 1be37b0254d2cec22d2a5c4bcb2d58b3ef601a91 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Aug 2023 15:54:41 +0200 Subject: [PATCH 22/32] Bump compare-versions from 6.0.0 to 6.1.0 in /ui (#2319) Bumps [compare-versions](https://github.com/omichelsen/compare-versions) from 6.0.0 to 6.1.0. - [Changelog](https://github.com/omichelsen/compare-versions/blob/main/CHANGELOG.md) - [Commits](https://github.com/omichelsen/compare-versions/compare/v6.0.0...v6.1.0) --- updated-dependencies: - dependency-name: compare-versions dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Stefan Feilmeier --- ui/package-lock.json | 14 +++++++------- ui/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index 60f9eef11d7..f5bb3d9ca37 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -25,7 +25,7 @@ "angular-mydatepicker": "^0.11.5", "chart.js": "^2.9.4", "classlist.js": "^1.1.20150312", - "compare-versions": "^6.0.0", + "compare-versions": "^6.1.0", "d3": "^7.8.5", "date-fns": "^2.30.0", "file-saver-es": "^2.0.5", @@ -6038,9 +6038,9 @@ "dev": true }, "node_modules/compare-versions": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.0.0.tgz", - "integrity": "sha512-s2MzYxfRsE9f/ow8hjn7ysa7pod1xhHdQMsgiJtKx6XSNf4x2N1KG4fjrkUmXcP/e9Y2ZX4zB6sHIso0Lm6evQ==" + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.0.tgz", + "integrity": "sha512-LNZQXhqUvqUTotpZ00qLSaify3b4VFD588aRr8MKFw4CMUr98ytzCW5wDH5qx/DEY5kCDXcbcRuCqL0szEf2tg==" }, "node_modules/compressible": { "version": "2.0.18", @@ -20383,9 +20383,9 @@ "dev": true }, "compare-versions": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.0.0.tgz", - "integrity": "sha512-s2MzYxfRsE9f/ow8hjn7ysa7pod1xhHdQMsgiJtKx6XSNf4x2N1KG4fjrkUmXcP/e9Y2ZX4zB6sHIso0Lm6evQ==" + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.0.tgz", + "integrity": "sha512-LNZQXhqUvqUTotpZ00qLSaify3b4VFD588aRr8MKFw4CMUr98ytzCW5wDH5qx/DEY5kCDXcbcRuCqL0szEf2tg==" }, "compressible": { "version": "2.0.18", diff --git a/ui/package.json b/ui/package.json index bfdac09bbae..9215d8fa925 100644 --- a/ui/package.json +++ b/ui/package.json @@ -20,7 +20,7 @@ "angular-mydatepicker": "^0.11.5", "chart.js": "^2.9.4", "classlist.js": "^1.1.20150312", - "compare-versions": "^6.0.0", + "compare-versions": "^6.1.0", "d3": "^7.8.5", "date-fns": "^2.30.0", "file-saver-es": "^2.0.5", From 5f207bd4e3523495ffdfefcdab46f058f555f3f2 Mon Sep 17 00:00:00 2001 From: Thomas Sicking <91258335+tsicking@users.noreply.github.com> Date: Fri, 25 Aug 2023 20:28:01 +0200 Subject: [PATCH 23/32] SunSpecCodeGenerator: refactor, add 7xx models, use JSON input files (#2324) Based on PR #2304 by [nlamarti](https://github.com/nlamarti), this new PR handles the comments made there. Comparing the old and new output file `DefaultSunSpecModel.java`, the following changed: - The descriptions "Ref 3: 8.14.3.2, Ref 4: 17" in the 12x models disappeared, because they are not in the json-files. - The models 302-304 are now ignored. They only consist of one repeating block (which OpenEMS cannot handle as of now), and as far as I can see, no component uses them currently. - The models 701-715 are added - Descriptions of some points changed Note that the length of model 701 is 155>126=maximum length of a modbus task in j2mod. As the `AbstractOpenemsSunSpecComponent` adds one task for each block, this causes an error (see issue 2311). I have fixed this already, this will be done in a seperate PR. Furthermore, I used modern syntax in the switch-statements in `SunSpecPoint.java`, as I had to update them anyway due to the new pointn type STRING32. Lastly, I had to write a new method `getAsStringOrElse` in `JsonUtils.java`, and the other changes in this file are caused by the automatic code clean up. --------- Co-authored-by: nlamarti <2514024+nlamarti@users.noreply.github.com> Co-authored-by: Stefan Feilmeier --- .../io/openems/common/utils/JsonUtils.java | 62 +- .../openems/common/utils/JsonUtilsTest.java | 4 + .../modbus/sunspec/DefaultSunSpecModel.java | 5150 ++++++++++++++++- .../modbus/sunspec/SunSpecCodeGenerator.java | 843 +-- .../bridge/modbus/sunspec/SunSpecModel.java | 4 +- .../bridge/modbus/sunspec/SunSpecPoint.java | 199 +- 6 files changed, 5222 insertions(+), 1040 deletions(-) diff --git a/io.openems.common/src/io/openems/common/utils/JsonUtils.java b/io.openems.common/src/io/openems/common/utils/JsonUtils.java index f8bca713e73..97886d08db5 100644 --- a/io.openems.common/src/io/openems/common/utils/JsonUtils.java +++ b/io.openems.common/src/io/openems/common/utils/JsonUtils.java @@ -39,23 +39,22 @@ public class JsonUtils { /** * Provide a easy way to generate a JsonArray from a list using the given * convert function to add each element. - * + * * @param list to convert * @param convert function to convert elements * @param type of an element from list - * + * * @return list as JsonArray */ public static JsonArray generateJsonArray(Collection list, Function convert) { if (list == null) { return null; - } else { - var jab = new JsonArrayBuilder(); - list.forEach(element -> { - jab.add(convert.apply(element)); - }); - return jab.build(); } + var jab = new JsonArrayBuilder(); + list.forEach(element -> { + jab.add(convert.apply(element)); + }); + return jab.build(); } /** @@ -262,10 +261,10 @@ public JsonObjectBuilder addProperty(String property, Enum value) { /** * Add a {@link ZonedDateTime} value to the {@link JsonObject}. - * + * *

    * The value gets added in the format of {@link DateTimeFormatter#ISO_INSTANT}. - * + * * @param property the key * @param value the value * @return the {@link JsonObjectBuilder} @@ -364,10 +363,10 @@ public JsonObjectBuilder addPropertyIfNotNull(String property, Enum value) { /** * Add a {@link ZonedDateTime} value to the {@link JsonObject} if it is not * null. - * + * *

    * The value gets added in the format of {@link DateTimeFormatter#ISO_INSTANT}. - * + * * @param property the key * @param value the value * @return the {@link JsonObjectBuilder} @@ -719,6 +718,19 @@ public static Optional getAsOptionalString(JsonElement jElement, String return Optional.ofNullable(toString(toPrimitive(toSubElement(jElement, memberName)))); } + /** + * Gets the member of the {@link JsonElement} as {@link String} if it exists, + * and the alternative value otherwise. + * + * @param jElement the {@link JsonElement} + * @param memberName the name of the member + * @param alternative the alternative value + * @return the {@link String} value or the alternative value + */ + public static String getAsStringOrElse(JsonElement jElement, String memberName, String alternative) { + return getAsOptionalString(jElement, memberName).orElse(alternative); + } + /** * Converts a {@link JsonArray} to a String Array. * @@ -1373,17 +1385,20 @@ public static JsonElement getAsJsonElement(Object value) { * String */ return new JsonPrimitive((String) value); - } else if (value instanceof Boolean) { + } + if (value instanceof Boolean) { /* * Boolean */ return new JsonPrimitive((Boolean) value); - } else if (value instanceof Inet4Address) { + } + if (value instanceof Inet4Address) { /* * Inet4Address */ return new JsonPrimitive(((Inet4Address) value).getHostAddress()); - } else if (value instanceof JsonElement) { + } + if (value instanceof JsonElement) { /* * JsonElement */ @@ -1497,17 +1512,20 @@ public static Object getAsType(Class type, JsonElement j) throws NotImplement * Asking for an Long */ return j.getAsLong(); - } else if (Boolean.class.isAssignableFrom(type)) { + } + if (Boolean.class.isAssignableFrom(type)) { /* * Asking for an Boolean */ return j.getAsBoolean(); - } else if (Double.class.isAssignableFrom(type)) { + } + if (Double.class.isAssignableFrom(type)) { /* * Asking for an Double */ return j.getAsDouble(); - } else if (String.class.isAssignableFrom(type)) { + } + if (String.class.isAssignableFrom(type)) { /* * Asking for a String */ @@ -1731,7 +1749,7 @@ public static boolean isEmptyJsonArray(JsonElement j) { /** * Check if the given {@link JsonElement} is a {@link Number}. - * + * * @param j the {@link JsonElement} to check * @return true if the element is a {@link Number}, otherwise false */ @@ -1742,7 +1760,7 @@ public static boolean isNumber(JsonElement j) { /** * Returns a sequential stream of the {@link JsonElement JsonElements} in the * {@link JsonArray}. - * + * * @param jsonArray The {@link JsonArray}, assumed to be unmodified during use * @return a Stream of the elements */ @@ -1764,7 +1782,7 @@ private static JsonObject toJsonObject(JsonElement jElement) { /** * Returns a {@link Collector} that accumulates the input elements into a new * {@link JsonObject}. - * + * * @param the type of the input * @param keyMapper the key mapper * @param valueMapper the value mapper @@ -1785,7 +1803,7 @@ private static JsonObject toJsonObject(JsonElement jElement) { /** * Returns a Collector that accumulates the input elements into a new JsonArray. - * + * * @return a Collector which collects all the input elements into a JsonArray */ public static Collector toJsonArray() { diff --git a/io.openems.common/test/io/openems/common/utils/JsonUtilsTest.java b/io.openems.common/test/io/openems/common/utils/JsonUtilsTest.java index 6b7925267b4..046f2377bf2 100644 --- a/io.openems.common/test/io/openems/common/utils/JsonUtilsTest.java +++ b/io.openems.common/test/io/openems/common/utils/JsonUtilsTest.java @@ -219,6 +219,10 @@ public void testGetAsString() throws OpenemsNamedException { // -> Optional Sub-Element assertEquals("value", JsonUtils.getAsOptionalString(JSON_OBJECT, "String").get()); assertEquals(Optional.empty(), JsonUtils.getAsOptionalString(JSON_OBJECT, "foo")); + + // -> As String or Else + assertEquals("value", JsonUtils.getAsStringOrElse(JSON_OBJECT, "String", "alternative")); + assertEquals("alternative", JsonUtils.getAsStringOrElse(JSON_OBJECT, "foo", "alternative")); } @Test diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/DefaultSunSpecModel.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/DefaultSunSpecModel.java index f430cd05502..f40ce5a9b4e 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/DefaultSunSpecModel.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/DefaultSunSpecModel.java @@ -93,7 +93,7 @@ public enum DefaultSunSpecModel implements SunSpecModel { S_120(// "Nameplate", // "Inverter Controls Nameplate Ratings", // - "Ref 3: 8.14.3.2, Ref 4: 17", // + "", // 26, // DefaultSunSpecModel.S120.values(), // SunSpecModelType.INVERTER // @@ -101,7 +101,7 @@ public enum DefaultSunSpecModel implements SunSpecModel { S_121(// "Basic Settings", // "Inverter Controls Basic Settings", // - "Ref 3: 8.4.2.1, Ref 4: 17", // + "", // 30, // DefaultSunSpecModel.S121.values(), // SunSpecModelType.INVERTER // @@ -109,7 +109,7 @@ public enum DefaultSunSpecModel implements SunSpecModel { S_122(// "Measurements_Status", // "Inverter Controls Extended Measurements and Status", // - "Ref 3: 8.14.3.2, Ref 4: 17", // + "", // 44, // DefaultSunSpecModel.S122.values(), // SunSpecModelType.INVERTER // @@ -117,7 +117,7 @@ public enum DefaultSunSpecModel implements SunSpecModel { S_123(// "Immediate Controls", // "Immediate Inverter Controls", // - "Ref 3: 8.7.1.2, 8.7.2.2, 8.7.3.2", // + "", // 24, // DefaultSunSpecModel.S123.values(), // SunSpecModelType.INVERTER // @@ -125,7 +125,7 @@ public enum DefaultSunSpecModel implements SunSpecModel { S_124(// "Storage", // "Basic Storage Controls", // - "Ref 3: 8.7.4.2", // + "", // 24, // DefaultSunSpecModel.S124.values(), // SunSpecModelType.INVERTER // @@ -133,7 +133,7 @@ public enum DefaultSunSpecModel implements SunSpecModel { S_125(// "Pricing", // "Pricing Signal", // - "Ref 3: 8.7.5.1; Ref 4: 6", // + "", // 8, // DefaultSunSpecModel.S125.values(), // SunSpecModelType.INVERTER // @@ -141,7 +141,7 @@ public enum DefaultSunSpecModel implements SunSpecModel { S_127(// "Freq-Watt Param", // "Parameterized Frequency-Watt", // - "Ref 3: 8.9.1.2, 8.9.4.2", // + "", // 10, // DefaultSunSpecModel.S127.values(), // SunSpecModelType.INVERTER // @@ -149,7 +149,7 @@ public enum DefaultSunSpecModel implements SunSpecModel { S_128(// "Dynamic Reactive Current", // "Dynamic Reactive Current", // - "Ref 3: 8.10.1.2; Ref 4: 12", // + "", // 14, // DefaultSunSpecModel.S128.values(), // SunSpecModelType.INVERTER // @@ -194,30 +194,6 @@ public enum DefaultSunSpecModel implements SunSpecModel { DefaultSunSpecModel.S204.values(), // SunSpecModelType.METER // ), // - S_302(// - "Irradiance Model", // - "Include to support various irradiance measurements", // - "", // - 5, // - DefaultSunSpecModel.S302.values(), // - SunSpecModelType.ENVIRONMENTAL // - ), // - S_303(// - "Back of Module Temperature Model", // - "Include to support variable number of back of module temperature measurements", // - "", // - 2, // - DefaultSunSpecModel.S303.values(), // - SunSpecModelType.ENVIRONMENTAL // - ), // - S_304(// - "Inclinometer Model", // - "Include to support orientation measurements", // - "", // - 6, // - DefaultSunSpecModel.S304.values(), // - SunSpecModelType.ENVIRONMENTAL // - ), // S_305(// "GPS", // "Include to support location measurements", // @@ -236,8 +212,8 @@ public enum DefaultSunSpecModel implements SunSpecModel { ), // S_307(// "Base Met", // - "Base Meteorolgical Model", // - "This model supersedes model 301", // + "Base Meteorological Model", // + "", // 11, // DefaultSunSpecModel.S307.values(), // SunSpecModelType.ENVIRONMENTAL // @@ -250,6 +226,126 @@ public enum DefaultSunSpecModel implements SunSpecModel { DefaultSunSpecModel.S308.values(), // SunSpecModelType.ENVIRONMENTAL // ), // + S_701(// + "DER AC Measurement", // + "DER AC measurement model.", // + "", // + 153, // + DefaultSunSpecModel.S701.values(), // + SunSpecModelType.RESERVED_1 // + ), // + S_702(// + "DER Capacity", // + "DER capacity model.", // + "", // + 50, // + DefaultSunSpecModel.S702.values(), // + SunSpecModelType.RESERVED_1 // + ), // + S_703(// + "Enter Service", // + "Enter service model.", // + "", // + 17, // + DefaultSunSpecModel.S703.values(), // + SunSpecModelType.RESERVED_1 // + ), // + S_704(// + "DER AC Controls", // + "DER AC controls model.", // + "", // + 57, // + DefaultSunSpecModel.S704.values(), // + SunSpecModelType.RESERVED_1 // + ), // + S_705(// + "DER Volt-Var", // + "DER Volt-Var model.", // + "", // + 13, // + DefaultSunSpecModel.S705.values(), // + SunSpecModelType.RESERVED_1 // + ), // + S_706(// + "DER Volt-Watt", // + "DER Volt-Watt model.", // + "", // + 13, // + DefaultSunSpecModel.S706.values(), // + SunSpecModelType.RESERVED_1 // + ), // + S_707(// + "DER Trip LV", // + "DER low voltage trip model.", // + "", // + 7, // + DefaultSunSpecModel.S707.values(), // + SunSpecModelType.RESERVED_1 // + ), // + S_708(// + "DER Trip HV", // + "DER high voltage trip model.", // + "", // + 7, // + DefaultSunSpecModel.S708.values(), // + SunSpecModelType.RESERVED_1 // + ), // + S_709(// + "DER Trip LF", // + "DER low frequency trip model.", // + "", // + 7, // + DefaultSunSpecModel.S709.values(), // + SunSpecModelType.RESERVED_1 // + ), // + S_710(// + "DER Trip HF", // + "DER high frequency trip model.", // + "", // + 7, // + DefaultSunSpecModel.S710.values(), // + SunSpecModelType.RESERVED_1 // + ), // + S_711(// + "DER Frequency Droop", // + "DER Frequency Droop model.", // + "", // + 12, // + DefaultSunSpecModel.S711.values(), // + SunSpecModelType.RESERVED_1 // + ), // + S_712(// + "DER Watt-Var", // + "DER Watt-Var model.", // + "", // + 12, // + DefaultSunSpecModel.S712.values(), // + SunSpecModelType.RESERVED_1 // + ), // + S_713(// + "DER Storage Capacity", // + "DER storage capacity.", // + "", // + 7, // + DefaultSunSpecModel.S713.values(), // + SunSpecModelType.RESERVED_1 // + ), // + S_714(// + "DER DC Measurement", // + "DER DC measurement.", // + "", // + 18, // + DefaultSunSpecModel.S714.values(), // + SunSpecModelType.RESERVED_1 // + ), // + S_715(// + "DERCtl", // + "DER Control", // + "", // + 7, // + DefaultSunSpecModel.S715.values(), // + SunSpecModelType.RESERVED_1 // + ), // S_801(// "Energy Storage Base Model (DEPRECATED)", // "This model has been deprecated.", // @@ -377,7 +473,7 @@ public static enum S1 implements SunSpecPoint { PAD(new PointImpl(// "S1_PAD", // "", // - "", // + "Force even alignment", // "", // PointType.PAD, // false, // @@ -875,7 +971,7 @@ public static enum S101 implements SunSpecPoint { "S101_APH_A", // "Amps PhaseA", // "Phase A Current", // - "Connected Phase", // + "", // PointType.UINT16, // true, // AccessMode.READ_ONLY, // @@ -917,8 +1013,8 @@ public static enum S101 implements SunSpecPoint { new OptionsEnum[0])), // P_P_VPH_A_B(new PointImpl(// "S101_P_P_VPH_A_B", // - "", // - "", // + "Phase Voltage AB", // + "Phase Voltage AB", // "", // PointType.UINT16, // false, // @@ -928,8 +1024,8 @@ public static enum S101 implements SunSpecPoint { new OptionsEnum[0])), // P_P_VPH_B_C(new PointImpl(// "S101_P_P_VPH_B_C", // - "", // - "", // + "Phase Voltage BC", // + "Phase Voltage BC", // "", // PointType.UINT16, // false, // @@ -939,8 +1035,8 @@ public static enum S101 implements SunSpecPoint { new OptionsEnum[0])), // P_P_VPH_C_A(new PointImpl(// "S101_P_P_VPH_C_A", // - "", // - "", // + "Phase Voltage CA", // + "Phase Voltage CA", // "", // PointType.UINT16, // false, // @@ -1429,7 +1525,7 @@ public static enum S102 implements SunSpecPoint { "S102_A", // "Amps", // "AC Current", // - "Sum of active phases", // + "", // PointType.UINT16, // true, // AccessMode.READ_ONLY, // @@ -1440,7 +1536,7 @@ public static enum S102 implements SunSpecPoint { "S102_APH_A", // "Amps PhaseA", // "Phase A Current", // - "Connected Phase", // + "", // PointType.UINT16, // true, // AccessMode.READ_ONLY, // @@ -1451,7 +1547,7 @@ public static enum S102 implements SunSpecPoint { "S102_APH_B", // "Amps PhaseB", // "Phase B Current", // - "Connected Phase", // + "", // PointType.UINT16, // true, // AccessMode.READ_ONLY, // @@ -1482,8 +1578,8 @@ public static enum S102 implements SunSpecPoint { new OptionsEnum[0])), // P_P_VPH_A_B(new PointImpl(// "S102_P_P_VPH_A_B", // - "", // - "", // + "Phase Voltage AB", // + "Phase Voltage AB", // "", // PointType.UINT16, // false, // @@ -1493,8 +1589,8 @@ public static enum S102 implements SunSpecPoint { new OptionsEnum[0])), // P_P_VPH_B_C(new PointImpl(// "S102_P_P_VPH_B_C", // - "", // - "", // + "Phase Voltage BC", // + "Phase Voltage BC", // "", // PointType.UINT16, // false, // @@ -1504,8 +1600,8 @@ public static enum S102 implements SunSpecPoint { new OptionsEnum[0])), // P_P_VPH_C_A(new PointImpl(// "S102_P_P_VPH_C_A", // - "", // - "", // + "Phase Voltage CA", // + "Phase Voltage CA", // "", // PointType.UINT16, // false, // @@ -1994,7 +2090,7 @@ public static enum S103 implements SunSpecPoint { "S103_A", // "Amps", // "AC Current", // - "Sum of active phases", // + "", // PointType.UINT16, // true, // AccessMode.READ_ONLY, // @@ -2005,7 +2101,7 @@ public static enum S103 implements SunSpecPoint { "S103_APH_A", // "Amps PhaseA", // "Phase A Current", // - "Connected Phase", // + "", // PointType.UINT16, // true, // AccessMode.READ_ONLY, // @@ -2016,7 +2112,7 @@ public static enum S103 implements SunSpecPoint { "S103_APH_B", // "Amps PhaseB", // "Phase B Current", // - "Connected Phase", // + "", // PointType.UINT16, // true, // AccessMode.READ_ONLY, // @@ -2027,7 +2123,7 @@ public static enum S103 implements SunSpecPoint { "S103_APH_C", // "Amps PhaseC", // "Phase C Current", // - "Connected Phase", // + "", // PointType.UINT16, // true, // AccessMode.READ_ONLY, // @@ -2047,8 +2143,8 @@ public static enum S103 implements SunSpecPoint { new OptionsEnum[0])), // P_P_VPH_A_B(new PointImpl(// "S103_P_P_VPH_A_B", // - "", // - "", // + "Phase Voltage AB", // + "Phase Voltage AB", // "", // PointType.UINT16, // false, // @@ -2058,8 +2154,8 @@ public static enum S103 implements SunSpecPoint { new OptionsEnum[0])), // P_P_VPH_B_C(new PointImpl(// "S103_P_P_VPH_B_C", // - "", // - "", // + "Phase Voltage BC", // + "Phase Voltage BC", // "", // PointType.UINT16, // false, // @@ -2069,8 +2165,8 @@ public static enum S103 implements SunSpecPoint { new OptionsEnum[0])), // P_P_VPH_C_A(new PointImpl(// "S103_P_P_VPH_C_A", // - "", // - "", // + "Phase Voltage CA", // + "Phase Voltage CA", // "", // PointType.UINT16, // false, // @@ -2570,7 +2666,7 @@ public static enum S111 implements SunSpecPoint { "S111_APH_A", // "Amps PhaseA", // "Phase A Current", // - "Connected Phase", // + "", // PointType.FLOAT32, // true, // AccessMode.READ_ONLY, // @@ -2601,8 +2697,8 @@ public static enum S111 implements SunSpecPoint { new OptionsEnum[0])), // P_P_VPH_A_B(new PointImpl(// "S111_P_P_VPH_A_B", // - "", // - "", // + "Phase Voltage AB", // + "Phase Voltage AB", // "", // PointType.FLOAT32, // false, // @@ -2612,8 +2708,8 @@ public static enum S111 implements SunSpecPoint { new OptionsEnum[0])), // P_P_VPH_B_C(new PointImpl(// "S111_P_P_VPH_B_C", // - "", // - "", // + "Phase Voltage BC", // + "Phase Voltage BC", // "", // PointType.FLOAT32, // false, // @@ -2623,8 +2719,8 @@ public static enum S111 implements SunSpecPoint { new OptionsEnum[0])), // P_P_VPH_C_A(new PointImpl(// "S111_P_P_VPH_C_A", // - "", // - "", // + "Phase Voltage CA", // + "Phase Voltage CA", // "", // PointType.FLOAT32, // false, // @@ -3003,7 +3099,7 @@ public static enum S112 implements SunSpecPoint { "S112_APH_A", // "Amps PhaseA", // "Phase A Current", // - "Connected Phase", // + "", // PointType.FLOAT32, // true, // AccessMode.READ_ONLY, // @@ -3014,7 +3110,7 @@ public static enum S112 implements SunSpecPoint { "S112_APH_B", // "Amps PhaseB", // "Phase B Current", // - "Connected Phase", // + "", // PointType.FLOAT32, // true, // AccessMode.READ_ONLY, // @@ -3034,8 +3130,8 @@ public static enum S112 implements SunSpecPoint { new OptionsEnum[0])), // P_P_VPH_A_B(new PointImpl(// "S112_P_P_VPH_A_B", // - "", // - "", // + "Phase Voltage AB", // + "Phase Voltage AB", // "", // PointType.FLOAT32, // false, // @@ -3045,8 +3141,8 @@ public static enum S112 implements SunSpecPoint { new OptionsEnum[0])), // P_P_VPH_B_C(new PointImpl(// "S112_P_P_VPH_B_C", // - "", // - "", // + "Phase Voltage BC", // + "Phase Voltage BC", // "", // PointType.FLOAT32, // false, // @@ -3056,8 +3152,8 @@ public static enum S112 implements SunSpecPoint { new OptionsEnum[0])), // P_P_VPH_C_A(new PointImpl(// "S112_P_P_VPH_C_A", // - "", // - "", // + "Phase Voltage CA", // + "Phase Voltage CA", // "", // PointType.FLOAT32, // false, // @@ -3425,7 +3521,7 @@ public static enum S113 implements SunSpecPoint { "S113_A", // "Amps", // "AC Current", // - "Sum of active phases", // + "", // PointType.FLOAT32, // true, // AccessMode.READ_ONLY, // @@ -3436,7 +3532,7 @@ public static enum S113 implements SunSpecPoint { "S113_APH_A", // "Amps PhaseA", // "Phase A Current", // - "Connected Phase", // + "", // PointType.FLOAT32, // true, // AccessMode.READ_ONLY, // @@ -3447,7 +3543,7 @@ public static enum S113 implements SunSpecPoint { "S113_APH_B", // "Amps PhaseB", // "Phase B Current", // - "Connected Phase", // + "", // PointType.FLOAT32, // true, // AccessMode.READ_ONLY, // @@ -3458,7 +3554,7 @@ public static enum S113 implements SunSpecPoint { "S113_APH_C", // "Amps PhaseC", // "Phase C Current", // - "Connected Phase", // + "", // PointType.FLOAT32, // true, // AccessMode.READ_ONLY, // @@ -3467,8 +3563,8 @@ public static enum S113 implements SunSpecPoint { new OptionsEnum[0])), // P_P_VPH_A_B(new PointImpl(// "S113_P_P_VPH_A_B", // - "", // - "", // + "Phase Voltage AB", // + "Phase Voltage AB", // "", // PointType.FLOAT32, // false, // @@ -3478,8 +3574,8 @@ public static enum S113 implements SunSpecPoint { new OptionsEnum[0])), // P_P_VPH_B_C(new PointImpl(// "S113_P_P_VPH_B_C", // - "", // - "", // + "Phase Voltage BC", // + "Phase Voltage BC", // "", // PointType.FLOAT32, // false, // @@ -3489,8 +3585,8 @@ public static enum S113 implements SunSpecPoint { new OptionsEnum[0])), // P_P_VPH_C_A(new PointImpl(// "S113_P_P_VPH_C_A", // - "", // - "", // + "Phase Voltage CA", // + "Phase Voltage CA", // "", // PointType.FLOAT32, // false, // @@ -3968,7 +4064,7 @@ public static enum S120 implements SunSpecPoint { "S120_A_RTG", // "ARtg", // "Maximum RMS AC current level capability of the inverter.", // - "Sum of all connected phases. Current rating under nominal voltage under nominal power factor.", // + "", // PointType.UINT16, // true, // AccessMode.READ_ONLY, // @@ -3990,7 +4086,7 @@ public static enum S120 implements SunSpecPoint { "S120_P_F_RTG_Q1", // "PFRtgQ1", // "Minimum power factor capability of the inverter in quadrant 1.", // - "EEI sign convention.", // + "", // PointType.INT16, // true, // AccessMode.READ_ONLY, // @@ -4001,7 +4097,7 @@ public static enum S120 implements SunSpecPoint { "S120_P_F_RTG_Q2", // "PFRtgQ2", // "Minimum power factor capability of the inverter in quadrant 2.", // - "EEI sign convention.", // + "", // PointType.INT16, // true, // AccessMode.READ_ONLY, // @@ -4012,7 +4108,7 @@ public static enum S120 implements SunSpecPoint { "S120_P_F_RTG_Q3", // "PFRtgQ3", // "Minimum power factor capability of the inverter in quadrant 3.", // - "EEI sign convention.", // + "", // PointType.INT16, // true, // AccessMode.READ_ONLY, // @@ -4023,7 +4119,7 @@ public static enum S120 implements SunSpecPoint { "S120_P_F_RTG_Q4", // "PFRtgQ4", // "Minimum power factor capability of the inverter in quadrant 4.", // - "EEI sign convention.", // + "", // PointType.INT16, // true, // AccessMode.READ_ONLY, // @@ -4131,8 +4227,8 @@ public static enum S120 implements SunSpecPoint { new OptionsEnum[0])), // PAD(new PointImpl(// "S120_PAD", // - "", // - "", // + "Pad", // + "Pad register.", // "", // PointType.PAD, // false, // @@ -4308,7 +4404,7 @@ public static enum S121 implements SunSpecPoint { "S121_P_F_MIN_Q1", // "PFMinQ1", // "Setpoint for minimum power factor value in quadrant 1. Default to PFRtgQ1.", // - "EEI sign convention.", // + "", // PointType.INT16, // false, // AccessMode.READ_WRITE, // @@ -4319,7 +4415,7 @@ public static enum S121 implements SunSpecPoint { "S121_P_F_MIN_Q2", // "PFMinQ2", // "Setpoint for minimum power factor value in quadrant 2. Default to PFRtgQ2.", // - "EEI sign convention.", // + "", // PointType.INT16, // false, // AccessMode.READ_WRITE, // @@ -4330,7 +4426,7 @@ public static enum S121 implements SunSpecPoint { "S121_P_F_MIN_Q3", // "PFMinQ3", // "Setpoint for minimum power factor value in quadrant 3. Default to PFRtgQ3.", // - "EEI sign convention.", // + "", // PointType.INT16, // false, // AccessMode.READ_WRITE, // @@ -4341,7 +4437,7 @@ public static enum S121 implements SunSpecPoint { "S121_P_F_MIN_Q4", // "PFMinQ4", // "Setpoint for minimum power factor value in quadrant 4. Default to PFRtgQ4.", // - "EEI sign convention.", // + "", // PointType.INT16, // false, // AccessMode.READ_WRITE, // @@ -4762,7 +4858,7 @@ public static enum S122 implements SunSpecPoint { "S122_ST_SET_LIM_MSK", // "StSetLimMsk", // "Bit Mask indicating setpoint limit(s) reached.", // - "Bits shall be automatically cleared on read.", // + "", // PointType.BITFIELD32, // false, // AccessMode.READ_ONLY, // @@ -5847,7 +5943,7 @@ public static enum S125 implements SunSpecPoint { MOD_ENA(new PointImpl(// "S125_MOD_ENA", // "ModEna", // - "Is price-based charge/dischage mode active?", // + "Is price-based charge/discharge mode active?", // "", // PointType.BITFIELD16, // true, // @@ -6042,7 +6138,7 @@ public static enum S127 implements SunSpecPoint { HYS_ENA(new PointImpl(// "S127_HYS_ENA", // "HysEna", // - "Enable hysterisis", // + "Enable hysteresis", // "", // PointType.BITFIELD16, // true, // @@ -6572,7 +6668,7 @@ public static enum S201 implements SunSpecPoint { "S201_PH_V", // "Voltage LN", // "Line to Neutral AC Voltage (average of active phases)", // - "Conditional AN connection", // + "", // PointType.INT16, // false, // AccessMode.READ_ONLY, // @@ -6583,7 +6679,7 @@ public static enum S201 implements SunSpecPoint { "S201_PH_VPH_A", // "Phase Voltage AN", // "Phase Voltage AN", // - "Conditional AN connection", // + "", // PointType.INT16, // false, // AccessMode.READ_ONLY, // @@ -6625,8 +6721,8 @@ public static enum S201 implements SunSpecPoint { new OptionsEnum[0])), // P_P_VPH_A_B(new PointImpl(// "S201_P_P_VPH_A_B", // - "", // - "", // + "Phase Voltage AB", // + "Phase Voltage AB", // "", // PointType.INT16, // false, // @@ -6636,8 +6732,8 @@ public static enum S201 implements SunSpecPoint { new OptionsEnum[0])), // P_P_VPH_B_C(new PointImpl(// "S201_P_P_VPH_B_C", // - "", // - "", // + "Phase Voltage BC", // + "Phase Voltage BC", // "", // PointType.INT16, // false, // @@ -6647,8 +6743,8 @@ public static enum S201 implements SunSpecPoint { new OptionsEnum[0])), // P_P_VPH_C_A(new PointImpl(// "S201_P_P_VPH_C_A", // - "", // - "", // + "Phase Voltage CA", // + "Phase Voltage CA", // "", // PointType.INT16, // false, // @@ -7142,7 +7238,7 @@ public static enum S201 implements SunSpecPoint { new OptionsEnum[0])), // TOT_V_ARH_IMP_Q1_PH_C(new PointImpl(// "S201_TOT_V_ARH_IMP_Q1_PH_C", // - "Total VAr-hourse Imported Q1 phase C", // + "Total VAr-hours Imported Q1 phase C", // "", // "", // PointType.ACC32, // @@ -7263,7 +7359,7 @@ public static enum S201 implements SunSpecPoint { new OptionsEnum[0])), // TOT_V_ARH_EXP_Q4_PH_B(new PointImpl(// "S201_TOT_V_ARH_EXP_Q4_PH_B", // - "", // + "Total VAr-hours Exported Q4 Imported phase B", // "", // "", // PointType.ACC32, // @@ -7479,8 +7575,8 @@ public static enum S202 implements SunSpecPoint { new OptionsEnum[0])), // PH_VPH_A_B(new PointImpl(// "S202_PH_VPH_A_B", // - "", // - "", // + "Phase Voltage AB", // + "Phase Voltage AB", // "", // PointType.INT16, // true, // @@ -7490,8 +7586,8 @@ public static enum S202 implements SunSpecPoint { new OptionsEnum[0])), // PH_VPH_B_C(new PointImpl(// "S202_PH_VPH_B_C", // - "", // - "", // + "Phase Voltage BC", // + "Phase Voltage BC", // "", // PointType.INT16, // false, // @@ -7501,8 +7597,8 @@ public static enum S202 implements SunSpecPoint { new OptionsEnum[0])), // PH_VPH_C_A(new PointImpl(// "S202_PH_VPH_C_A", // - "", // - "", // + "Phase Voltage CA", // + "Phase Voltage CA", // "", // PointType.INT16, // false, // @@ -7996,7 +8092,7 @@ public static enum S202 implements SunSpecPoint { new OptionsEnum[0])), // TOT_V_ARH_IMP_Q1_PH_C(new PointImpl(// "S202_TOT_V_ARH_IMP_Q1_PH_C", // - "Total VAr-hourse Imported Q1 phase C", // + "Total VAr-hours Imported Q1 phase C", // "", // "", // PointType.ACC32, // @@ -8341,8 +8437,8 @@ public static enum S203 implements SunSpecPoint { new OptionsEnum[0])), // PH_VPH_A_B(new PointImpl(// "S203_PH_VPH_A_B", // - "", // - "", // + "Phase Voltage AB", // + "Phase Voltage AB", // "", // PointType.INT16, // true, // @@ -8352,8 +8448,8 @@ public static enum S203 implements SunSpecPoint { new OptionsEnum[0])), // PH_VPH_B_C(new PointImpl(// "S203_PH_VPH_B_C", // - "", // - "", // + "Phase Voltage BC", // + "Phase Voltage BC", // "", // PointType.INT16, // true, // @@ -8363,8 +8459,8 @@ public static enum S203 implements SunSpecPoint { new OptionsEnum[0])), // PH_VPH_C_A(new PointImpl(// "S203_PH_VPH_C_A", // - "", // - "", // + "Phase Voltage CA", // + "Phase Voltage CA", // "", // PointType.INT16, // true, // @@ -8858,7 +8954,7 @@ public static enum S203 implements SunSpecPoint { new OptionsEnum[0])), // TOT_V_ARH_IMP_Q1_PH_C(new PointImpl(// "S203_TOT_V_ARH_IMP_Q1_PH_C", // - "Total VAr-hourse Imported Q1 phase C", // + "Total VAr-hours Imported Q1 phase C", // "", // "", // PointType.ACC32, // @@ -8979,7 +9075,7 @@ public static enum S203 implements SunSpecPoint { new OptionsEnum[0])), // TOT_V_ARH_EXP_Q4_PH_B(new PointImpl(// "S203_TOT_V_ARH_EXP_Q4_PH_B", // - "", // + "Total VAr-hours Exported Q4 Imported phase B", // "", // "", // PointType.ACC32, // @@ -9203,8 +9299,8 @@ public static enum S204 implements SunSpecPoint { new OptionsEnum[0])), // PH_VPH_A_B(new PointImpl(// "S204_PH_VPH_A_B", // - "", // - "", // + "Phase Voltage AB", // + "Phase Voltage AB", // "", // PointType.INT16, // true, // @@ -9214,8 +9310,8 @@ public static enum S204 implements SunSpecPoint { new OptionsEnum[0])), // PH_VPH_B_C(new PointImpl(// "S204_PH_VPH_B_C", // - "", // - "", // + "Phase Voltage BC", // + "Phase Voltage BC", // "", // PointType.INT16, // true, // @@ -9225,8 +9321,8 @@ public static enum S204 implements SunSpecPoint { new OptionsEnum[0])), // PH_VPH_C_A(new PointImpl(// "S204_PH_VPH_C_A", // - "", // - "", // + "Phase Voltage CA", // + "Phase Voltage CA", // "", // PointType.INT16, // true, // @@ -9720,7 +9816,7 @@ public static enum S204 implements SunSpecPoint { new OptionsEnum[0])), // TOT_V_ARH_IMP_Q1_PH_C(new PointImpl(// "S204_TOT_V_ARH_IMP_Q1_PH_C", // - "Total VAr-hourse Imported Q1 phase C", // + "Total VAr-hours Imported Q1 phase C", // "", // "", // PointType.ACC32, // @@ -9841,7 +9937,7 @@ public static enum S204 implements SunSpecPoint { new OptionsEnum[0])), // TOT_V_ARH_EXP_Q4_PH_B(new PointImpl(// "S204_TOT_V_ARH_EXP_Q4_PH_B", // - "", // + "Total VAr-hours Exported Q4 Imported phase B", // "", // "", // PointType.ACC32, // @@ -9952,147 +10048,6 @@ public OptionsEnum getUndefined() { } } - public static enum S302 implements SunSpecPoint { - GHI(new PointImpl(// - "S302_GHI", // - "GHI", // - "Global Horizontal Irradiance", // - "", // - PointType.UINT16, // - false, // - AccessMode.READ_ONLY, // - Unit.NONE, // - null, // - new OptionsEnum[0])), // - POAI(new PointImpl(// - "S302_POAI", // - "POAI", // - "Plane-of-Array Irradiance", // - "", // - PointType.UINT16, // - false, // - AccessMode.READ_ONLY, // - Unit.NONE, // - null, // - new OptionsEnum[0])), // - DFI(new PointImpl(// - "S302_DFI", // - "DFI", // - "Diffuse Irradiance", // - "", // - PointType.UINT16, // - false, // - AccessMode.READ_ONLY, // - Unit.NONE, // - null, // - new OptionsEnum[0])), // - DNI(new PointImpl(// - "S302_DNI", // - "DNI", // - "Direct Normal Irradiance", // - "", // - PointType.UINT16, // - false, // - AccessMode.READ_ONLY, // - Unit.NONE, // - null, // - new OptionsEnum[0])), // - OTI(new PointImpl(// - "S302_OTI", // - "OTI", // - "Other Irradiance", // - "", // - PointType.UINT16, // - false, // - AccessMode.READ_ONLY, // - Unit.NONE, // - null, // - new OptionsEnum[0])); // - - protected final PointImpl impl; - - private S302(PointImpl impl) { - this.impl = impl; - } - - @Override - public PointImpl get() { - return this.impl; - } - } - - public static enum S303 implements SunSpecPoint { - TMP_B_O_M(new PointImpl(// - "S303_TMP_B_O_M", // - "Temp", // - "Back of module temperature measurement", // - "", // - PointType.INT16, // - true, // - AccessMode.READ_ONLY, // - Unit.DEGREE_CELSIUS, // - "-1", // - new OptionsEnum[0])); // - - protected final PointImpl impl; - - private S303(PointImpl impl) { - this.impl = impl; - } - - @Override - public PointImpl get() { - return this.impl; - } - } - - public static enum S304 implements SunSpecPoint { - INCLX(new PointImpl(// - "S304_INCLX", // - "X", // - "X-Axis inclination", // - "", // - PointType.INT32, // - true, // - AccessMode.READ_ONLY, // - Unit.NONE, // - "-2", // - new OptionsEnum[0])), // - INCLY(new PointImpl(// - "S304_INCLY", // - "Y", // - "Y-Axis inclination", // - "", // - PointType.INT32, // - false, // - AccessMode.READ_ONLY, // - Unit.NONE, // - "-2", // - new OptionsEnum[0])), // - INCLZ(new PointImpl(// - "S304_INCLZ", // - "Z", // - "Z-Axis inclination", // - "", // - PointType.INT32, // - false, // - AccessMode.READ_ONLY, // - Unit.NONE, // - "-2", // - new OptionsEnum[0])); // - - protected final PointImpl impl; - - private S304(PointImpl impl) { - this.impl = impl; - } - - @Override - public PointImpl get() { - return this.impl; - } - } - public static enum S305 implements SunSpecPoint { TM(new PointImpl(// "S305_TM", // @@ -10177,7 +10132,7 @@ public static enum S306 implements SunSpecPoint { GHI(new PointImpl(// "S306_GHI", // "GHI", // - "Global Horizontal Irrandiance", // + "Global Horizontal Irradiance", // "", // PointType.UINT16, // false, // @@ -10424,8 +10379,4575 @@ public PointImpl get() { } } - public static enum S801 implements SunSpecPoint { - DEPRECATED(new PointImpl(// + public static enum S701 implements SunSpecPoint { + A_C_TYPE(new PointImpl(// + "S701_A_C_TYPE", // + "AC Wiring Type", // + "AC wiring type.", // + "", // + PointType.ENUM16, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + S701_ACType.values())), // + ST(new PointImpl(// + "S701_ST", // + "Operating State", // + "Operating state of the DER.", // + "", // + PointType.ENUM16, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + S701_St.values())), // + INV_ST(new PointImpl(// + "S701_INV_ST", // + "Inverter State", // + "Enumerated value. Inverter state.", // + "", // + PointType.ENUM16, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + S701_InvSt.values())), // + CONN_ST(new PointImpl(// + "S701_CONN_ST", // + "Grid Connection State", // + "Grid connection state of the DER.", // + "", // + PointType.ENUM16, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + S701_ConnSt.values())), // + ALRM(new PointImpl(// + "S701_ALRM", // + "Alarm Bitfield", // + "Active alarms for the DER.", // + "", // + PointType.BITFIELD32, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + S701_Alrm.values())), // + D_E_R_MODE(new PointImpl(// + "S701_D_E_R_MODE", // + "DER Operational Characteristics", // + "Current operational characteristics of the DER.", // + "", // + PointType.BITFIELD32, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + S701_DERMode.values())), // + W(new PointImpl(// + "S701_W", // + "Active Power", // + "Total active power. Active power is positive for DER generation and negative for absorption.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.WATT, // + "W_SF", // + new OptionsEnum[0])), // + VA(new PointImpl(// + "S701_VA", // + "Apparent Power", // + "Total apparent power.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT_AMPERE, // + "VA_SF", // + new OptionsEnum[0])), // + VAR(new PointImpl(// + "S701_VAR", // + "Reactive Power", // + "Total reactive power.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT_AMPERE_REACTIVE, // + "Var_SF", // + new OptionsEnum[0])), // + PF(new PointImpl(// + "S701_PF", // + "Power Factor", // + "Power factor. The sign of power factor should be the sign of active power.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + "PF_SF", // + new OptionsEnum[0])), // + A(new PointImpl(// + "S701_A", // + "Total AC Current", // + "Total AC current.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.AMPERE, // + "A_SF", // + new OptionsEnum[0])), // + LLV(new PointImpl(// + "S701_LLV", // + "Voltage LL", // + "Line to line AC voltage as an average of active phases.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT, // + "V_SF", // + new OptionsEnum[0])), // + LNV(new PointImpl(// + "S701_LNV", // + "Voltage LN", // + "Line to neutral AC voltage as an average of active phases.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT, // + "V_SF", // + new OptionsEnum[0])), // + HZ(new PointImpl(// + "S701_HZ", // + "Frequency", // + "AC frequency.", // + "", // + PointType.UINT32, // + false, // + AccessMode.READ_ONLY, // + Unit.HERTZ, // + "Hz_SF", // + new OptionsEnum[0])), // + TOT_WH_INJ(new PointImpl(// + "S701_TOT_WH_INJ", // + "Total Energy Injected", // + "Total active energy injected (Quadrants 1 & 4).", // + "", // + PointType.UINT64, // + false, // + AccessMode.READ_ONLY, // + Unit.CUMULATED_WATT_HOURS, // + "TotWh_SF", // + new OptionsEnum[0])), // + TOT_WH_ABS(new PointImpl(// + "S701_TOT_WH_ABS", // + "Total Energy Absorbed", // + "Total active energy absorbed (Quadrants 2 & 3).", // + "", // + PointType.UINT64, // + false, // + AccessMode.READ_ONLY, // + Unit.CUMULATED_WATT_HOURS, // + "TotWh_SF", // + new OptionsEnum[0])), // + TOT_VARH_INJ(new PointImpl(// + "S701_TOT_VARH_INJ", // + "Total Reactive Energy Inj", // + "Total reactive energy injected (Quadrants 1 & 2).", // + "", // + PointType.UINT64, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT_AMPERE_REACTIVE_HOURS, // + "TotVarh_SF", // + new OptionsEnum[0])), // + TOT_VARH_ABS(new PointImpl(// + "S701_TOT_VARH_ABS", // + "Total Reactive Energy Abs", // + "Total reactive energy absorbed (Quadrants 3 & 4).", // + "", // + PointType.UINT64, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT_AMPERE_REACTIVE_HOURS, // + "TotVarh_SF", // + new OptionsEnum[0])), // + TMP_AMB(new PointImpl(// + "S701_TMP_AMB", // + "Ambient Temperature", // + "Ambient temperature.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.DEGREE_CELSIUS, // + "Tmp_SF", // + new OptionsEnum[0])), // + TMP_CAB(new PointImpl(// + "S701_TMP_CAB", // + "Cabinet Temperature", // + "Cabinet temperature.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.DEGREE_CELSIUS, // + "Tmp_SF", // + new OptionsEnum[0])), // + TMP_SNK(new PointImpl(// + "S701_TMP_SNK", // + "Heat Sink Temperature", // + "Heat sink temperature.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.DEGREE_CELSIUS, // + "Tmp_SF", // + new OptionsEnum[0])), // + TMP_TRNS(new PointImpl(// + "S701_TMP_TRNS", // + "Transformer Temperature", // + "Transformer temperature.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.DEGREE_CELSIUS, // + "Tmp_SF", // + new OptionsEnum[0])), // + TMP_SW(new PointImpl(// + "S701_TMP_SW", // + "IGBT/MOSFET Temperature", // + "IGBT/MOSFET temperature.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.DEGREE_CELSIUS, // + "Tmp_SF", // + new OptionsEnum[0])), // + TMP_OT(new PointImpl(// + "S701_TMP_OT", // + "Other Temperature", // + "Other temperature.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.DEGREE_CELSIUS, // + "Tmp_SF", // + new OptionsEnum[0])), // + WL1(new PointImpl(// + "S701_WL1", // + "Watts L1", // + "Active power L1.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.WATT, // + "W_SF", // + new OptionsEnum[0])), // + VAL1(new PointImpl(// + "S701_VAL1", // + "VA L1", // + "Apparent power L1.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT_AMPERE, // + "VA_SF", // + new OptionsEnum[0])), // + VAR_L1(new PointImpl(// + "S701_VAR_L1", // + "Var L1", // + "Reactive power L1.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT_AMPERE_REACTIVE, // + "Var_SF", // + new OptionsEnum[0])), // + PFL1(new PointImpl(// + "S701_PFL1", // + "PF L1", // + "Power factor phase L1.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + "PF_SF", // + new OptionsEnum[0])), // + AL1(new PointImpl(// + "S701_AL1", // + "Amps L1", // + "Current phase L1.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.AMPERE, // + "A_SF", // + new OptionsEnum[0])), // + VL1L2(new PointImpl(// + "S701_VL1L2", // + "Phase Voltage L1-L2", // + "Phase voltage L1-L2.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT, // + "V_SF", // + new OptionsEnum[0])), // + VL1(new PointImpl(// + "S701_VL1", // + "Phase Voltage L1-N", // + "Phase voltage L1-N.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT, // + "V_SF", // + new OptionsEnum[0])), // + TOT_WH_INJ_L1(new PointImpl(// + "S701_TOT_WH_INJ_L1", // + "Total Watt-Hours Inj L1", // + "Total active energy injected L1.", // + "", // + PointType.UINT64, // + false, // + AccessMode.READ_ONLY, // + Unit.CUMULATED_WATT_HOURS, // + "TotWh_SF", // + new OptionsEnum[0])), // + TOT_WH_ABS_L1(new PointImpl(// + "S701_TOT_WH_ABS_L1", // + "Total Watt-Hours Abs L1", // + "Total active energy absorbed L1.", // + "", // + PointType.UINT64, // + false, // + AccessMode.READ_ONLY, // + Unit.CUMULATED_WATT_HOURS, // + "TotWh_SF", // + new OptionsEnum[0])), // + TOT_VARH_INJ_L1(new PointImpl(// + "S701_TOT_VARH_INJ_L1", // + "Total Var-Hours Inj L1", // + "Total reactive energy injected L1.", // + "", // + PointType.UINT64, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT_AMPERE_REACTIVE_HOURS, // + "TotVarh_SF", // + new OptionsEnum[0])), // + TOT_VARH_ABS_L1(new PointImpl(// + "S701_TOT_VARH_ABS_L1", // + "Total Var-Hours Abs L1", // + "Total reactive energy absorbed L1.", // + "", // + PointType.UINT64, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT_AMPERE_REACTIVE_HOURS, // + "TotVarh_SF", // + new OptionsEnum[0])), // + WL2(new PointImpl(// + "S701_WL2", // + "Watts L2", // + "Active power L2.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.WATT, // + "W_SF", // + new OptionsEnum[0])), // + VAL2(new PointImpl(// + "S701_VAL2", // + "VA L2", // + "Apparent power L2.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT_AMPERE, // + "VA_SF", // + new OptionsEnum[0])), // + VAR_L2(new PointImpl(// + "S701_VAR_L2", // + "Var L2", // + "Reactive power L2.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT_AMPERE_REACTIVE, // + "Var_SF", // + new OptionsEnum[0])), // + PFL2(new PointImpl(// + "S701_PFL2", // + "PF L2", // + "Power factor L2.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + "PF_SF", // + new OptionsEnum[0])), // + AL2(new PointImpl(// + "S701_AL2", // + "Amps L2", // + "Current L2.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.AMPERE, // + "A_SF", // + new OptionsEnum[0])), // + VL2L3(new PointImpl(// + "S701_VL2L3", // + "Phase Voltage L2-L3", // + "Phase voltage L2-L3.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT, // + "V_SF", // + new OptionsEnum[0])), // + VL2(new PointImpl(// + "S701_VL2", // + "Phase Voltage L2-N", // + "Phase voltage L2-N.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT, // + "V_SF", // + new OptionsEnum[0])), // + TOT_WH_INJ_L2(new PointImpl(// + "S701_TOT_WH_INJ_L2", // + "Total Watt-Hours Inj L2", // + "Total active energy injected L2.", // + "", // + PointType.UINT64, // + false, // + AccessMode.READ_ONLY, // + Unit.CUMULATED_WATT_HOURS, // + "TotWh_SF", // + new OptionsEnum[0])), // + TOT_WH_ABS_L2(new PointImpl(// + "S701_TOT_WH_ABS_L2", // + "Total Watt-Hours Abs L2", // + "Total active energy absorbed L2.", // + "", // + PointType.UINT64, // + false, // + AccessMode.READ_ONLY, // + Unit.CUMULATED_WATT_HOURS, // + "TotWh_SF", // + new OptionsEnum[0])), // + TOT_VARH_INJ_L2(new PointImpl(// + "S701_TOT_VARH_INJ_L2", // + "Total Var-Hours Inj L2", // + "Total reactive energy injected L2.", // + "", // + PointType.UINT64, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT_AMPERE_REACTIVE_HOURS, // + "TotVarh_SF", // + new OptionsEnum[0])), // + TOT_VARH_ABS_L2(new PointImpl(// + "S701_TOT_VARH_ABS_L2", // + "Total Var-Hours Abs L2", // + "Total reactive energy absorbed L2.", // + "", // + PointType.UINT64, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT_AMPERE_REACTIVE_HOURS, // + "TotVarh_SF", // + new OptionsEnum[0])), // + WL3(new PointImpl(// + "S701_WL3", // + "Watts L3", // + "Active power L3.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.WATT, // + "W_SF", // + new OptionsEnum[0])), // + VAL3(new PointImpl(// + "S701_VAL3", // + "VA L3", // + "Apparent power L3.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT_AMPERE, // + "VA_SF", // + new OptionsEnum[0])), // + VAR_L3(new PointImpl(// + "S701_VAR_L3", // + "Var L3", // + "Reactive power L3.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT_AMPERE_REACTIVE, // + "Var_SF", // + new OptionsEnum[0])), // + PFL3(new PointImpl(// + "S701_PFL3", // + "PF L3", // + "Power factor L3.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + "PF_SF", // + new OptionsEnum[0])), // + AL3(new PointImpl(// + "S701_AL3", // + "Amps L3", // + "Current L3.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.AMPERE, // + "A_SF", // + new OptionsEnum[0])), // + VL3L1(new PointImpl(// + "S701_VL3L1", // + "Phase Voltage L3-L1", // + "Phase voltage L3-L1.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT, // + "V_SF", // + new OptionsEnum[0])), // + VL3(new PointImpl(// + "S701_VL3", // + "Phase Voltage L3-N", // + "Phase voltage L3-N.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT, // + "V_SF", // + new OptionsEnum[0])), // + TOT_WH_INJ_L3(new PointImpl(// + "S701_TOT_WH_INJ_L3", // + "Total Watt-Hours Inj L3", // + "Total active energy injected L3.", // + "", // + PointType.UINT64, // + false, // + AccessMode.READ_ONLY, // + Unit.CUMULATED_WATT_HOURS, // + "TotWh_SF", // + new OptionsEnum[0])), // + TOT_WH_ABS_L3(new PointImpl(// + "S701_TOT_WH_ABS_L3", // + "Total Watt-Hours Abs L3", // + "Total active energy absorbed L3.", // + "", // + PointType.UINT64, // + false, // + AccessMode.READ_ONLY, // + Unit.CUMULATED_WATT_HOURS, // + "TotWh_SF", // + new OptionsEnum[0])), // + TOT_VARH_INJ_L3(new PointImpl(// + "S701_TOT_VARH_INJ_L3", // + "Total Var-Hours Inj L3", // + "Total reactive energy injected L3.", // + "", // + PointType.UINT64, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT_AMPERE_REACTIVE_HOURS, // + "TotVarh_SF", // + new OptionsEnum[0])), // + TOT_VARH_ABS_L3(new PointImpl(// + "S701_TOT_VARH_ABS_L3", // + "Total Var-Hours Abs L3", // + "Total reactive energy absorbed L3.", // + "", // + PointType.UINT64, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT_AMPERE_REACTIVE_HOURS, // + "TotVarh_SF", // + new OptionsEnum[0])), // + THROT_PCT(new PointImpl(// + "S701_THROT_PCT", // + "Throttling In Pct", // + "Throttling in pct of maximum active power.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + THROT_SRC(new PointImpl(// + "S701_THROT_SRC", // + "Throttle Source Information", // + "Active throttling source.", // + "", // + PointType.BITFIELD32, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + S701_ThrotSrc.values())), // + A_SF(new PointImpl(// + "S701_A_SF", // + "Current Scale Factor", // + "Current scale factor.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + V_SF(new PointImpl(// + "S701_V_SF", // + "Voltage Scale Factor", // + "Voltage scale factor.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + HZ_S_F(new PointImpl(// + "S701_HZ_S_F", // + "Frequency Scale Factor", // + "Frequency scale factor.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + W_SF(new PointImpl(// + "S701_W_SF", // + "Active Power Scale Factor", // + "Active power scale factor.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + PF_SF(new PointImpl(// + "S701_PF_SF", // + "Power Factor Scale Factor", // + "Power factor scale factor.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + VA_SF(new PointImpl(// + "S701_VA_SF", // + "Apparent Power Scale Factor", // + "Apparent power scale factor.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + VAR_S_F(new PointImpl(// + "S701_VAR_S_F", // + "Reactive Power Scale Factor", // + "Reactive power scale factor.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + TOT_WH_S_F(new PointImpl(// + "S701_TOT_WH_S_F", // + "Active Energy Scale Factor", // + "Active energy scale factor.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + TOT_VARH_S_F(new PointImpl(// + "S701_TOT_VARH_S_F", // + "Reactive Energy Scale Factor", // + "Reactive energy scale factor.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + TMP_S_F(new PointImpl(// + "S701_TMP_S_F", // + "Temperature Scale Factor", // + "Temperature scale factor.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + MN_ALRM_INFO(new PointImpl(// + "S701_MN_ALRM_INFO", // + "Manufacturer Alarm Info", // + "Manufacturer alarm information. Valid if MANUFACTURER_ALRM indication is active.", // + "", // + PointType.STRING32, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])); // + + protected final PointImpl impl; + + private S701(PointImpl impl) { + this.impl = impl; + } + + @Override + public PointImpl get() { + return this.impl; + } + } + + public static enum S701_ACType implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + SINGLE_PHASE(0, "SINGLE_PHASE"), // + SPLIT_PHASE(1, "SPLIT_PHASE"), // + THREE_PHASE(2, "THREE_PHASE"); // + + private final int value; + private final String name; + + private S701_ACType(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S701_St implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + OFF(0, "OFF"), // + ON(1, "ON"); // + + private final int value; + private final String name; + + private S701_St(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S701_InvSt implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + OFF(0, "OFF"), // + SLEEPING(1, "SLEEPING"), // + STARTING(2, "STARTING"), // + RUNNING(3, "RUNNING"), // + THROTTLED(4, "THROTTLED"), // + SHUTTING_DOWN(5, "SHUTTING_DOWN"), // + FAULT(6, "FAULT"), // + STANDBY(7, "STANDBY"); // + + private final int value; + private final String name; + + private S701_InvSt(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S701_ConnSt implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + DISCONNECTED(0, "DISCONNECTED"), // + CONNECTED(1, "CONNECTED"); // + + private final int value; + private final String name; + + private S701_ConnSt(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S701_Alrm implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + GROUND_FAULT(0, "GROUND_FAULT"), // + DC_OVER_VOLT(1, "DC_OVER_VOLT"), // + AC_DISCONNECT(2, "AC_DISCONNECT"), // + DC_DISCONNECT(3, "DC_DISCONNECT"), // + GRID_DISCONNECT(4, "GRID_DISCONNECT"), // + CABINET_OPEN(5, "CABINET_OPEN"), // + MANUAL_SHUTDOWN(6, "MANUAL_SHUTDOWN"), // + OVER_TEMP(7, "OVER_TEMP"), // + OVER_FREQUENCY(8, "OVER_FREQUENCY"), // + UNDER_FREQUENCY(9, "UNDER_FREQUENCY"), // + AC_OVER_VOLT(10, "AC_OVER_VOLT"), // + AC_UNDER_VOLT(11, "AC_UNDER_VOLT"), // + BLOWN_STRING_FUSE(12, "BLOWN_STRING_FUSE"), // + UNDER_TEMP(13, "UNDER_TEMP"), // + MEMORY_LOSS(14, "MEMORY_LOSS"), // + HW_TEST_FAILURE(15, "HW_TEST_FAILURE"), // + MANUFACTURER_ALRM(16, "MANUFACTURER_ALRM"); // + + private final int value; + private final String name; + + private S701_Alrm(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S701_DERMode implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + GRID_FOLLOWING(0, "GRID_FOLLOWING"), // + GRID_FORMING(1, "GRID_FORMING"), // + PV_CLIPPED(2, "PV_CLIPPED"); // + + private final int value; + private final String name; + + private S701_DERMode(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S701_ThrotSrc implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + MAX_W(0, "MAX_W"), // + FIXED_W(1, "FIXED_W"), // + FIXED_VAR(2, "FIXED_VAR"), // + FIXED_PF(3, "FIXED_PF"), // + VOLT_VAR(4, "VOLT_VAR"), // + FREQ_WATT(5, "FREQ_WATT"), // + DYN_REACT_CURR(6, "DYN_REACT_CURR"), // + LVRT(7, "LVRT"), // + HVRT(8, "HVRT"), // + WATT_VAR(9, "WATT_VAR"), // + VOLT_WATT(10, "VOLT_WATT"), // + SCHEDULED(11, "SCHEDULED"), // + LFRT(12, "LFRT"), // + HFRT(13, "HFRT"), // + DERATED(14, "DERATED"); // + + private final int value; + private final String name; + + private S701_ThrotSrc(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S702 implements SunSpecPoint { + W_MAX_RTG(new PointImpl(// + "S702_W_MAX_RTG", // + "Active Power Max Rating", // + "Maximum active power rating at unity power factor in watts.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.WATT, // + "W_SF", // + new OptionsEnum[0])), // + W_OVR_EXT_RTG(new PointImpl(// + "S702_W_OVR_EXT_RTG", // + "Active Power (Over-Excited) Rating", // + "Active power rating at specified over-excited power factor in watts.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.WATT, // + "W_SF", // + new OptionsEnum[0])), // + W_OVR_EXT_RTG_P_F(new PointImpl(// + "S702_W_OVR_EXT_RTG_P_F", // + "Specified Over-Excited PF", // + "Specified over-excited power factor.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + "PF_SF", // + new OptionsEnum[0])), // + W_UND_EXT_RTG(new PointImpl(// + "S702_W_UND_EXT_RTG", // + "Active Power (Under-Excited) Rating", // + "Active power rating at specified under-excited power factor in watts.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.WATT, // + "W_SF", // + new OptionsEnum[0])), // + W_UND_EXT_RTG_P_F(new PointImpl(// + "S702_W_UND_EXT_RTG_P_F", // + "Specified Under-Excited PF", // + "Specified under-excited power factor.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + "PF_SF", // + new OptionsEnum[0])), // + V_A_MAX_RTG(new PointImpl(// + "S702_V_A_MAX_RTG", // + "Apparent Power Max Rating", // + "Maximum apparent power rating in voltamperes.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT_AMPERE, // + "VA_SF", // + new OptionsEnum[0])), // + VAR_MAX_INJ_RTG(new PointImpl(// + "S702_VAR_MAX_INJ_RTG", // + "Reactive Power Injected Rating", // + "Maximum injected reactive power rating in vars.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT_AMPERE_REACTIVE, // + "Var_SF", // + new OptionsEnum[0])), // + VAR_MAX_ABS_RTG(new PointImpl(// + "S702_VAR_MAX_ABS_RTG", // + "Reactive Power Absorbed Rating", // + "Maximum absorbed reactive power rating in vars.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT_AMPERE_REACTIVE, // + "Var_SF", // + new OptionsEnum[0])), // + W_CHA_RTE_MAX_RTG(new PointImpl(// + "S702_W_CHA_RTE_MAX_RTG", // + "Charge Rate Max Rating", // + "Maximum active power charge rate in watts.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.WATT, // + "W_SF", // + new OptionsEnum[0])), // + W_DIS_CHA_RTE_MAX_RTG(new PointImpl(// + "S702_W_DIS_CHA_RTE_MAX_RTG", // + "Discharge Rate Max Rating", // + "Maximum active power discharge rate in watts.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.WATT, // + "W_SF", // + new OptionsEnum[0])), // + V_A_CHA_RTE_MAX_RTG(new PointImpl(// + "S702_V_A_CHA_RTE_MAX_RTG", // + "Charge Rate Max VA Rating", // + "Maximum apparent power charge rate in voltamperes.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT_AMPERE, // + "VA_SF", // + new OptionsEnum[0])), // + V_A_DIS_CHA_RTE_MAX_RTG(new PointImpl(// + "S702_V_A_DIS_CHA_RTE_MAX_RTG", // + "Discharge Rate Max VA Rating", // + "Maximum apparent power discharge rate in voltamperes.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT_AMPERE, // + "VA_SF", // + new OptionsEnum[0])), // + V_NOM_RTG(new PointImpl(// + "S702_V_NOM_RTG", // + "AC Voltage Nominal Rating", // + "AC voltage nominal rating.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT, // + "V_SF", // + new OptionsEnum[0])), // + V_MAX_RTG(new PointImpl(// + "S702_V_MAX_RTG", // + "AC Voltage Max Rating", // + "AC voltage maximum rating.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT, // + "V_SF", // + new OptionsEnum[0])), // + V_MIN_RTG(new PointImpl(// + "S702_V_MIN_RTG", // + "AC Voltage Min Rating", // + "AC voltage minimum rating.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT, // + "V_SF", // + new OptionsEnum[0])), // + A_MAX_RTG(new PointImpl(// + "S702_A_MAX_RTG", // + "AC Current Max Rating", // + "AC current maximum rating in amps.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.AMPERE, // + "A_SF", // + new OptionsEnum[0])), // + P_F_OVR_EXT_RTG(new PointImpl(// + "S702_P_F_OVR_EXT_RTG", // + "PF Over-Excited Rating", // + "Power factor over-excited rating.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + "PF_SF", // + new OptionsEnum[0])), // + P_F_UND_EXT_RTG(new PointImpl(// + "S702_P_F_UND_EXT_RTG", // + "PF Under-Excited Rating", // + "Power factor under-excited rating.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + "PF_SF", // + new OptionsEnum[0])), // + REACT_SUSCEPT_RTG(new PointImpl(// + "S702_REACT_SUSCEPT_RTG", // + "Reactive Susceptance", // + "Reactive susceptance that remains connected to the Area EPS in the cease to energize and trip state.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + "S_SF", // + new OptionsEnum[0])), // + NOR_OP_CAT_RTG(new PointImpl(// + "S702_NOR_OP_CAT_RTG", // + "Normal Operating Category", // + "Normal operating performance category as specified in IEEE 1547-2018.", // + "", // + PointType.ENUM16, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + S702_NorOpCatRtg.values())), // + ABN_OP_CAT_RTG(new PointImpl(// + "S702_ABN_OP_CAT_RTG", // + "Abnormal Operating Category", // + "Abnormal operating performance category as specified in IEEE 1547-2018.", // + "", // + PointType.ENUM16, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + S702_AbnOpCatRtg.values())), // + CTRL_MODES(new PointImpl(// + "S702_CTRL_MODES", // + "Supported Control Modes", // + "Supported control mode functions.", // + "", // + PointType.BITFIELD32, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + S702_CtrlModes.values())), // + INT_ISLAND_CAT_RTG(new PointImpl(// + "S702_INT_ISLAND_CAT_RTG", // + "Intentional Island Categories", // + "Intentional island categories.", // + "", // + PointType.BITFIELD16, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + S702_IntIslandCatRtg.values())), // + W_MAX(new PointImpl(// + "S702_W_MAX", // + "Active Power Max Setting", // + "Maximum active power setting used to adjust maximum active power setting.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_WRITE, // + Unit.WATT, // + "W_SF", // + new OptionsEnum[0])), // + W_MAX_OVR_EXT(new PointImpl(// + "S702_W_MAX_OVR_EXT", // + "Active Power (Over-Excited) Setting", // + "Active power setting at specified over-excited power factor in watts.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_WRITE, // + Unit.WATT, // + "W_SF", // + new OptionsEnum[0])), // + W_OVR_EXT_P_F(new PointImpl(// + "S702_W_OVR_EXT_P_F", // + "Specified Over-Excited PF", // + "Specified over-excited power factor.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + "PF_SF", // + new OptionsEnum[0])), // + W_MAX_UND_EXT(new PointImpl(// + "S702_W_MAX_UND_EXT", // + "Active Power (Under-Excited) Setting", // + "Active power setting at specified under-excited power factor in watts.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_WRITE, // + Unit.WATT, // + "W_SF", // + new OptionsEnum[0])), // + W_UND_EXT_P_F(new PointImpl(// + "S702_W_UND_EXT_P_F", // + "Specified Under-Excited PF", // + "Specified under-excited power factor.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + "PF_SF", // + new OptionsEnum[0])), // + V_A_MAX(new PointImpl(// + "S702_V_A_MAX", // + "Apparent Power Max Setting", // + "Maximum apparent power setting used to adjust maximum apparent power rating.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_WRITE, // + Unit.VOLT_AMPERE, // + "VA_SF", // + new OptionsEnum[0])), // + VAR_MAX_INJ(new PointImpl(// + "S702_VAR_MAX_INJ", // + "Reactive Power Injected Setting", // + "Maximum injected reactive power setting used to adjust maximum injected reactive power rating.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_WRITE, // + Unit.VOLT_AMPERE_REACTIVE, // + "Var_SF", // + new OptionsEnum[0])), // + VAR_MAX_ABS(new PointImpl(// + "S702_VAR_MAX_ABS", // + "Reactive Power Absorbed Setting", // + "Maximum absorbed reactive power setting used to adjust maximum absorbed reactive power rating.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_WRITE, // + Unit.VOLT_AMPERE_REACTIVE, // + "Var_SF", // + new OptionsEnum[0])), // + W_CHA_RTE_MAX(new PointImpl(// + "S702_W_CHA_RTE_MAX", // + "Charge Rate Max Setting", // + "Maximum active power charge rate setting used to adjust maximum active power charge rate rating.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_WRITE, // + Unit.WATT, // + "W_SF", // + new OptionsEnum[0])), // + W_DIS_CHA_RTE_MAX(new PointImpl(// + "S702_W_DIS_CHA_RTE_MAX", // + "Discharge Rate Max Setting", // + "Maximum active power discharge rate setting used to adjust maximum active power discharge rate rating.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_WRITE, // + Unit.WATT, // + "W_SF", // + new OptionsEnum[0])), // + V_A_CHA_RTE_MAX(new PointImpl(// + "S702_V_A_CHA_RTE_MAX", // + "Charge Rate Max VA Setting", // + "Maximum apparent power charge rate setting used to adjust maximum apparent power charge rate rating.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_WRITE, // + Unit.VOLT_AMPERE, // + "VA_SF", // + new OptionsEnum[0])), // + V_A_DIS_CHA_RTE_MAX(new PointImpl(// + "S702_V_A_DIS_CHA_RTE_MAX", // + "Discharge Rate Max VA Setting", // + "Maximum apparent power discharge rate setting used to adjust maximum apparent power discharge rate rating.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_WRITE, // + Unit.VOLT_AMPERE, // + "VA_SF", // + new OptionsEnum[0])), // + V_NOM(new PointImpl(// + "S702_V_NOM", // + "Nominal AC Voltage Setting", // + "Nominal AC voltage setting.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_WRITE, // + Unit.VOLT, // + "V_SF", // + new OptionsEnum[0])), // + V_MAX(new PointImpl(// + "S702_V_MAX", // + "AC Voltage Max Setting", // + "AC voltage maximum setting used to adjust AC voltage maximum rating.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_WRITE, // + Unit.VOLT, // + "V_SF", // + new OptionsEnum[0])), // + V_MIN(new PointImpl(// + "S702_V_MIN", // + "AC Voltage Min Setting", // + "AC voltage minimum setting used to adjust AC voltage minimum rating.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_WRITE, // + Unit.VOLT, // + "V_SF", // + new OptionsEnum[0])), // + A_MAX(new PointImpl(// + "S702_A_MAX", // + "AC Current Max Setting", // + "Maximum AC current setting used to adjust maximum AC current rating.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_WRITE, // + Unit.AMPERE, // + "A_SF", // + new OptionsEnum[0])), // + P_F_OVR_EXT(new PointImpl(// + "S702_P_F_OVR_EXT", // + "PF Over-Excited Setting", // + "Power factor over-excited setting.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + "PF_SF", // + new OptionsEnum[0])), // + P_F_UND_EXT(new PointImpl(// + "S702_P_F_UND_EXT", // + "PF Under-Excited Setting", // + "Power factor under-excited setting.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + "PF_SF", // + new OptionsEnum[0])), // + INT_ISLAND_CAT(new PointImpl(// + "S702_INT_ISLAND_CAT", // + "Intentional Island Categories", // + "Intentional island categories.", // + "", // + PointType.BITFIELD16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + S702_IntIslandCat.values())), // + W_SF(new PointImpl(// + "S702_W_SF", // + "Active Power Scale Factor", // + "Active power scale factor.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + PF_SF(new PointImpl(// + "S702_PF_SF", // + "Power Factor Scale Factor", // + "Power factor scale factor.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + VA_SF(new PointImpl(// + "S702_VA_SF", // + "Apparent Power Scale Factor", // + "Apparent power scale factor.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + VAR_S_F(new PointImpl(// + "S702_VAR_S_F", // + "Reactive Power Scale Factor", // + "Reactive power scale factor.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + V_SF(new PointImpl(// + "S702_V_SF", // + "Voltage Scale Factor", // + "Voltage scale factor.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + A_SF(new PointImpl(// + "S702_A_SF", // + "Current Scale Factor", // + "Current scale factor.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + S_SF(new PointImpl(// + "S702_S_SF", // + "Susceptance Scale Factor", // + "Susceptance scale factor.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])); // + + protected final PointImpl impl; + + private S702(PointImpl impl) { + this.impl = impl; + } + + @Override + public PointImpl get() { + return this.impl; + } + } + + public static enum S702_NorOpCatRtg implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + CAT_A(0, "CAT_A"), // + CAT_B(1, "CAT_B"); // + + private final int value; + private final String name; + + private S702_NorOpCatRtg(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S702_AbnOpCatRtg implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + CAT_1(0, "CAT_1"), // + CAT_2(1, "CAT_2"), // + CAT_3(2, "CAT_3"); // + + private final int value; + private final String name; + + private S702_AbnOpCatRtg(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S702_CtrlModes implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + MAX_W(0, "MAX_W"), // + FIXED_W(1, "FIXED_W"), // + FIXED_VAR(2, "FIXED_VAR"), // + FIXED_PF(3, "FIXED_PF"), // + VOLT_VAR(4, "VOLT_VAR"), // + FREQ_WATT(5, "FREQ_WATT"), // + DYN_REACT_CURR(6, "DYN_REACT_CURR"), // + LV_TRIP(7, "LV_TRIP"), // + HV_TRIP(8, "HV_TRIP"), // + WATT_VAR(9, "WATT_VAR"), // + VOLT_WATT(10, "VOLT_WATT"), // + SCHEDULED(11, "SCHEDULED"), // + LF_TRIP(12, "LF_TRIP"), // + HF_TRIP(13, "HF_TRIP"); // + + private final int value; + private final String name; + + private S702_CtrlModes(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S702_IntIslandCatRtg implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + UNCATEGORIZED(0, "UNCATEGORIZED"), // + INT_ISL_CAPABLE(1, "INT_ISL_CAPABLE"), // + BLACK_START_CAPABLE(2, "BLACK_START_CAPABLE"), // + ISOCH_CAPABLE(3, "ISOCH_CAPABLE"); // + + private final int value; + private final String name; + + private S702_IntIslandCatRtg(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S702_IntIslandCat implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + UNCATEGORIZED(0, "UNCATEGORIZED"), // + INT_ISL_CAPABLE(1, "INT_ISL_CAPABLE"), // + BLACK_START_CAPABLE(2, "BLACK_START_CAPABLE"), // + ISOCH_CAPABLE(3, "ISOCH_CAPABLE"); // + + private final int value; + private final String name; + + private S702_IntIslandCat(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S703 implements SunSpecPoint { + ES(new PointImpl(// + "S703_ES", // + "Permit Enter Service", // + "Permit enter service.", // + "", // + PointType.ENUM16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + S703_ES.values())), // + E_S_V_HI(new PointImpl(// + "S703_E_S_V_HI", // + "Enter Service Voltage High", // + "Enter service voltage high threshold as percent of normal voltage.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + "V_SF", // + new OptionsEnum[0])), // + E_S_V_LO(new PointImpl(// + "S703_E_S_V_LO", // + "Enter Service Voltage Low", // + "Enter service voltage low threshold as percent of normal voltage.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + "V_SF", // + new OptionsEnum[0])), // + E_S_HZ_HI(new PointImpl(// + "S703_E_S_HZ_HI", // + "Enter Service Frequency High", // + "Enter service frequency high threshold.", // + "", // + PointType.UINT32, // + false, // + AccessMode.READ_WRITE, // + Unit.HERTZ, // + "Hz_SF", // + new OptionsEnum[0])), // + E_S_HZ_LO(new PointImpl(// + "S703_E_S_HZ_LO", // + "Enter Service Frequency Low", // + "Enter service frequency low threshold.", // + "", // + PointType.UINT32, // + false, // + AccessMode.READ_WRITE, // + Unit.HERTZ, // + "Hz_SF", // + new OptionsEnum[0])), // + E_S_DLY_TMS(new PointImpl(// + "S703_E_S_DLY_TMS", // + "Enter Service Delay Time", // + "Enter service delay time in seconds.", // + "", // + PointType.UINT32, // + false, // + AccessMode.READ_WRITE, // + Unit.SECONDS, // + null, // + new OptionsEnum[0])), // + E_S_RND_TMS(new PointImpl(// + "S703_E_S_RND_TMS", // + "Enter Service Random Delay", // + "Enter service random delay in seconds.", // + "", // + PointType.UINT32, // + false, // + AccessMode.READ_WRITE, // + Unit.SECONDS, // + null, // + new OptionsEnum[0])), // + E_S_RMP_TMS(new PointImpl(// + "S703_E_S_RMP_TMS", // + "Enter Service Ramp Time", // + "Enter service ramp time in seconds.", // + "", // + PointType.UINT32, // + false, // + AccessMode.READ_WRITE, // + Unit.SECONDS, // + null, // + new OptionsEnum[0])), // + E_S_DLY_REM_TMS(new PointImpl(// + "S703_E_S_DLY_REM_TMS", // + "Enter Service Delay Remaining", // + "Enter service delay time remaining in seconds.", // + "", // + PointType.UINT32, // + false, // + AccessMode.READ_ONLY, // + Unit.SECONDS, // + null, // + new OptionsEnum[0])), // + V_SF(new PointImpl(// + "S703_V_SF", // + "Voltage Scale Factor", // + "Voltage percentage scale factor.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + HZ_S_F(new PointImpl(// + "S703_HZ_S_F", // + "Frequency Scale Factor", // + "Frequency scale factor.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])); // + + protected final PointImpl impl; + + private S703(PointImpl impl) { + this.impl = impl; + } + + @Override + public PointImpl get() { + return this.impl; + } + } + + public static enum S703_ES implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + DISABLED(0, "DISABLED"), // + ENABLED(1, "ENABLED"); // + + private final int value; + private final String name; + + private S703_ES(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S704 implements SunSpecPoint { + P_F_W_INJ_ENA(new PointImpl(// + "S704_P_F_W_INJ_ENA", // + "Power Factor Enable (W Inj) Enable", // + "Power factor enable when injecting active power.", // + "", // + PointType.ENUM16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + S704_PFWInjEna.values())), // + P_F_W_INJ_ENA_RVRT(new PointImpl(// + "S704_P_F_W_INJ_ENA_RVRT", // + "Power Factor Reversion Enable (W Inj)", // + "Power factor reversion timer when injecting active power enable.", // + "", // + PointType.ENUM16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + S704_PFWInjEnaRvrt.values())), // + P_F_W_INJ_RVRT_TMS(new PointImpl(// + "S704_P_F_W_INJ_RVRT_TMS", // + "PF Reversion Time (W Inj)", // + "Power factor reversion timer when injecting active power.", // + "", // + PointType.UINT32, // + false, // + AccessMode.READ_WRITE, // + Unit.SECONDS, // + null, // + new OptionsEnum[0])), // + P_F_W_INJ_RVRT_REM(new PointImpl(// + "S704_P_F_W_INJ_RVRT_REM", // + "PF Reversion Time Rem (W Inj)", // + "Power factor reversion time remaining when injecting active power.", // + "", // + PointType.UINT32, // + false, // + AccessMode.READ_ONLY, // + Unit.SECONDS, // + null, // + new OptionsEnum[0])), // + P_F_W_ABS_ENA(new PointImpl(// + "S704_P_F_W_ABS_ENA", // + "Power Factor Enable (W Abs) Enable", // + "Power factor enable when absorbing active power.", // + "", // + PointType.ENUM16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + S704_PFWAbsEna.values())), // + P_F_W_ABS_ENA_RVRT(new PointImpl(// + "S704_P_F_W_ABS_ENA_RVRT", // + "Power Factor Reversion Enable (W Abs)", // + "Power factor reversion timer when absorbing active power enable.", // + "", // + PointType.ENUM16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + S704_PFWAbsEnaRvrt.values())), // + P_F_W_ABS_RVRT_TMS(new PointImpl(// + "S704_P_F_W_ABS_RVRT_TMS", // + "PF Reversion Time (W Abs)", // + "Power factor reversion timer when absorbing active power.", // + "", // + PointType.UINT32, // + false, // + AccessMode.READ_WRITE, // + Unit.SECONDS, // + null, // + new OptionsEnum[0])), // + P_F_W_ABS_RVRT_REM(new PointImpl(// + "S704_P_F_W_ABS_RVRT_REM", // + "PF Reversion Time Rem (W Abs)", // + "Power factor reversion time remaining when absorbing active power.", // + "", // + PointType.UINT32, // + false, // + AccessMode.READ_ONLY, // + Unit.SECONDS, // + null, // + new OptionsEnum[0])), // + W_MAX_LIM_PCT_ENA(new PointImpl(// + "S704_W_MAX_LIM_PCT_ENA", // + "Limit Max Power Pct Enable", // + "Limit maximum active power percent enable.", // + "", // + PointType.ENUM16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + S704_WMaxLimPctEna.values())), // + W_MAX_LIM_PCT(new PointImpl(// + "S704_W_MAX_LIM_PCT", // + "Limit Max Power Pct Setpoint", // + "Limit maximum active power percent value.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + "WMaxLimPct_SF", // + new OptionsEnum[0])), // + W_MAX_LIM_PCT_RVRT(new PointImpl(// + "S704_W_MAX_LIM_PCT_RVRT", // + "Reversion Limit Max Power Pct", // + "Reversion limit maximum active power percent value.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + "WMaxLimPct_SF", // + new OptionsEnum[0])), // + W_MAX_LIM_PCT_ENA_RVRT(new PointImpl(// + "S704_W_MAX_LIM_PCT_ENA_RVRT", // + "Reversion Limit Max Power Pct Enable", // + "Reversion limit maximum active power percent value enable.", // + "", // + PointType.ENUM16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + S704_WMaxLimPctEnaRvrt.values())), // + W_MAX_LIM_PCT_RVRT_TMS(new PointImpl(// + "S704_W_MAX_LIM_PCT_RVRT_TMS", // + "Limit Max Power Pct Reversion Time", // + "Limit maximum active power percent reversion time.", // + "", // + PointType.UINT32, // + false, // + AccessMode.READ_WRITE, // + Unit.SECONDS, // + null, // + new OptionsEnum[0])), // + W_MAX_LIM_PCT_RVRT_REM(new PointImpl(// + "S704_W_MAX_LIM_PCT_RVRT_REM", // + "Limit Max Power Pct Rev Time Rem", // + "Limit maximum active power percent reversion time remaining.", // + "", // + PointType.UINT32, // + false, // + AccessMode.READ_ONLY, // + Unit.SECONDS, // + null, // + new OptionsEnum[0])), // + W_SET_ENA(new PointImpl(// + "S704_W_SET_ENA", // + "Set Active Power Enable", // + "Set active power enable.", // + "", // + PointType.ENUM16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + S704_WSetEna.values())), // + W_SET_MOD(new PointImpl(// + "S704_W_SET_MOD", // + "Set Active Power Mode", // + "Set active power mode.", // + "", // + PointType.ENUM16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + S704_WSetMod.values())), // + W_SET(new PointImpl(// + "S704_W_SET", // + "Active Power Setpoint (W)", // + "Active power setting value in watts.", // + "", // + PointType.INT32, // + false, // + AccessMode.READ_WRITE, // + Unit.WATT, // + "WSet_SF", // + new OptionsEnum[0])), // + W_SET_RVRT(new PointImpl(// + "S704_W_SET_RVRT", // + "Reversion Active Power (W)", // + "Reversion active power setting value in watts.", // + "", // + PointType.INT32, // + false, // + AccessMode.READ_WRITE, // + Unit.WATT, // + "WSet_SF", // + new OptionsEnum[0])), // + W_SET_PCT(new PointImpl(// + "S704_W_SET_PCT", // + "Active Power Setpoint (Pct)", // + "Active power setting value as percent.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + "WSetPct_SF", // + new OptionsEnum[0])), // + W_SET_PCT_RVRT(new PointImpl(// + "S704_W_SET_PCT_RVRT", // + "Reversion Active Power (Pct)", // + "Reversion active power setting value as percent.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + "WSetPct_SF", // + new OptionsEnum[0])), // + W_SET_ENA_RVRT(new PointImpl(// + "S704_W_SET_ENA_RVRT", // + "Reversion Active Power Enable", // + "Reversion active power function enable.", // + "", // + PointType.ENUM16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + S704_WSetEnaRvrt.values())), // + W_SET_RVRT_TMS(new PointImpl(// + "S704_W_SET_RVRT_TMS", // + "Active Power Reversion Time", // + "Set active power reversion time.", // + "", // + PointType.UINT32, // + false, // + AccessMode.READ_WRITE, // + Unit.SECONDS, // + null, // + new OptionsEnum[0])), // + W_SET_RVRT_REM(new PointImpl(// + "S704_W_SET_RVRT_REM", // + "Active Power Rev Time Rem", // + "Set active power reversion time remaining.", // + "", // + PointType.UINT32, // + false, // + AccessMode.READ_ONLY, // + Unit.SECONDS, // + null, // + new OptionsEnum[0])), // + VAR_SET_ENA(new PointImpl(// + "S704_VAR_SET_ENA", // + "Set Reactive Power Enable", // + "Set reactive power enable.", // + "", // + PointType.ENUM16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + S704_VarSetEna.values())), // + VAR_SET_MOD(new PointImpl(// + "S704_VAR_SET_MOD", // + "Set Reactive Power Mode", // + "Set reactive power mode.", // + "", // + PointType.ENUM16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + S704_VarSetMod.values())), // + VAR_SET_PRI(new PointImpl(// + "S704_VAR_SET_PRI", // + "Reactive Power Priority", // + "Reactive power priority.", // + "", // + PointType.ENUM16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + S704_VarSetPri.values())), // + VAR_SET(new PointImpl(// + "S704_VAR_SET", // + "Reactive Power Setpoint (Vars)", // + "Reactive power setting value in vars.", // + "", // + PointType.INT32, // + false, // + AccessMode.READ_WRITE, // + Unit.VOLT_AMPERE_REACTIVE, // + "VarSet_SF", // + new OptionsEnum[0])), // + VAR_SET_RVRT(new PointImpl(// + "S704_VAR_SET_RVRT", // + "Reversion Reactive Power (Vars)", // + "Reversion reactive power setting value in vars.", // + "", // + PointType.INT32, // + false, // + AccessMode.READ_WRITE, // + Unit.VOLT_AMPERE_REACTIVE, // + "VarSet_SF", // + new OptionsEnum[0])), // + VAR_SET_PCT(new PointImpl(// + "S704_VAR_SET_PCT", // + "Reactive Power Setpoint (Pct)", // + "Reactive power setting value as percent.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + "VarSetPct_SF", // + new OptionsEnum[0])), // + VAR_SET_PCT_RVRT(new PointImpl(// + "S704_VAR_SET_PCT_RVRT", // + "Reversion Reactive Power (Pct)", // + "Reversion reactive power setting value as percent.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + "VarSetPct_SF", // + new OptionsEnum[0])), // + VAR_SET_ENA_RVRT(new PointImpl(// + "S704_VAR_SET_ENA_RVRT", // + "Reversion Reactive Power Enable", // + "Reversion reactive power function enable.", // + "", // + PointType.ENUM16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + S704_VarSetEnaRvrt.values())), // + VAR_SET_RVRT_TMS(new PointImpl(// + "S704_VAR_SET_RVRT_TMS", // + "Reactive Power Reversion Time", // + "Set reactive power reversion time.", // + "", // + PointType.UINT32, // + false, // + AccessMode.READ_WRITE, // + Unit.SECONDS, // + null, // + new OptionsEnum[0])), // + VAR_SET_RVRT_REM(new PointImpl(// + "S704_VAR_SET_RVRT_REM", // + "Reactive Power Rev Time Rem", // + "Set reactive power reversion time remaining.", // + "", // + PointType.UINT32, // + false, // + AccessMode.READ_ONLY, // + Unit.SECONDS, // + null, // + new OptionsEnum[0])), // + W_RMP(new PointImpl(// + "S704_W_RMP", // + "Normal Ramp Rate", // + "Ramp rate for increases in active power during normal generation.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + W_RMP_REF(new PointImpl(// + "S704_W_RMP_REF", // + "Normal Ramp Rate Reference", // + "Ramp rate reference unit for increases in active power or current during normal generation.", // + "", // + PointType.ENUM16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + S704_WRmpRef.values())), // + VAR_RMP(new PointImpl(// + "S704_VAR_RMP", // + "Reactive Power Ramp Rate", // + "Ramp rate based on max reactive power per second.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + ANTI_ISL_ENA(new PointImpl(// + "S704_ANTI_ISL_ENA", // + "Anti-Islanding Enable", // + "Anti-islanding enable.", // + "", // + PointType.ENUM16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + S704_AntiIslEna.values())), // + PF_SF(new PointImpl(// + "S704_PF_SF", // + "Power Factor Scale Factor", // + "Power factor scale factor.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + W_MAX_LIM_PCT_S_F(new PointImpl(// + "S704_W_MAX_LIM_PCT_S_F", // + "Limit Max Power Scale Factor", // + "Limit maximum power scale factor.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + W_SET_S_F(new PointImpl(// + "S704_W_SET_S_F", // + "Active Power Scale Factor", // + "Active power scale factor.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + W_SET_PCT_S_F(new PointImpl(// + "S704_W_SET_PCT_S_F", // + "Active Power Pct Scale Factor", // + "Active power pct scale factor.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + VAR_SET_S_F(new PointImpl(// + "S704_VAR_SET_S_F", // + "Reactive Power Scale Factor", // + "Reactive power scale factor.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + VAR_SET_PCT_S_F(new PointImpl(// + "S704_VAR_SET_PCT_S_F", // + "Reactive Power Pct Scale Factor", // + "Reactive power pct scale factor.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])); // + + protected final PointImpl impl; + + private S704(PointImpl impl) { + this.impl = impl; + } + + @Override + public PointImpl get() { + return this.impl; + } + } + + public static enum S704_PFWInjEna implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + DISABLED(0, "DISABLED"), // + ENABLED(1, "ENABLED"); // + + private final int value; + private final String name; + + private S704_PFWInjEna(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S704_PFWInjEnaRvrt implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + DISABLED(0, "DISABLED"), // + ENABLED(1, "ENABLED"); // + + private final int value; + private final String name; + + private S704_PFWInjEnaRvrt(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S704_PFWAbsEna implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + DISABLED(0, "DISABLED"), // + ENABLED(1, "ENABLED"); // + + private final int value; + private final String name; + + private S704_PFWAbsEna(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S704_PFWAbsEnaRvrt implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + DISABLED(0, "DISABLED"), // + ENABLED(1, "ENABLED"); // + + private final int value; + private final String name; + + private S704_PFWAbsEnaRvrt(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S704_WMaxLimPctEna implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + DISABLED(0, "DISABLED"), // + ENABLED(1, "ENABLED"); // + + private final int value; + private final String name; + + private S704_WMaxLimPctEna(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S704_WMaxLimPctEnaRvrt implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + DISABLED(0, "DISABLED"), // + ENABLED(1, "ENABLED"); // + + private final int value; + private final String name; + + private S704_WMaxLimPctEnaRvrt(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S704_WSetEna implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + DISABLED(0, "DISABLED"), // + ENABLED(1, "ENABLED"); // + + private final int value; + private final String name; + + private S704_WSetEna(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S704_WSetMod implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + W_MAX_PCT(0, "W_MAX_PCT"), // + WATTS(1, "WATTS"); // + + private final int value; + private final String name; + + private S704_WSetMod(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S704_WSetEnaRvrt implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + DISABLED(0, "DISABLED"), // + ENABLED(1, "ENABLED"); // + + private final int value; + private final String name; + + private S704_WSetEnaRvrt(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S704_VarSetEna implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + DISABLED(0, "DISABLED"), // + ENABLED(1, "ENABLED"); // + + private final int value; + private final String name; + + private S704_VarSetEna(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S704_VarSetMod implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + W_MAX_PCT(0, "W_MAX_PCT"), // + VAR_MAX_PCT(1, "VAR_MAX_PCT"), // + VAR_AVAIL_PCT(2, "VAR_AVAIL_PCT"), // + VA_MAX_PCT(3, "VA_MAX_PCT"), // + VARS(4, "VARS"); // + + private final int value; + private final String name; + + private S704_VarSetMod(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S704_VarSetPri implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + ACTIVE(0, "ACTIVE"), // + REACTIVE(1, "REACTIVE"), // + VENDOR(2, "VENDOR"); // + + private final int value; + private final String name; + + private S704_VarSetPri(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S704_VarSetEnaRvrt implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + DISABLED(0, "DISABLED"), // + ENABLED(1, "ENABLED"); // + + private final int value; + private final String name; + + private S704_VarSetEnaRvrt(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S704_WRmpRef implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + A_MAX(0, "A_MAX"), // + W_MAX(1, "W_MAX"); // + + private final int value; + private final String name; + + private S704_WRmpRef(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S704_AntiIslEna implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + DISABLED(0, "DISABLED"), // + ENABLED(1, "ENABLED"); // + + private final int value; + private final String name; + + private S704_AntiIslEna(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S705 implements SunSpecPoint { + ENA(new PointImpl(// + "S705_ENA", // + "DER Volt-Var Module Enable", // + "Volt-Var control enable.", // + "", // + PointType.ENUM16, // + true, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + S705_Ena.values())), // + ADPT_CRV_REQ(new PointImpl(// + "S705_ADPT_CRV_REQ", // + "Adopt Curve Request", // + "Index of curve points to adopt. First curve index is 1.", // + "", // + PointType.UINT16, // + true, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + ADPT_CRV_RSLT(new PointImpl(// + "S705_ADPT_CRV_RSLT", // + "Adopt Curve Result", // + "Result of last adopt curve operation.", // + "", // + PointType.ENUM16, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + S705_AdptCrvRslt.values())), // + N_PT(new PointImpl(// + "S705_N_PT", // + "Number Of Points", // + "Number of curve points supported.", // + "", // + PointType.UINT16, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + N_CRV(new PointImpl(// + "S705_N_CRV", // + "Stored Curve Count", // + "Number of stored curves supported.", // + "", // + PointType.UINT16, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + RVRT_TMS(new PointImpl(// + "S705_RVRT_TMS", // + "Reversion Timeout", // + "Reversion time in seconds. 0 = No reversion time.", // + "", // + PointType.UINT32, // + false, // + AccessMode.READ_WRITE, // + Unit.SECONDS, // + null, // + new OptionsEnum[0])), // + RVRT_REM(new PointImpl(// + "S705_RVRT_REM", // + "Reversion Time Remaining", // + "Reversion time remaining in seconds.", // + "", // + PointType.UINT32, // + false, // + AccessMode.READ_ONLY, // + Unit.SECONDS, // + null, // + new OptionsEnum[0])), // + RVRT_CRV(new PointImpl(// + "S705_RVRT_CRV", // + "Reversion Curve", // + "Default curve after reversion timeout.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + V_SF(new PointImpl(// + "S705_V_SF", // + "Voltage Scale Factor", // + "Scale factor for curve voltage points.", // + "", // + PointType.SUNSSF, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + DEPT_REF_S_F(new PointImpl(// + "S705_DEPT_REF_S_F", // + "Var Scale Factor", // + "Scale factor for curve var points.", // + "", // + PointType.SUNSSF, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + RSP_TMS_S_F(new PointImpl(// + "S705_RSP_TMS_S_F", // + "Open-Loop Scale Factor", // + "Open loop response time scale factor.", // + "", // + PointType.SUNSSF, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])); // + + protected final PointImpl impl; + + private S705(PointImpl impl) { + this.impl = impl; + } + + @Override + public PointImpl get() { + return this.impl; + } + } + + public static enum S705_Ena implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + DISABLED(0, "DISABLED"), // + ENABLED(1, "ENABLED"); // + + private final int value; + private final String name; + + private S705_Ena(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S705_AdptCrvRslt implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + IN_PROGRESS(0, "IN_PROGRESS"), // + COMPLETED(1, "COMPLETED"), // + FAILED(2, "FAILED"); // + + private final int value; + private final String name; + + private S705_AdptCrvRslt(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S706 implements SunSpecPoint { + ENA(new PointImpl(// + "S706_ENA", // + "DER Volt-Watt Module Enable", // + "Volt-Watt control enable.", // + "", // + PointType.ENUM16, // + true, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + S706_Ena.values())), // + ADPT_CRV_REQ(new PointImpl(// + "S706_ADPT_CRV_REQ", // + "Adopt Curve Request", // + "Index of curve points to adopt. First curve index is 1.", // + "", // + PointType.UINT16, // + true, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + ADPT_CRV_RSLT(new PointImpl(// + "S706_ADPT_CRV_RSLT", // + "Adopt Curve Result", // + "Result of last adopt curve operation.", // + "", // + PointType.ENUM16, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + S706_AdptCrvRslt.values())), // + N_PT(new PointImpl(// + "S706_N_PT", // + "Number Of Points", // + "Number of curve points supported.", // + "", // + PointType.UINT16, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + N_CRV(new PointImpl(// + "S706_N_CRV", // + "Stored Curve Count", // + "Number of stored curves supported.", // + "", // + PointType.UINT16, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + RVRT_TMS(new PointImpl(// + "S706_RVRT_TMS", // + "Reversion Timeout", // + "Reversion time in seconds. 0 = No reversion time.", // + "", // + PointType.UINT32, // + false, // + AccessMode.READ_WRITE, // + Unit.SECONDS, // + null, // + new OptionsEnum[0])), // + RVRT_REM(new PointImpl(// + "S706_RVRT_REM", // + "Reversion Time Remaining", // + "Reversion time remaining in seconds.", // + "", // + PointType.UINT32, // + false, // + AccessMode.READ_ONLY, // + Unit.SECONDS, // + null, // + new OptionsEnum[0])), // + RVRT_CRV(new PointImpl(// + "S706_RVRT_CRV", // + "Reversion Curve", // + "Default curve after reversion timeout.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + V_SF(new PointImpl(// + "S706_V_SF", // + "Voltage Scale Factor", // + "Scale factor for curve voltage points.", // + "", // + PointType.SUNSSF, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + DEPT_REF_S_F(new PointImpl(// + "S706_DEPT_REF_S_F", // + "Watt Scale Factor", // + "Scale factor for curve watt points.", // + "", // + PointType.SUNSSF, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + RSP_TMS_S_F(new PointImpl(// + "S706_RSP_TMS_S_F", // + "Open-Loop Scale Factor", // + "Open loop response time scale factor.", // + "", // + PointType.SUNSSF, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])); // + + protected final PointImpl impl; + + private S706(PointImpl impl) { + this.impl = impl; + } + + @Override + public PointImpl get() { + return this.impl; + } + } + + public static enum S706_Ena implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + DISABLED(0, "DISABLED"), // + ENABLED(1, "ENABLED"); // + + private final int value; + private final String name; + + private S706_Ena(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S706_AdptCrvRslt implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + IN_PROGRESS(0, "IN_PROGRESS"), // + COMPLETED(1, "COMPLETED"), // + FAILED(2, "FAILED"); // + + private final int value; + private final String name; + + private S706_AdptCrvRslt(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S707 implements SunSpecPoint { + ENA(new PointImpl(// + "S707_ENA", // + "DER Trip LV Module Enable", // + "DER low voltage trip control enable.", // + "", // + PointType.ENUM16, // + true, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + S707_Ena.values())), // + ADPT_CRV_REQ(new PointImpl(// + "S707_ADPT_CRV_REQ", // + "Adopt Curve Request", // + "Index of curve points to adopt. First curve index is 1.", // + "", // + PointType.UINT16, // + true, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + ADPT_CRV_RSLT(new PointImpl(// + "S707_ADPT_CRV_RSLT", // + "Adopt Curve Result", // + "Result of last adopt curve operation.", // + "", // + PointType.ENUM16, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + S707_AdptCrvRslt.values())), // + N_PT(new PointImpl(// + "S707_N_PT", // + "Number Of Points", // + "Number of curve points supported.", // + "", // + PointType.UINT16, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + N_CRV_SET(new PointImpl(// + "S707_N_CRV_SET", // + "Stored Curve Count", // + "Number of stored curves supported.", // + "", // + PointType.UINT16, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + V_SF(new PointImpl(// + "S707_V_SF", // + "Voltage Scale Factor", // + "Scale factor for curve voltage points.", // + "", // + PointType.SUNSSF, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + TMS_S_F(new PointImpl(// + "S707_TMS_S_F", // + "Time Point Scale Factor", // + "Scale factor for curve time points.", // + "", // + PointType.SUNSSF, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])); // + + protected final PointImpl impl; + + private S707(PointImpl impl) { + this.impl = impl; + } + + @Override + public PointImpl get() { + return this.impl; + } + } + + public static enum S707_Ena implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + DISABLED(0, "DISABLED"), // + ENABLED(1, "ENABLED"); // + + private final int value; + private final String name; + + private S707_Ena(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S707_AdptCrvRslt implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + IN_PROGRESS(0, "IN_PROGRESS"), // + COMPLETED(1, "COMPLETED"), // + FAILED(2, "FAILED"); // + + private final int value; + private final String name; + + private S707_AdptCrvRslt(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S708 implements SunSpecPoint { + ENA(new PointImpl(// + "S708_ENA", // + "DER Trip HV Module Enable", // + "DER high voltage trip control enable.", // + "", // + PointType.ENUM16, // + true, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + S708_Ena.values())), // + ADPT_CRV_REQ(new PointImpl(// + "S708_ADPT_CRV_REQ", // + "Adopt Curve Request", // + "Index of curve points to adopt. First curve index is 1.", // + "", // + PointType.UINT16, // + true, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + ADPT_CRV_RSLT(new PointImpl(// + "S708_ADPT_CRV_RSLT", // + "Adopt Curve Result", // + "Result of last adopt curve operation.", // + "", // + PointType.ENUM16, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + S708_AdptCrvRslt.values())), // + N_PT(new PointImpl(// + "S708_N_PT", // + "Number Of Points", // + "Number of curve points supported.", // + "", // + PointType.UINT16, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + N_CRV_SET(new PointImpl(// + "S708_N_CRV_SET", // + "Stored Curve Count", // + "Number of stored curves supported.", // + "", // + PointType.UINT16, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + V_SF(new PointImpl(// + "S708_V_SF", // + "Voltage Scale Factor", // + "Scale factor for curve voltage points.", // + "", // + PointType.SUNSSF, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + TMS_S_F(new PointImpl(// + "S708_TMS_S_F", // + "Time Point Scale Factor", // + "Scale factor for curve time points.", // + "", // + PointType.SUNSSF, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])); // + + protected final PointImpl impl; + + private S708(PointImpl impl) { + this.impl = impl; + } + + @Override + public PointImpl get() { + return this.impl; + } + } + + public static enum S708_Ena implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + DISABLED(0, "DISABLED"), // + ENABLED(1, "ENABLED"); // + + private final int value; + private final String name; + + private S708_Ena(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S708_AdptCrvRslt implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + IN_PROGRESS(0, "IN_PROGRESS"), // + COMPLETED(1, "COMPLETED"), // + FAILED(2, "FAILED"); // + + private final int value; + private final String name; + + private S708_AdptCrvRslt(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S709 implements SunSpecPoint { + ENA(new PointImpl(// + "S709_ENA", // + "DER Trip LF Module Enable", // + "DER low frequency trip control enable.", // + "", // + PointType.ENUM16, // + true, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + S709_Ena.values())), // + ADPT_CRV_REQ(new PointImpl(// + "S709_ADPT_CRV_REQ", // + "Adopt Curve Request", // + "Index of curve points to adopt. First curve index is 1.", // + "", // + PointType.UINT16, // + true, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + ADPT_CRV_RSLT(new PointImpl(// + "S709_ADPT_CRV_RSLT", // + "Adopt Curve Result", // + "Result of last adopt curve operation.", // + "", // + PointType.ENUM16, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + S709_AdptCrvRslt.values())), // + N_PT(new PointImpl(// + "S709_N_PT", // + "Number Of Points", // + "Number of curve points supported.", // + "", // + PointType.UINT16, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + N_CRV_SET(new PointImpl(// + "S709_N_CRV_SET", // + "Stored Curve Count", // + "Number of stored curves supported.", // + "", // + PointType.UINT16, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + HZ_S_F(new PointImpl(// + "S709_HZ_S_F", // + "Frequency Scale Factor", // + "Scale factor for curve frequency points.", // + "", // + PointType.SUNSSF, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + TMS_S_F(new PointImpl(// + "S709_TMS_S_F", // + "Time Point Scale Factor", // + "Scale factor for curve time points.", // + "", // + PointType.SUNSSF, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])); // + + protected final PointImpl impl; + + private S709(PointImpl impl) { + this.impl = impl; + } + + @Override + public PointImpl get() { + return this.impl; + } + } + + public static enum S709_Ena implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + DISABLED(0, "DISABLED"), // + ENABLED(1, "ENABLED"); // + + private final int value; + private final String name; + + private S709_Ena(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S709_AdptCrvRslt implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + IN_PROGRESS(0, "IN_PROGRESS"), // + COMPLETED(1, "COMPLETED"), // + FAILED(2, "FAILED"); // + + private final int value; + private final String name; + + private S709_AdptCrvRslt(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S710 implements SunSpecPoint { + ENA(new PointImpl(// + "S710_ENA", // + "DER Trip HF Module Enable", // + "DER high frequency trip control enable.", // + "", // + PointType.ENUM16, // + true, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + S710_Ena.values())), // + ADPT_CRV_REQ(new PointImpl(// + "S710_ADPT_CRV_REQ", // + "Adopt Curve Request", // + "Index of curve points to adopt. First curve index is 1.", // + "", // + PointType.UINT16, // + true, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + ADPT_CRV_RSLT(new PointImpl(// + "S710_ADPT_CRV_RSLT", // + "Adopt Curve Result", // + "Result of last adopt curve operation.", // + "", // + PointType.ENUM16, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + S710_AdptCrvRslt.values())), // + N_PT(new PointImpl(// + "S710_N_PT", // + "Number Of Points", // + "Number of curve points supported.", // + "", // + PointType.UINT16, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + N_CRV_SET(new PointImpl(// + "S710_N_CRV_SET", // + "Stored Curve Count", // + "Number of stored curves supported.", // + "", // + PointType.UINT16, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + HZ_S_F(new PointImpl(// + "S710_HZ_S_F", // + "Frequency Scale Factor", // + "Scale factor for curve frequency points.", // + "", // + PointType.SUNSSF, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + TMS_S_F(new PointImpl(// + "S710_TMS_S_F", // + "Time Point Scale Factor", // + "Scale factor for curve time points.", // + "", // + PointType.SUNSSF, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])); // + + protected final PointImpl impl; + + private S710(PointImpl impl) { + this.impl = impl; + } + + @Override + public PointImpl get() { + return this.impl; + } + } + + public static enum S710_Ena implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + DISABLED(0, "DISABLED"), // + ENABLED(1, "ENABLED"); // + + private final int value; + private final String name; + + private S710_Ena(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S710_AdptCrvRslt implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + IN_PROGRESS(0, "IN_PROGRESS"), // + COMPLETED(1, "COMPLETED"), // + FAILED(2, "FAILED"); // + + private final int value; + private final String name; + + private S710_AdptCrvRslt(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S711 implements SunSpecPoint { + ENA(new PointImpl(// + "S711_ENA", // + "DER Frequency Droop Module Enable", // + "DER Frequency-Watt (Frequency-Droop) control enable.", // + "", // + PointType.ENUM16, // + true, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + S711_Ena.values())), // + ADPT_CTL_REQ(new PointImpl(// + "S711_ADPT_CTL_REQ", // + "Set Active Control Request", // + "Set active control. 0 = No active control.", // + "", // + PointType.UINT16, // + true, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + ADPT_CTL_RSLT(new PointImpl(// + "S711_ADPT_CTL_RSLT", // + "Set Active Control Result", // + "Result of last set active control operation.", // + "", // + PointType.ENUM16, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + S711_AdptCtlRslt.values())), // + N_CTL(new PointImpl(// + "S711_N_CTL", // + "Stored Control Count", // + "Number of stored controls supported.", // + "", // + PointType.UINT16, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + RVRT_TMS(new PointImpl(// + "S711_RVRT_TMS", // + "Reversion Timeout", // + "Reversion time in seconds. 0 = No reversion time.", // + "", // + PointType.UINT32, // + false, // + AccessMode.READ_WRITE, // + Unit.SECONDS, // + null, // + new OptionsEnum[0])), // + RVRT_REM(new PointImpl(// + "S711_RVRT_REM", // + "Reversion Time Left", // + "Reversion time remaining in seconds.", // + "", // + PointType.UINT32, // + false, // + AccessMode.READ_ONLY, // + Unit.SECONDS, // + null, // + new OptionsEnum[0])), // + RVRT_CTL(new PointImpl(// + "S711_RVRT_CTL", // + "Reversion Control", // + "Default control after reversion timeout.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + DB_S_F(new PointImpl(// + "S711_DB_S_F", // + "Deadband Scale Factor", // + "Deadband scale factor.", // + "", // + PointType.SUNSSF, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + K_SF(new PointImpl(// + "S711_K_SF", // + "Frequency Change Scale Factor", // + "Frequency change scale factor.", // + "", // + PointType.SUNSSF, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + RSP_TMS_S_F(new PointImpl(// + "S711_RSP_TMS_S_F", // + "Open-Loop Scale Factor", // + "Open loop response time scale factor.", // + "", // + PointType.SUNSSF, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])); // + + protected final PointImpl impl; + + private S711(PointImpl impl) { + this.impl = impl; + } + + @Override + public PointImpl get() { + return this.impl; + } + } + + public static enum S711_Ena implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + DISABLED(0, "DISABLED"), // + ENABLED(1, "ENABLED"); // + + private final int value; + private final String name; + + private S711_Ena(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S711_AdptCtlRslt implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + IN_PROGRESS(0, "IN_PROGRESS"), // + COMPLETED(1, "COMPLETED"), // + FAILED(2, "FAILED"); // + + private final int value; + private final String name; + + private S711_AdptCtlRslt(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S712 implements SunSpecPoint { + ENA(new PointImpl(// + "S712_ENA", // + "DER Watt-Var Module Enable", // + "DER Watt-Var control enable.", // + "", // + PointType.ENUM16, // + true, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + S712_Ena.values())), // + ADPT_CRV_REQ(new PointImpl(// + "S712_ADPT_CRV_REQ", // + "Set Active Curve Request", // + "Set active curve. 0 = No active curve.", // + "", // + PointType.UINT16, // + true, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + ADPT_CRV_RSLT(new PointImpl(// + "S712_ADPT_CRV_RSLT", // + "Set Active Curve Result", // + "Result of last set active curve operation.", // + "", // + PointType.ENUM16, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + S712_AdptCrvRslt.values())), // + N_PT(new PointImpl(// + "S712_N_PT", // + "Number Of Points", // + "Number of curve points supported.", // + "", // + PointType.UINT16, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + N_CRV(new PointImpl(// + "S712_N_CRV", // + "Stored Curve Count", // + "Number of stored curves supported.", // + "", // + PointType.UINT16, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + RVRT_TMS(new PointImpl(// + "S712_RVRT_TMS", // + "Reversion Timeout", // + "Reversion time in seconds. 0 = No reversion time.", // + "", // + PointType.UINT32, // + false, // + AccessMode.READ_WRITE, // + Unit.SECONDS, // + null, // + new OptionsEnum[0])), // + RVRT_REM(new PointImpl(// + "S712_RVRT_REM", // + "Reversion Time Left", // + "Reversion time remaining in seconds.", // + "", // + PointType.UINT32, // + false, // + AccessMode.READ_ONLY, // + Unit.SECONDS, // + null, // + new OptionsEnum[0])), // + RVRT_CRV(new PointImpl(// + "S712_RVRT_CRV", // + "Reversion Curve", // + "Default curve after reversion timeout.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + W_SF(new PointImpl(// + "S712_W_SF", // + "Active Power Scale Factor", // + "Scale factor for curve active power points.", // + "", // + PointType.SUNSSF, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + DEPT_REF_S_F(new PointImpl(// + "S712_DEPT_REF_S_F", // + "Var Scale Factor", // + "Scale factor for curve var points.", // + "", // + PointType.SUNSSF, // + true, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])); // + + protected final PointImpl impl; + + private S712(PointImpl impl) { + this.impl = impl; + } + + @Override + public PointImpl get() { + return this.impl; + } + } + + public static enum S712_Ena implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + DISABLED(0, "DISABLED"), // + ENABLED(1, "ENABLED"); // + + private final int value; + private final String name; + + private S712_Ena(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S712_AdptCrvRslt implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + IN_PROGRESS(0, "IN_PROGRESS"), // + COMPLETED(1, "COMPLETED"), // + FAILED(2, "FAILED"); // + + private final int value; + private final String name; + + private S712_AdptCrvRslt(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S713 implements SunSpecPoint { + W_H_RTG(new PointImpl(// + "S713_W_H_RTG", // + "Energy Rating", // + "Energy rating of the DER storage.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.CUMULATED_WATT_HOURS, // + "WH_SF", // + new OptionsEnum[0])), // + W_H_AVAIL(new PointImpl(// + "S713_W_H_AVAIL", // + "Energy Available", // + "Energy available of the DER storage (WHAvail = WHRtg * SoC * SoH)", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.CUMULATED_WATT_HOURS, // + "WH_SF", // + new OptionsEnum[0])), // + SO_C(new PointImpl(// + "S713_SO_C", // + "State of Charge", // + "State of charge of the DER storage.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + "Pct_SF", // + new OptionsEnum[0])), // + SO_H(new PointImpl(// + "S713_SO_H", // + "State of Health", // + "State of health of the DER storage.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + "Pct_SF", // + new OptionsEnum[0])), // + STA(new PointImpl(// + "S713_STA", // + "Status", // + "Storage status.", // + "", // + PointType.ENUM16, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + S713_Sta.values())), // + WH_SF(new PointImpl(// + "S713_WH_SF", // + "Energy Scale Factor", // + "Scale factor for energy capacity.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + PCT_S_F(new PointImpl(// + "S713_PCT_S_F", // + "Percent Scale Factor", // + "Scale factor for percentage.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])); // + + protected final PointImpl impl; + + private S713(PointImpl impl) { + this.impl = impl; + } + + @Override + public PointImpl get() { + return this.impl; + } + } + + public static enum S713_Sta implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + OK(0, "OK"), // + WARNING(1, "WARNING"), // + ERROR(2, "ERROR"); // + + private final int value; + private final String name; + + private S713_Sta(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S714 implements SunSpecPoint { + PRT_ALRMS(new PointImpl(// + "S714_PRT_ALRMS", // + "Port Alarms", // + "Bitfield of ports with active alarms. Bit is 1 if port has an active alarm. Bit 0 is first port.", // + "", // + PointType.BITFIELD32, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + N_PRT(new PointImpl(// + "S714_N_PRT", // + "Number Of Ports", // + "Number of DC ports.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + DCA(new PointImpl(// + "S714_DCA", // + "DC Current", // + "Total DC current for all ports.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.AMPERE, // + "DCA_SF", // + new OptionsEnum[0])), // + DCW(new PointImpl(// + "S714_DCW", // + "DC Power", // + "Total DC power for all ports.", // + "", // + PointType.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.WATT, // + "DCW_SF", // + new OptionsEnum[0])), // + D_C_WH_INJ(new PointImpl(// + "S714_D_C_WH_INJ", // + "DC Energy Injected", // + "Total cumulative DC energy injected for all ports.", // + "", // + PointType.UINT64, // + false, // + AccessMode.READ_ONLY, // + Unit.CUMULATED_WATT_HOURS, // + "DCWH_SF", // + new OptionsEnum[0])), // + D_C_WH_ABS(new PointImpl(// + "S714_D_C_WH_ABS", // + "DC Energy Absorbed", // + "Total cumulative DC energy absorbed for all ports.", // + "", // + PointType.UINT64, // + false, // + AccessMode.READ_ONLY, // + Unit.CUMULATED_WATT_HOURS, // + "DCWH_SF", // + new OptionsEnum[0])), // + DCA_SF(new PointImpl(// + "S714_DCA_SF", // + "DC Current Scale Factor", // + "DC current scale factor.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + DCV_SF(new PointImpl(// + "S714_DCV_SF", // + "DC Voltage Scale Factor", // + "DC voltage scale factor.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + DCW_SF(new PointImpl(// + "S714_DCW_SF", // + "DC Power Scale Factor", // + "DC power scale factor.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + DCWH_SF(new PointImpl(// + "S714_DCWH_SF", // + "DC Energy Scale Factor", // + "DC energy scale factor.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + TMP_S_F(new PointImpl(// + "S714_TMP_S_F", // + "Temperature Scale Factor", // + "Temperature Scale Factor.", // + "", // + PointType.SUNSSF, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])); // + + protected final PointImpl impl; + + private S714(PointImpl impl) { + this.impl = impl; + } + + @Override + public PointImpl get() { + return this.impl; + } + } + + public static enum S715 implements SunSpecPoint { + LOC_REM_CTL(new PointImpl(// + "S715_LOC_REM_CTL", // + "Control Mode", // + "DER control mode. Enumeration.", // + "", // + PointType.ENUM16, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + S715_LocRemCtl.values())), // + D_E_R_HB(new PointImpl(// + "S715_D_E_R_HB", // + "DER Heartbeat", // + "Value is incremented every second by the DER with periodic resets to zero.", // + "", // + PointType.UINT32, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + CONTROLLER_HB(new PointImpl(// + "S715_CONTROLLER_HB", // + "Controller Heartbeat", // + "Value is incremented every second by the controller with periodic resets to zero.", // + "", // + PointType.UINT32, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + ALARM_RESET(new PointImpl(// + "S715_ALARM_RESET", // + "Alarm Reset", // + "Used to reset any latched alarms. 1 = Reset.", // + "", // + PointType.UINT16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + new OptionsEnum[0])), // + OP_CTL(new PointImpl(// + "S715_OP_CTL", // + "Set Operation", // + "Commands to PCS. Enumerated value.", // + "", // + PointType.ENUM16, // + false, // + AccessMode.READ_WRITE, // + Unit.NONE, // + null, // + S715_OpCtl.values())); // + + protected final PointImpl impl; + + private S715(PointImpl impl) { + this.impl = impl; + } + + @Override + public PointImpl get() { + return this.impl; + } + } + + public static enum S715_LocRemCtl implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + REMOTE(0, "REMOTE"), // + LOCAL(1, "LOCAL"); // + + private final int value; + private final String name; + + private S715_LocRemCtl(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S715_OpCtl implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + STOP(0, "STOP"), // + START(1, "START"), // + ENTER_STANDBY(2, "ENTER_STANDBY"), // + EXIT_STANDBY(3, "EXIT_STANDBY"); // + + private final int value; + private final String name; + + private S715_OpCtl(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + } + + public static enum S801 implements SunSpecPoint { + DEPRECATED(new PointImpl(// "S801_DEPRECATED", // "Deprecated Model", // "This model has been deprecated.", // @@ -10469,7 +14991,7 @@ public static enum S802 implements SunSpecPoint { PointType.UINT16, // true, // AccessMode.READ_ONLY, // - Unit.WATT_HOURS, // + Unit.CUMULATED_WATT_HOURS, // "WHRtg_SF", // new OptionsEnum[0])), // W_CHA_RTE_MAX(new PointImpl(// @@ -10485,7 +15007,7 @@ public static enum S802 implements SunSpecPoint { new OptionsEnum[0])), // W_DIS_CHA_RTE_MAX(new PointImpl(// "S802_W_DIS_CHA_RTE_MAX", // - "Namplate Max Discharge Rate", // + "Nameplate Max Discharge Rate", // "Maximum rate of energy transfer out of the storage device in DC watts.", // "", // PointType.UINT16, // @@ -10541,7 +15063,7 @@ public static enum S802 implements SunSpecPoint { SO_C_RSV_MIN(new PointImpl(// "S802_SO_C_RSV_MIN", // "Min Reserve Percent", // - "Setpoint for maximum reserve for storage as a percentage of the nominal maximum storage.", // + "Setpoint for minimum reserve for storage as a percentage of the nominal maximum storage.", // "", // PointType.UINT16, // false, // @@ -10553,7 +15075,7 @@ public static enum S802 implements SunSpecPoint { "S802_SO_C", // "State of Charge", // "State of charge, expressed as a percentage.", // - "Measurement.", // + "", // PointType.UINT16, // true, // AccessMode.READ_ONLY, // @@ -10564,7 +15086,7 @@ public static enum S802 implements SunSpecPoint { "S802_DO_D", // "Depth of Discharge", // "Depth of discharge, expressed as a percentage.", // - "Measurement.", // + "", // PointType.UINT16, // false, // AccessMode.READ_ONLY, // @@ -10608,7 +15130,7 @@ public static enum S802 implements SunSpecPoint { "S802_LOC_REM_CTL", // "Control Mode", // "Battery control mode. Enumeration.", // - "Maps to DRCC.LocRemCtl in IEC 61850.", // + "", // PointType.ENUM16, // true, // AccessMode.READ_ONLY, // @@ -10641,7 +15163,7 @@ public static enum S802 implements SunSpecPoint { "S802_ALM_RST", // "Alarm Reset", // "Used to reset any latched alarms. 1 = Reset.", // - "Battery should reset to 0 when reset is complete.", // + "", // PointType.UINT16, // true, // AccessMode.READ_WRITE, // @@ -10652,7 +15174,7 @@ public static enum S802 implements SunSpecPoint { "S802_TYP", // "Battery Type", // "Type of battery. Enumeration.", // - "Maps to DBAT.BatTyp in 61850.", // + "", // PointType.ENUM16, // true, // AccessMode.READ_ONLY, // @@ -10663,7 +15185,7 @@ public static enum S802 implements SunSpecPoint { "S802_STATE", // "State of the Battery Bank", // "State of the battery bank. Enumeration.", // - "Must be reconciled with State in IEC 61850.", // + "", // PointType.ENUM16, // true, // AccessMode.READ_ONLY, // @@ -10685,7 +15207,7 @@ public static enum S802 implements SunSpecPoint { "S802_WARR_DT", // "Warranty Date", // "Date the device warranty expires.", // - "Number of days since 1/1/2000.", // + "", // PointType.UINT32, // false, // AccessMode.READ_ONLY, // @@ -10707,7 +15229,7 @@ public static enum S802 implements SunSpecPoint { "S802_EVT2", // "Battery Event 2 Bitfield", // "Alarms and warnings. Bit flags.", // - "Reserved for future use.", // + "", // PointType.BITFIELD32, // true, // AccessMode.READ_ONLY, // @@ -10740,7 +15262,7 @@ public static enum S802 implements SunSpecPoint { "S802_V", // "External Battery Voltage", // "DC Bus Voltage.", // - "Maps to ZBAT.V in IEC 61850.", // + "", // PointType.UINT16, // true, // AccessMode.READ_ONLY, // @@ -10751,7 +15273,7 @@ public static enum S802 implements SunSpecPoint { "S802_V_MAX", // "Max Battery Voltage", // "Instantaneous maximum battery voltage.", // - "If not implemented, must implement AChaMax and ADisChaMax.", // + "", // PointType.UINT16, // false, // AccessMode.READ_ONLY, // @@ -10762,7 +15284,7 @@ public static enum S802 implements SunSpecPoint { "S802_V_MIN", // "Min Battery Voltage", // "Instantaneous minimum battery voltage.", // - "If not implemented, must implement AChaMax and ADisChaMax.", // + "", // PointType.UINT16, // false, // AccessMode.READ_ONLY, // @@ -10773,7 +15295,7 @@ public static enum S802 implements SunSpecPoint { "S802_CELL_V_MAX", // "Max Cell Voltage", // "Maximum voltage for all cells in the bank.", // - "Measurement.", // + "", // PointType.UINT16, // false, // AccessMode.READ_ONLY, // @@ -10806,7 +15328,7 @@ public static enum S802 implements SunSpecPoint { "S802_CELL_V_MIN", // "Min Cell Voltage", // "Minimum voltage for all cells in the bank.", // - "Measurement.", // + "", // PointType.UINT16, // false, // AccessMode.READ_ONLY, // @@ -10839,7 +15361,7 @@ public static enum S802 implements SunSpecPoint { "S802_CELL_V_AVG", // "Average Cell Voltage", // "Average cell voltage for all cells in the bank.", // - "Calculation based on measurements.", // + "", // PointType.UINT16, // false, // AccessMode.READ_ONLY, // @@ -10850,7 +15372,7 @@ public static enum S802 implements SunSpecPoint { "S802_A", // "Total DC Current", // "Total DC current flowing to/from the battery bank.", // - "Measurement.", // + "", // PointType.INT16, // true, // AccessMode.READ_ONLY, // @@ -10861,7 +15383,7 @@ public static enum S802 implements SunSpecPoint { "S802_A_CHA_MAX", // "Max Charge Current", // "Instantaneous maximum DC charge current.", // - "Calculation which is always unsigned (i.e. magnitude only). If not implemented, must implement VMax and VMin.", // + "", // PointType.UINT16, // false, // AccessMode.READ_ONLY, // @@ -10872,7 +15394,7 @@ public static enum S802 implements SunSpecPoint { "S802_A_DIS_CHA_MAX", // "Max Discharge Current", // "Instantaneous maximum DC discharge current.", // - "Calculation which is always unsigned (i.e. magnitude only). If not implemented, must implement VMax and VMin.", // + "", // PointType.UINT16, // false, // AccessMode.READ_ONLY, // @@ -10883,7 +15405,7 @@ public static enum S802 implements SunSpecPoint { "S802_W", // "Total Power", // "Total power flowing to/from the battery bank.", // - "Measurement.", // + "", // PointType.INT16, // true, // AccessMode.READ_ONLY, // @@ -10894,7 +15416,7 @@ public static enum S802 implements SunSpecPoint { "S802_REQ_INV_STATE", // "Inverter State Request", // "Request from battery to start or stop the inverter. Enumeration.", // - "Used in special states such as manual battery charging.", // + "", // PointType.ENUM16, // false, // AccessMode.READ_ONLY, // @@ -10905,7 +15427,7 @@ public static enum S802 implements SunSpecPoint { "S802_REQ_W", // "Battery Power Request", // "AC Power requested by battery.", // - "Used in special states such as string balancing.", // + "", // PointType.INT16, // false, // AccessMode.READ_ONLY, // @@ -10927,7 +15449,7 @@ public static enum S802 implements SunSpecPoint { "S802_SET_INV_STATE", // "Set Inverter State", // "Set the current state of the inverter.", // - "Information needed by battery for some operations.", // + "", // PointType.ENUM16, // true, // AccessMode.READ_WRITE, // @@ -11047,7 +15569,7 @@ public static enum S802 implements SunSpecPoint { A_MAX_S_F(new PointImpl(// "S802_A_MAX_S_F", // "", // - "Scale factor for instantationous DC charge/discharge current.", // + "Scale factor for instantaneous DC charge/discharge current.", // "", // PointType.SUNSSF, // true, // @@ -12634,7 +17156,7 @@ public static enum S64111 implements SunSpecPoint { new OptionsEnum[0])), // TODAY_MIN_BAT_V(new PointImpl(// "S64111_TODAY_MIN_BAT_V", // - "Todays Minimum Battery Voltage", // + "Today's Minimum Battery Voltage", // "", // "", // PointType.UINT16, // @@ -12645,7 +17167,7 @@ public static enum S64111 implements SunSpecPoint { new OptionsEnum[0])), // TODAY_MAX_BAT_V(new PointImpl(// "S64111_TODAY_MAX_BAT_V", // - "Todays Maximum Battery Voltage", // + "Today's Maximum Battery Voltage", // "", // "", // PointType.UINT16, // @@ -12667,7 +17189,7 @@ public static enum S64111 implements SunSpecPoint { new OptionsEnum[0])), // TODAY_MAX_V_O_C(new PointImpl(// "S64111_TODAY_MAX_V_O_C", // - "Todays Maximum VOC", // + "Today's Maximum VOC", // "", // "", // PointType.UINT16, // @@ -12678,7 +17200,7 @@ public static enum S64111 implements SunSpecPoint { new OptionsEnum[0])), // TODAYK_WH_OUTPUT(new PointImpl(// "S64111_TODAYK_WH_OUTPUT", // - "Todays kWh", // + "Today's kWh", // "", // "", // PointType.UINT16, // @@ -12689,7 +17211,7 @@ public static enum S64111 implements SunSpecPoint { new OptionsEnum[0])), // TODAY_A_H_OUTPUT(new PointImpl(// "S64111_TODAY_A_H_OUTPUT", // - "Todays AH", // + "Today's AH", // "", // "", // PointType.UINT16, // @@ -13252,7 +17774,7 @@ public static enum S64112 implements SunSpecPoint { new OptionsEnum[0])), // C_C_CONFIG_A_U_X_NLITE_ON_HIST(new PointImpl(// "S64112_C_C_CONFIG_A_U_X_NLITE_ON_HIST", // - "AUX Night Light On Hysterisis", // + "AUX Night Light On Hysteresis", // "", // "", // PointType.UINT16, // @@ -13263,7 +17785,7 @@ public static enum S64112 implements SunSpecPoint { new OptionsEnum[0])), // C_C_CONFIG_A_U_X_NLITE_OFF_HIST(new PointImpl(// "S64112_C_C_CONFIG_A_U_X_NLITE_OFF_HIST", // - "AUX Night Light Off Hysterisis", // + "AUX Night Light Off Hysteresis", // "", // "", // PointType.UINT16, // @@ -13318,7 +17840,7 @@ public static enum S64112 implements SunSpecPoint { new OptionsEnum[0])), // C_C_CONFIG_A_U_X_DIVERT_HYST_V(new PointImpl(// "S64112_C_C_CONFIG_A_U_X_DIVERT_HYST_V", // - "AUX Divert Hysterisis", // + "AUX Divert Hysteresis", // "", // "", // PointType.UINT16, // @@ -13384,7 +17906,7 @@ public static enum S64112 implements SunSpecPoint { new OptionsEnum[0])), // C_C_CONFIG_DATA_LOG_DAILY_A_H(new PointImpl(// "S64112_C_C_CONFIG_DATA_LOG_DAILY_A_H", // - "Data Log Daily", // + "Data Log Daily (Ah)", // "", // "", // PointType.UINT16, // @@ -13395,7 +17917,7 @@ public static enum S64112 implements SunSpecPoint { new OptionsEnum[0])), // C_C_CONFIG_DATA_LOG_DAILY_K_W_H(new PointImpl(// "S64112_C_C_CONFIG_DATA_LOG_DAILY_K_W_H", // - "Data Log Daily", // + "Data Log Daily (kWh)", // "", // "", // PointType.UINT16, // @@ -13406,7 +17928,7 @@ public static enum S64112 implements SunSpecPoint { new OptionsEnum[0])), // C_C_CONFIG_DATA_LOG_MAX_OUT_A(new PointImpl(// "S64112_C_C_CONFIG_DATA_LOG_MAX_OUT_A", // - "Data Log Daily Maximum Output", // + "Data Log Daily Maximum Output (A)", // "", // "", // PointType.UINT16, // @@ -13417,7 +17939,7 @@ public static enum S64112 implements SunSpecPoint { new OptionsEnum[0])), // C_C_CONFIG_DATA_LOG_MAX_OUT_W(new PointImpl(// "S64112_C_C_CONFIG_DATA_LOG_MAX_OUT_W", // - "Data Log Daily Maximum Output", // + "Data Log Daily Maximum Output (W)", // "", // "", // PointType.UINT16, // diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/SunSpecCodeGenerator.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/SunSpecCodeGenerator.java index f86213d59fe..a9827f5ce18 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/SunSpecCodeGenerator.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/SunSpecCodeGenerator.java @@ -6,109 +6,109 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; +import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.Function; -import javax.xml.parsers.DocumentBuilderFactory; - -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - import com.google.common.base.CaseFormat; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import io.openems.common.channel.AccessMode; import io.openems.common.channel.Unit; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; import io.openems.common.function.ThrowingFunction; -import io.openems.common.utils.XmlUtils; -import io.openems.edge.bridge.modbus.sunspec.SunSpecCodeGenerator.Point.Symbol; +import io.openems.common.utils.JsonUtils; import io.openems.edge.bridge.modbus.sunspec.SunSpecPoint.PointCategory; import io.openems.edge.bridge.modbus.sunspec.SunSpecPoint.PointType; /** - * This tool converts SunSpec XML definitions to Java code suitable for the + * This tool converts SunSpec Json definitions to Java code suitable for the * OpenEMS SunSpec implementation. * *

    - * Download XML files from https://github.com/sunspec/models. + * Download Json files from https://github.com/sunspec/models. */ public class SunSpecCodeGenerator { /** - * Path to the SunSpec model XML files; download them from + * Path to the SunSpec model Json files; download them from * https://github.com/sunspec/models. */ - private static final String SUNSPEC_XML_PATH = System.getProperty("user.home") + "/git/sunspec/smdx/"; + private static final String SUNSPEC_JSON_PATH = System.getProperty("user.home") + "\\git\\sunspec\\json\\"; /** * Path to the generated output file. */ - private static final String OUT_FILE_PATH = System.getProperty("user.home") + "/git/sunspec/smdx/SunSpecModel.java"; + private static final String OUT_FILE_PATH = System.getProperty("user.dir") + + "\\src\\io\\openems\\edge\\bridge\\modbus\\sunspec\\DefaultSunSpecModel.java"; /** - * XML files that should be ignored; mainly because certain features are not + * Json files that should be ignored; mainly because certain features are not * implemented yet. */ private static final Set IGNORE_FILES = new HashSet<>(Arrays.asList(// - "smdx_00003.xml", // - "smdx_00004.xml", // - "smdx_00005.xml", // - "smdx_00006.xml", // - "smdx_00007.xml", // - "smdx_00008.xml", // - "smdx_00009.xml", // - "smdx_00010.xml", // - "smdx_00011.xml", // - "smdx_00012.xml", // - "smdx_00013.xml", // - "smdx_00014.xml", // - "smdx_00016.xml", // - "smdx_00017.xml", // - "smdx_00019.xml", // - "smdx_00126.xml", // - "smdx_00129.xml", // - "smdx_00130.xml", // - "smdx_00131.xml", // - "smdx_00132.xml", // - "smdx_00133.xml", // - "smdx_00134.xml", // - "smdx_00135.xml", // - "smdx_00136.xml", // - "smdx_00137.xml", // - "smdx_00138.xml", // - "smdx_00139.xml", // - "smdx_00140.xml", // - "smdx_00141.xml", // - "smdx_00142.xml", // - "smdx_00143.xml", // - "smdx_00144.xml", // - "smdx_00160.xml", // - "smdx_00211.xml", // - "smdx_00212.xml", // - "smdx_00213.xml", // - "smdx_00214.xml", // - "smdx_00220.xml", // - "smdx_00401.xml", // - "smdx_00402.xml", // - "smdx_00403.xml", // - "smdx_00404.xml", // - "smdx_00501.xml", // - "smdx_00502.xml", // - "smdx_00601.xml", // - "smdx_00803.xml", // - "smdx_00804.xml", // - "smdx_00805.xml", // - "smdx_00806.xml", // - "smdx_00807.xml", // - "smdx_00808.xml", // - "smdx_00809.xml", // - "smdx_63001.xml", // - "smdx_63002.xml", // - "smdx_64020.xml" // + "model_3.json", // + "model_4.json", // + "model_5.json", // + "model_6.json", // + "model_7.json", // + "model_8.json", // + "model_9.json", // + "model_10.json", // + "model_11.json", // + "model_12.json", // + "model_13.json", // + "model_14.json", // + "model_16.json", // + "model_17.json", // + "model_19.json", // + "model_126.json", // + "model_129.json", // + "model_130.json", // + "model_131.json", // + "model_132.json", // + "model_133.json", // + "model_134.json", // + "model_135.json", // + "model_136.json", // + "model_137.json", // + "model_138.json", // + "model_139.json", // + "model_140.json", // + "model_141.json", // + "model_142.json", // + "model_143.json", // + "model_144.json", // + "model_160.json", // + "model_211.json", // + "model_212.json", // + "model_213.json", // + "model_214.json", // + "model_220.json", // + "model_302.json", // + "model_303.json", // + "model_304.json", // + "model_401.json", // + "model_402.json", // + "model_403.json", // + "model_404.json", // + "model_501.json", // + "model_502.json", // + "model_601.json", // + "model_803.json", // + "model_804.json", // + "model_805.json", // + "model_806.json", // + "model_807.json", // + "model_808.json", // + "model_809.json", // + "model_63001.json", // + "model_63002.json", // + "model_64020.json" // )); /** @@ -118,49 +118,55 @@ public class SunSpecCodeGenerator { * @throws Exception on error */ public static void main(String[] args) throws Exception { + System.out.println(SUNSPEC_JSON_PATH); var generator = new SunSpecCodeGenerator(); - var models = generator.parseSunSpecXmlFiles(); + var models = generator.parseSunSpecFiles(); generator.writeSunSpecModelJavaFile(models); } /** - * Parses all SunSpec XML files in a directory. + * Parses all SunSpec Json files in a directory. * * @return a list of Models * @throws Exception on error */ - private List parseSunSpecXmlFiles() throws Exception { + private List parseSunSpecFiles() throws Exception { List result = new ArrayList<>(); - for (File file : new File(SUNSPEC_XML_PATH).listFiles(file -> // - file.getName().startsWith("smdx_") // - && file.getName().endsWith(".xml") // + for (File file : new File(SUNSPEC_JSON_PATH).listFiles(file -> // + file.getName().startsWith("model") // + && file.getName().endsWith(".json") // && !IGNORE_FILES.contains(file.getName()))) { try { - var model = this.parseSunSpecXmlFile(file); + var model = this.parseSunSpecFile(Files.readString(file.toPath())); result.add(model); } catch (Exception e) { throw new Exception("Error while reading from " + file, e); } } + // Sort by model ids to have an ordered output + result.sort(new Comparator() { + @Override + public int compare(Model m1, Model m2) { + return m1.id - m2.id; + } + }); + return result; } /** - * Parses a SunSpec XML file. + * Parses a SunSpec Json file. * - * @param file the SunSpec XML file handler + * @param file the SunSpec Json file handler * @return the Model * @throws Exception on error */ - private Model parseSunSpecXmlFile(File file) throws Exception { - var dbFactory = DocumentBuilderFactory.newInstance(); - var dBuilder = dbFactory.newDocumentBuilder(); - var doc = dBuilder.parse(file); - doc.getDocumentElement().normalize(); + private Model parseSunSpecFile(String file) throws Exception { + var json = JsonUtils.parseToJsonObject(file); var generator = new SunSpecCodeGenerator(); - return generator.parseSunSpecModels(doc.getDocumentElement()); + return generator.parseSunSpecModels(json); } /** @@ -174,417 +180,13 @@ private Model parseSunSpecXmlFile(File file) throws Exception { *

  • xs:attribute name="v" type="xs:string" default="1" * * - * @param sunSpecModelsElement the 'sunSpecModels' node - * @return the Model - * @throws OpenemsNamedException on error - */ - private Model parseSunSpecModels(Element sunSpecModelsElement) throws OpenemsNamedException { - // parse all "model" XML elements - var modelNodes = sunSpecModelsElement.getElementsByTagName("model"); - var modelNode = this.assertExactlyOneNode(modelNodes, node -> node.getAttributes().getLength() != 0); - var model = this.parseModel(modelNode); - - // parse all "strings" XML elements - var stringsNodes = sunSpecModelsElement.getElementsByTagName("strings"); - var stringsNode = this.assertExactlyOneNode(stringsNodes); - this.parseStrings(stringsNode, model); - - return model; - } - - /** - * Parses the element sunSpecModels -> model. - * - *
    -	 *   <model id="1" len="66" name="common">
    -	 * 
    - * - *
      - *
    • xs:attribute name="id" type="xs:integer" - *
    • xs:attribute name="len" type="xs:integer" - *
    - * - * @param node the 'model' node + * @param sunSpecModels the 'sunSpecModels' json * @return the Model * @throws OpenemsNamedException on error + * @throws JSONException on json error */ - private Model parseModel(Node node) throws OpenemsNamedException { - // read attributes - var attrs = node.getAttributes(); - var id = XmlUtils.getAsInt(attrs, "id"); - var len = XmlUtils.getAsInt(attrs, "len"); - var name = XmlUtils.getAsStringOrElse(attrs, "name", ""); - - // read points - var element = (Element) node; - var blockNodes = element.getElementsByTagName("block"); - var blockNode = this.assertExactlyOneNode(blockNodes); - var points = this.parseModelBlock(blockNode); - - return new Model(id, len, name, points); - } - - /** - * Parses the element sunSpecModels -> model -> block. - * - *
    -	 *   <block len="66">
    -	 * 
    - * - *
      - *
    • xs:attribute name="id" type="xs:integer" - *
    • xs:attribute name="len" type="xs:integer" - *
    - * - * @param node the 'block' node - * @return a list Points - * @throws OpenemsNamedException on error - */ - private List parseModelBlock(Node node) throws OpenemsNamedException { - // TODO implement "repeating" blocks - List points = new ArrayList<>(); - var element = (Element) node; - var pointNodes = element.getElementsByTagName("point"); - for (var i = 0; i < pointNodes.getLength(); i++) { - var pointNode = pointNodes.item(i); - points.add(this.parseModelBlockPoint(pointNode)); - } - return points; - } - - /** - * Parses the element sunSpecModels -> model -> block -> point. - * - *
    -	 *   <point id="Mn" offset="0" type="string" len="16" mandatory="true" />
    -	 * 
    - * - *
      - *
    • xs:attribute name="id" type="xs:string" use="required" - *
    • xs:attribute name="len" type="xs:integer" - *
    • xs:attribute name="offset" type="xs:integer" - *
    • xs:attribute name="type" type="PointTypeDefinition" - *
    • xs:attribute name="sf" type="xs:string" - *
    • xs:attribute name="units" type="xs:string" - *
    • xs:attribute name="access" type="PointAccessDefinition" default="r" - *
    • xs:attribute name="mandatory" type="xs:boolean" default="false" - *
    • xs:attribute name="category" type="CategoryDefinition" - * default="measurement" - *
    - * - * @param node the 'point' node. - * @return the Point - * @throws OpenemsNamedException on error - */ - private Point parseModelBlockPoint(Node node) throws OpenemsNamedException { - var attrs = node.getAttributes(); - - int len; - PointType type; - var typeString = XmlUtils.getAsString(attrs, "type"); - if (typeString.equals("string")) { - len = XmlUtils.getAsInt(attrs, "len"); - type = PointType.valueOf("STRING" + len); - } else { - type = XmlUtils.getAsEnum(PointType.class, attrs, "type"); - len = type.length; - } - - var scaleFactor = XmlUtils.getAsStringOrElse(attrs, "sf", null); - var unitString = XmlUtils.getAsStringOrElse(attrs, "units", ""); - final ThrowingFunction toUnit = s -> { - s = s.trim(); - if (s.contains(" ")) { - s = s.substring(0, s.indexOf(" ")); - } - switch (s) { - case "": - case "%ARtg/%dV": - case "bps": // not available in OpenEMS - case "cos()": // not available in OpenEMS - case "deg": // not available in OpenEMS - case "Degrees": // not available in OpenEMS - case "hhmmss": // not available in OpenEMS - case "hhmmss.sssZ": // not available in OpenEMS - case "HPa": // not available in OpenEMS - case "kO": // not available in OpenEMS - case "Mbps": // not available in OpenEMS - case "meters": // not available in OpenEMS - case "mm": // not available in OpenEMS - case "mps": // not available in OpenEMS - case "m/s": // not available in OpenEMS - case "ohms": // not available in OpenEMS - case "Pct": // not available in OpenEMS - case "PF": // not available in OpenEMS - case "SF": // not available in OpenEMS - case "text": // not available in OpenEMS - case "Tmd": // not available in OpenEMS - case "Tmh": // not available in OpenEMS - case "Tms": // not available in OpenEMS - case "Various": // not available in OpenEMS - case "Vm": // not available in OpenEMS - case "W/m2": // not available in OpenEMS - case "YYYYMMDD": // not available in OpenEMS - return Unit.NONE; - case "%": - case "%WHRtg": - return Unit.PERCENT; - case "A": - return Unit.AMPERE; - case "Ah": - case "AH": - return Unit.AMPERE_HOURS; - case "C": - return Unit.DEGREE_CELSIUS; - case "Hz": - return Unit.HERTZ; - case "kAH": - return Unit.KILOAMPERE_HOURS; - case "kWh": - return Unit.KILOWATT_HOURS; - case "mSecs": - return Unit.MILLISECONDS; - case "Secs": - return Unit.SECONDS; - case "V": - return Unit.VOLT; - case "VA": - return Unit.VOLT_AMPERE; - case "VAh": - return Unit.VOLT_AMPERE_HOURS; - case "var": - return Unit.VOLT_AMPERE_REACTIVE; - case "varh": - return Unit.VOLT_AMPERE_REACTIVE_HOURS; - case "W": - return Unit.WATT; - case "Wh": - // Validate manually: OpenEMS distinguishes CUMULATED and DISCRETE Watt-Hours. - return Unit.CUMULATED_WATT_HOURS; - } - throw new OpenemsException("Unhandled unit [" + s + "]"); - }; - var unit = toUnit.apply(unitString); - - var accessModeString = XmlUtils.getAsStringOrElse(attrs, "access", "r"); - AccessMode accessMode; - switch (accessModeString.toLowerCase()) { - case "wo": - accessMode = AccessMode.WRITE_ONLY; - break; - case "rw": - accessMode = AccessMode.READ_WRITE; - break; - case "r": - case "ro": - default: - accessMode = AccessMode.READ_ONLY; - break; - } - var mandatory = XmlUtils.getAsBooleanOrElse(attrs, "mandatory", false); - var category = XmlUtils.getAsEnumOrElse(PointCategory.class, attrs, "category", PointCategory.MEASUREMENT); - - // read symbols - var element = (Element) node; - var symbolNodes = element.getElementsByTagName("symbol"); - Symbol[] symbols; - if (symbolNodes.getLength() > 0) { - symbols = new Symbol[symbolNodes.getLength()]; - for (var i = 0; i < symbolNodes.getLength(); i++) { - var symbolNode = symbolNodes.item(i); - symbols[i] = this.parseModelBlockPointSymbol(symbolNode); - } - } else { - symbols = new Symbol[0]; - } - - var id = XmlUtils.getAsString(attrs, "id"); - var offset = XmlUtils.getAsInt(attrs, "offset"); - - return new Point(id, len, offset, type, scaleFactor, unit, accessMode, mandatory, category, symbols); - } - - /** - * Parses the element sunSpecModels -> model -> block -> point -> - * symbol. - * - *
    -	 *   <symbol id="OFF">1<symbol>
    -	 * 
    - * - *
      - *
    • xs:attribute name="id" type="xs:string" use="required" - *
    - * - * @param node the 'symbol' node - * @return the Symbol - * @throws OpenemsNamedException on error - */ - private Symbol parseModelBlockPointSymbol(Node node) throws OpenemsNamedException { - var attrs = node.getAttributes(); - var id = XmlUtils.getAsString(attrs, "id"); - var value = XmlUtils.getContentAsInt(node); - return new Symbol(id, value); - } - - /** - * Parses the element sunSpecModels -> strings. - * - *
    -	 *   <strings id="1" locale="en">
    -	 * 
    - * - *
      - *
    • xs:attribute name="id" type="xs:integer" use="required" - *
    • xs:attribute name="locale" type="xs:string" - *
    - * - * @param node the 'strings' node - * @param model the Model, that needs to be completed. - * @throws OpenemsNamedException on error - */ - @SuppressWarnings("unused") - private void parseStrings(Node node, Model model) throws OpenemsNamedException { - // read attributes - var attrs = node.getAttributes(); - var id = XmlUtils.getAsInt(attrs, "id"); - var locale = XmlUtils.getAsString(attrs, "locale"); - - if (model.id != id) { - throw new OpenemsException("Model-IDs are not matching"); - } - - var element = (Element) node; - - // read model - var modelNodes = element.getElementsByTagName("model"); - var modelNode = this.assertExactlyOneNode(modelNodes); - this.parseStringsModel(modelNode, model); - - // read points - var pointNodes = element.getElementsByTagName("point"); - for (var i = 0; i < pointNodes.getLength(); i++) { - var pointNode = pointNodes.item(i); - this.parseStringsPoint(pointNode, model); - } - } - - /** - * Parses the element sunSpecModels -> strings -> model. - * - *
    -	 *   <model>
    -	 * 
    - * - * @param node the 'model' node. - * @param model the Model, that needs to be completed. - * @throws OpenemsNamedException on error - */ - private void parseStringsModel(Node node, Model model) throws OpenemsNamedException { - model.label = this.getTextContent(node, "label"); - model.description = this.getTextContent(node, "description"); - model.notes = this.getTextContent(node, "notes"); - } - - /** - * Parses the element sunSpecModels -> strings -> point. - * - *
    -	 *   <point>
    -	 * 
    - * - * @param node the 'point' node - * @param model the Model, that needs to be completed. - * @throws OpenemsNamedException on error - */ - private void parseStringsPoint(Node node, Model model) throws OpenemsNamedException { - var attrs = node.getAttributes(); - var id = XmlUtils.getAsString(attrs, "id"); - switch (id) { - case "VArWMaxPct_SF": - case "TotVArhExpQ4Ph": - case "PPVphAB": - case "PPVphBC": - case "PPVphCA": - case "Pad1": - case "Pad": - // Special handling for IDs 123, 201, 202, 801, 802 - // TODO: create pull-request to fix XML file upstream - return; - } - - var point = model.getPoint(id); - - var element = (Element) node; - var subNodes = element.getChildNodes(); - for (var i = 0; i < subNodes.getLength(); i++) { - var subNode = subNodes.item(i); - switch (subNode.getNodeName()) { - case "label": - point.label = XmlUtils.getContentAsString(subNode); - break; - case "description": - point.description = XmlUtils.getContentAsString(subNode); - break; - case "notes": - point.notes = XmlUtils.getContentAsString(subNode); - break; - case "symbol": - this.parseStringsPointSymbol(subNode, point); - break; - case "#text": - // ignore - break; - default: - throw new OpenemsException("Unable to handle " + subNode.getNodeName()); - } - } - } - - /** - * Parses the element sunSpecModels -> strings -> point -> symbol. - * - *
    -	 *   <point>
    -	 * 
    - * - * @param node the 'symbol' node - * @param point the Model, that needs to be completed. - * @throws OpenemsNamedException on error - */ - private void parseStringsPointSymbol(Node node, Point point) throws OpenemsNamedException { - var attrs = node.getAttributes(); - var id = XmlUtils.getAsString(attrs, "id"); - switch (id) { - case "OEM16": - // Special handling for ID 201 - // TODO: create pull-request to fix XML file upstream - return; - } - - var symbol = point.getSymbol(id); - - var element = (Element) node; - var subNodes = element.getChildNodes(); - for (var i = 0; i < subNodes.getLength(); i++) { - var subNode = subNodes.item(i); - switch (subNode.getNodeName()) { - case "label": - symbol.label = XmlUtils.getContentAsString(subNode); - break; - case "description": - symbol.description = XmlUtils.getContentAsString(subNode); - break; - case "notes": - symbol.notes = XmlUtils.getContentAsString(subNode); - break; - case "#text": - // ignore - break; - default: - throw new OpenemsException("Unable to handle " + subNode.getNodeName()); - } - } + private Model parseSunSpecModels(JsonObject sunSpecModels) throws OpenemsNamedException { + return new Model(sunSpecModels); } /** @@ -595,6 +197,9 @@ private void parseStringsPointSymbol(Node node, Point point) throws OpenemsNamed */ private void writeSunSpecModelJavaFile(List models) throws IOException { try (var w = Files.newBufferedWriter(Paths.get(OUT_FILE_PATH))) { + w.write("// CHECKSTYLE:OFF"); + w.newLine(); + w.newLine(); w.write("package io.openems.edge.bridge.modbus.sunspec;"); w.newLine(); w.newLine(); @@ -611,7 +216,7 @@ private void writeSunSpecModelJavaFile(List models) throws IOException { w.newLine(); w.write(" */"); w.newLine(); - w.write("public enum SunSpecModel {"); + w.write("public enum DefaultSunSpecModel implements SunSpecModel {"); w.newLine(); /* @@ -629,7 +234,7 @@ private void writeSunSpecModelJavaFile(List models) throws IOException { w.newLine(); w.write(" " + model.len + ", //"); w.newLine(); - w.write(" SunSpecModel.S" + model.id + ".values(), //"); + w.write(" DefaultSunSpecModel.S" + model.id + ".values(), //"); w.newLine(); w.write(" SunSpecModelType." + model.modelType + " //"); w.newLine(); @@ -759,7 +364,7 @@ private void writeSunSpecModelJavaFile(List models) throws IOException { w.newLine(); w.write(" public int getValue() {"); w.newLine(); - w.write(" return value;"); + w.write(" return this.value;"); w.newLine(); w.write(" }"); w.newLine(); @@ -768,7 +373,7 @@ private void writeSunSpecModelJavaFile(List models) throws IOException { w.newLine(); w.write(" public String getName() {"); w.newLine(); - w.write(" return name;"); + w.write(" return this.name;"); w.newLine(); w.write(" }"); w.newLine(); @@ -800,7 +405,7 @@ private void writeSunSpecModelJavaFile(List models) throws IOException { w.write(" public final SunSpecModelType modelType;"); w.newLine(); w.newLine(); - w.write(" private SunSpecModel(String label, String description, String notes, int length, SunSpecPoint[] points,"); + w.write(" private DefaultSunSpecModel(String label, String description, String notes, int length, SunSpecPoint[] points,"); w.newLine(); w.write(" SunSpecModelType modelType) {"); w.newLine(); @@ -818,8 +423,28 @@ private void writeSunSpecModelJavaFile(List models) throws IOException { w.newLine(); w.write(" }"); w.newLine(); + w.newLine(); + w.write(" @Override"); + w.newLine(); + w.write(" public SunSpecPoint[] points() {"); + w.newLine(); + w.write(" return this.points;"); + w.newLine(); + w.write(" }"); + w.newLine(); + w.newLine(); + w.write(" @Override"); + w.newLine(); + w.write(" public String label() {"); + w.newLine(); + w.write(" return this.label;"); + w.newLine(); + w.write(" }"); + w.newLine(); w.write("}"); w.newLine(); + w.write("// CHECKSTYLE:ON"); + w.newLine(); } } @@ -839,53 +464,6 @@ private static final String esc(String string) { .trim(); } - /** - * Throws an exception if the list does not have exactly one Node that matches - * the filters. Returns that node otherwise. - * - * @param nodes the list of nodes - * @param filters the filters that need to be matched by the node - * @return the Node - * @throws IllegalArgumentException if not exactly one matching Node was found - */ - @SafeVarargs - private final Node assertExactlyOneNode(NodeList nodes, Function... filters) { - if (nodes.getLength() == 1) { - return nodes.item(0); - } - Node result = null; - for (var i = 0; i < nodes.getLength(); i++) { - var node = nodes.item(i); - for (Function filter : filters) { - if (filter.apply(node)) { - if (result != null) { - throw new IllegalArgumentException("Exactly one node matching the filters was expected!"); - } - result = node; - } - } - } - - if (result != null) { - return result; - } - throw new IllegalArgumentException("Exactly one node matching the filters was expected!"); - } - - /** - * Gets the Content of a Sub-Node. - * - * @param node the Node - * @param tagName the tag name of the Sub-Node - * @return the Content as a String - */ - private String getTextContent(Node node, String tagName) { - var element = (Element) node; - var nodes = element.getElementsByTagName(tagName); - var subNode = this.assertExactlyOneNode(nodes); - return subNode.getTextContent(); - } - /** * POJO container for a SunSpec Model. */ @@ -900,12 +478,27 @@ public static class Model { protected String description = ""; protected String notes = ""; - public Model(int id, int len, String name, List points) { - this.id = id; - this.len = len; - this.name = name; - this.points = points; - this.modelType = SunSpecModelType.getModelType(id); + public Model(JsonObject model) throws OpenemsNamedException { + this.id = JsonUtils.getAsInt(model, "id"); + var group = model.get("group").getAsJsonObject(); + this.name = JsonUtils.getAsString(group, "name"); + this.label = JsonUtils.getAsStringOrElse(group, "label", ""); + this.description = JsonUtils.getAsStringOrElse(group, "desc", ""); + var points = JsonUtils.getAsJsonArray(group, "points"); + + var list = new ArrayList(); + var offset = 0; + for (var i = 0; i < points.size(); i++) { + var p = new Point(points.get(i).getAsJsonObject(), offset); + // ID and length not to be considered as points + if (!p.id.equals("ID") && !p.id.equals("L")) { + list.add(p); + } + offset += p.len; + } + this.points = list; + this.len = this.points.stream().map(p -> p.len).reduce(0, (t, p) -> t + p); + this.modelType = SunSpecModelType.getModelType(this.id); } /** @@ -926,8 +519,8 @@ public Point getPoint(String id) throws OpenemsException { @Override public String toString() { - return "Model [id=" + this.id + ", len=" + this.len + ", name=" + this.name + ", points=" + this.points - + ", label=" + this.label + ", description=" + this.description + ", notes=" + this.notes + "]"; + return "Model [id=" + this.id + ", name=" + this.name + ", points=" + this.points + ", label=" + this.label + + ", description=" + this.description + ", notes=" + this.notes + "]"; } } @@ -952,18 +545,137 @@ public static class Point { protected String description; protected String notes; - public Point(String id, int len, int offset, PointType type, String scaleFactor, Unit unit, - AccessMode accessMode, boolean mandatory, PointCategory category, Symbol[] symbols) { - this.id = id; - this.len = len; + public Point(JsonObject point, int offset) throws OpenemsNamedException { + this.id = JsonUtils.getAsString(point, "name"); + this.len = JsonUtils.getAsInt(point, "size"); + this.label = JsonUtils.getAsStringOrElse(point, "label", ""); + this.description = JsonUtils.getAsStringOrElse(point, "desc", ""); this.offset = offset; - this.type = type; - this.scaleFactor = Optional.ofNullable(scaleFactor); - this.unit = unit; - this.accessMode = accessMode; - this.mandatory = mandatory; - this.category = category; + var t = JsonUtils.getAsString(point, "type"); + if (t.equals("string")) { + this.type = PointType.valueOf("STRING" + this.len); + } else { + this.type = PointType.valueOf(t.toUpperCase()); + } + var sf = JsonUtils.getAsOptionalPrimitive(point, "sf"); + if (sf.isPresent()) { + this.scaleFactor = Optional.of(sf.get().getAsString()); + } else { + this.scaleFactor = Optional.empty(); + } + this.unit = toUnit(JsonUtils.getAsStringOrElse(point, "units", "")); + var access = JsonUtils.getAsStringOrElse(point, "access", "r"); + switch (access.toLowerCase()) { + case "wo": + this.accessMode = AccessMode.WRITE_ONLY; + break; + case "rw": + this.accessMode = AccessMode.READ_WRITE; + break; + case "r": + case "ro": + default: + this.accessMode = AccessMode.READ_ONLY; + break; + } + this.mandatory = JsonUtils.getAsOptionalString(point, "mandatory").isPresent(); + this.category = PointCategory.MEASUREMENT; + + var symbolsJsonOpt = JsonUtils.getAsOptionalJsonArray(point, "symbols"); + Symbol[] symbols; + if (symbolsJsonOpt.isPresent()) { + var symbolsJson = symbolsJsonOpt.get(); + symbols = new Symbol[symbolsJson.size()]; + for (var i = 0; i < symbolsJson.size(); i++) { + var symbol = symbolsJson.get(i); + symbols[i] = new Symbol(symbol); + } + } else { + symbols = new Symbol[0]; + } this.symbols = symbols; + + } + + static Unit toUnit(String unit) throws OpenemsNamedException { + final ThrowingFunction toUnit = s -> { + s = s.trim(); + if (s.contains(" ")) { + s = s.substring(0, s.indexOf(" ")); + } + switch (s) { + case "": + case "%ARtg/%dV": + case "bps": // not available in OpenEMS + case "cos()": // not available in OpenEMS + case "deg": // not available in OpenEMS + case "Degrees": // not available in OpenEMS + case "hhmmss": // not available in OpenEMS + case "hhmmss.sssZ": // not available in OpenEMS + case "HPa": // not available in OpenEMS + case "kO": // not available in OpenEMS + case "Mbps": // not available in OpenEMS + case "meters": // not available in OpenEMS + case "mm": // not available in OpenEMS + case "mps": // not available in OpenEMS + case "m/s": // not available in OpenEMS + case "ohms": // not available in OpenEMS + case "Pct": // not available in OpenEMS + case "PF": // not available in OpenEMS + case "SF": // not available in OpenEMS + case "text": // not available in OpenEMS + case "Tmd": // not available in OpenEMS + case "Tmh": // not available in OpenEMS + case "Tms": // not available in OpenEMS + case "Various": // not available in OpenEMS + case "Vm": // not available in OpenEMS + case "W/m2": // not available in OpenEMS + case "YYYYMMDD": // not available in OpenEMS + case "S": // not available in OpenEMS + case "%Max/Sec": // not available in OpenEMS + return Unit.NONE; + case "%": + case "%WHRtg": + return Unit.PERCENT; + case "A": + return Unit.AMPERE; + case "Ah": + case "AH": + return Unit.AMPERE_HOURS; + case "C": + return Unit.DEGREE_CELSIUS; + case "Hz": + return Unit.HERTZ; + case "kAH": + return Unit.KILOAMPERE_HOURS; + case "kWh": + return Unit.KILOWATT_HOURS; + case "mSecs": + return Unit.MILLISECONDS; + case "Secs": + return Unit.SECONDS; + case "V": + return Unit.VOLT; + case "VA": + return Unit.VOLT_AMPERE; + case "VAh": + return Unit.VOLT_AMPERE_HOURS; + case "var": + case "Var": + return Unit.VOLT_AMPERE_REACTIVE; + case "varh": + case "Varh": + return Unit.VOLT_AMPERE_REACTIVE_HOURS; + case "W": + return Unit.WATT; + case "Wh": + case "WH": + // Validate manually: OpenEMS distinguishes CUMULATED and DISCRETE Watt-Hours. + return Unit.CUMULATED_WATT_HOURS; + } + throw new OpenemsException("Unhandled unit [" + s + "]"); + }; + return toUnit.apply(unit); } /** @@ -1012,7 +724,7 @@ public static class Symbol { case "ggFAULT": case "ggSTANDBY": // Special handling for ID 111 point "Operating State" - // TODO: create pull-request to fix XML file upstream + // TODO: create pull-request to fix Json file upstream return id.substring(2); case "M_EVENT_Power_Failure": case "M_EVENT_Under_Voltage": @@ -1054,6 +766,11 @@ protected Symbol(String id, int value) { this.id = idCleaner.apply(id); this.value = value; } + + public Symbol(JsonElement symbol) throws OpenemsNamedException { + this(JsonUtils.getAsString(symbol, "name"), JsonUtils.getAsInt(symbol, "value")); + this.label = JsonUtils.getAsStringOrElse(symbol, "label", ""); + } } } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/SunSpecModel.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/SunSpecModel.java index 11f61431a20..5712bf7afec 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/SunSpecModel.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/SunSpecModel.java @@ -27,14 +27,14 @@ public default int getBlockId() { /** * The Label. - * + * * @return the Label */ public String label(); /** * The Points. - * + * * @return an array of {@link SunSpecPoint}s */ public SunSpecPoint[] points(); diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/SunSpecPoint.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/SunSpecPoint.java index 0a0846cb463..5d6affc4cde 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/SunSpecPoint.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/SunSpecPoint.java @@ -8,6 +8,7 @@ import io.openems.common.types.OptionsEnum; import io.openems.edge.bridge.modbus.api.element.DummyRegisterElement; import io.openems.edge.bridge.modbus.api.element.FloatDoublewordElement; +import io.openems.edge.bridge.modbus.api.element.FloatQuadruplewordElement; import io.openems.edge.bridge.modbus.api.element.ModbusElement; import io.openems.edge.bridge.modbus.api.element.SignedDoublewordElement; import io.openems.edge.bridge.modbus.api.element.SignedQuadruplewordElement; @@ -100,69 +101,40 @@ public PointImpl(String channelId, String label, String description, String note } /** - * Generates a {@link ModbusElement} for the given point + startAddress. + * Generates a Modbus Element for the given point + startAddress. * * @param startAddress the startAddress of the Point * @return a new Modbus Element */ public final ModbusElement generateModbusElement(Integer startAddress) { - switch (this.type) { - case UINT16: - case ACC16: - case ENUM16: - case BITFIELD16: - return new UnsignedWordElement(startAddress); - case INT16: - case SUNSSF: - case COUNT: - return new SignedWordElement(startAddress); - case UINT32: - case ACC32: - case ENUM32: - case BITFIELD32: - case IPADDR: - return new UnsignedDoublewordElement(startAddress); - case INT32: - return new SignedDoublewordElement(startAddress); - case UINT64: - case ACC64: - return new UnsignedQuadruplewordElement(startAddress); - case INT64: - return new SignedQuadruplewordElement(startAddress); - case FLOAT32: - return new FloatDoublewordElement(startAddress); - case PAD: - return new DummyRegisterElement(startAddress); - case FLOAT64: - break; - case EUI48: - break; - case IPV6ADDR: + return switch (this.type) { + case UINT16, ACC16, ENUM16, BITFIELD16 -> new UnsignedWordElement(startAddress); + case INT16, SUNSSF, COUNT -> new SignedWordElement(startAddress); + case UINT32, ACC32, ENUM32, BITFIELD32, IPADDR -> new UnsignedDoublewordElement(startAddress); + case INT32 -> new SignedDoublewordElement(startAddress); + case UINT64, ACC64 -> new UnsignedQuadruplewordElement(startAddress); + case INT64 -> new SignedQuadruplewordElement(startAddress); + case FLOAT32 -> new FloatDoublewordElement(startAddress); + case PAD -> new DummyRegisterElement(startAddress); + case FLOAT64 -> new FloatQuadruplewordElement(startAddress); + case EUI48 -> null; + case IPV6ADDR // TODO this would be UINT128 - break; - case STRING2: - return new StringWordElement(startAddress, 2); - case STRING4: - return new StringWordElement(startAddress, 4); - case STRING5: - return new StringWordElement(startAddress, 5); - case STRING6: - return new StringWordElement(startAddress, 6); - case STRING7: - return new StringWordElement(startAddress, 7); - case STRING8: - return new StringWordElement(startAddress, 8); - case STRING12: - return new StringWordElement(startAddress, 12); - case STRING16: - return new StringWordElement(startAddress, 16); - case STRING20: - return new StringWordElement(startAddress, 20); - case STRING25: - return new StringWordElement(startAddress, 25); - } - throw new IllegalArgumentException( + -> null; + case STRING2 -> new StringWordElement(startAddress, 2); + case STRING4 -> new StringWordElement(startAddress, 4); + case STRING5 -> new StringWordElement(startAddress, 5); + case STRING6 -> new StringWordElement(startAddress, 6); + case STRING7 -> new StringWordElement(startAddress, 7); + case STRING8 -> new StringWordElement(startAddress, 8); + case STRING12 -> new StringWordElement(startAddress, 12); + case STRING16 -> new StringWordElement(startAddress, 16); + case STRING20 -> new StringWordElement(startAddress, 20); + case STRING25 -> new StringWordElement(startAddress, 25); + case STRING32 -> new StringWordElement(startAddress, 32); + default -> throw new IllegalArgumentException( "Point [" + this.label + "]: Type [" + this.type + "] is not supported!"); + }; } /** @@ -179,43 +151,19 @@ public final OpenemsType getMatchingOpenemsType(boolean hasScaleFactor) { } // TODO: map to floating point OpenemsType when appropriate - switch (this.type) { - case UINT16: - case ACC16: - case ENUM16: - case BITFIELD16: - case INT16: - case SUNSSF: - case COUNT: - case INT32: - case PAD: // ignore - case EUI48: - case FLOAT32: // avoid floating point numbers; FLOAT32 might not fit in INTEGER - return OpenemsType.INTEGER; - case ACC32: - case BITFIELD32: - case ENUM32: - case IPADDR: - case UINT32: - case UINT64: - case ACC64: - case INT64: - case IPV6ADDR: - case FLOAT64: // avoid floating point numbers - return OpenemsType.LONG; - case STRING2: - case STRING4: - case STRING5: - case STRING6: - case STRING7: - case STRING8: - case STRING12: - case STRING16: - case STRING20: - case STRING25: - return OpenemsType.STRING; - } - throw new IllegalArgumentException("Unable to get matching OpenemsType for " + this.type); + return switch (this.type) { + case UINT16, ACC16, ENUM16, BITFIELD16, INT16, SUNSSF, COUNT, INT32, PAD, // ignore + EUI48, FLOAT32 // avoid floating point numbers; FLOAT32 might not fit in INTEGER + -> OpenemsType.INTEGER; + case ACC32, BITFIELD32, ENUM32, IPADDR, UINT32, UINT64, ACC64, INT64, IPV6ADDR, // + FLOAT64 // avoid floating point numbers + -> OpenemsType.LONG; + case STRING2, STRING4, STRING5, STRING6, STRING7, STRING8, STRING12, STRING16, STRING20, STRING25, + STRING32 -> + OpenemsType.STRING; + default -> throw new IllegalArgumentException("Unable to get matching OpenemsType for " + this.type); + + }; } } @@ -223,6 +171,7 @@ public static enum PointType { INT16(1), UINT16(1), COUNT(1), ACC16(1), INT32(2), UINT32(2), FLOAT32(2), ACC32(2), INT64(4), UINT64(4), FLOAT64(4), ACC64(4), ENUM16(1), ENUM32(2), BITFIELD16(1), BITFIELD32(2), SUNSSF(1), STRING2(2), STRING4(4), STRING5(5), STRING6(6), STRING7(7), STRING8(8), STRING12(12), STRING16(16), STRING20(20), STRING25(25), + STRING32(32), /* use PAD for reserved points */ PAD(1), IPADDR(1), IPV6ADDR(16), EUI48(6); @@ -243,53 +192,25 @@ public static boolean isDefined(PointType type, Object value) { if (value == null) { return false; } - switch (type) { - case INT16: - case SUNSSF: - return !value.equals(Short.MIN_VALUE /* -32768 */); - case UINT16: - case ENUM16: - case BITFIELD16: - case COUNT: - return !value.equals(65535); - case ACC16: - case ACC32: - case IPADDR: - case ACC64: - case IPV6ADDR: - return !value.equals(0); - case INT32: - return !value.equals(0x80000000); // TODO correct? - case UINT32: - case ENUM32: - case BITFIELD32: - return !value.equals(4294967295L); - case INT64: - return !value.equals(0x8000000000000000L); // TODO correct? - case UINT64: - return !value.equals(0xFFFFFFFFFFFFFFFFL); // TODO correct? - case FLOAT32: - return !value.equals(Float.NaN); - case FLOAT64: - return false; // TODO not implemented - case PAD: + return switch (type) { + case INT16, SUNSSF -> !value.equals(Short.MIN_VALUE /* -32768 */); + case UINT16, ENUM16, BITFIELD16, COUNT -> !value.equals(65535); + case ACC16, ACC32, IPADDR, ACC64, IPV6ADDR -> !value.equals(0); + case INT32 -> !value.equals(0x80000000); // TODO correct? + case UINT32, ENUM32, BITFIELD32 -> !value.equals(4294967295L); + case INT64 -> !value.equals(0x8000000000000000L); // TODO correct? + case UINT64 -> !value.equals(0xFFFFFFFFFFFFFFFFL); // TODO correct? + case FLOAT32 -> !value.equals(Float.NaN); + case FLOAT64 -> false; // TODO not implemented + case PAD // This point is never needed/reserved - return false; - case STRING12: - case STRING16: - case STRING2: - case STRING20: - case STRING25: - case STRING4: - case STRING5: - case STRING6: - case STRING7: - case STRING8: - return !"".equals(value); - case EUI48: - return false; // TODO not implemented - } - return false; + -> false; + case STRING12, STRING16, STRING2, STRING20, STRING25, STRING32, STRING4, STRING5, STRING6, STRING7, + STRING8 -> + !"".equals(value); + case EUI48 -> false; // TODO not implemented + default -> false; + }; } } From 52d38cc1543528eb954dc95d6e191ff28955485f Mon Sep 17 00:00:00 2001 From: Stefan Feilmeier Date: Sat, 26 Aug 2023 01:37:31 +0200 Subject: [PATCH 24/32] Docs: Update Getting Started & Implementing a Device (#2331) - Update text and images - Fix Bnd templates Fixes #1507 --- ...he-felix-console-backend-configuration.png | Bin 54804 -> 0 bytes .../apache-felix-console-configuration.png | Bin 39697 -> 53953 bytes .../images/config-backend-edge.websocket.png | Bin 25264 -> 24509 bytes .../images/config-backend-timedata.dummy.png | Bin 12730 -> 16996 bytes .../images/config-backend-ui.websocket.png | Bin 24987 -> 24389 bytes .../config-controller-balancing-symmetric.png | Bin 34770 -> 0 bytes .../config-controller-ess-balancing.png | Bin 0 -> 47129 bytes .../eclipse-backend-initial-log-output.png | Bin 62452 -> 50353 bytes .../assets/images/eclipse-bnd-file-build.png | Bin 16558 -> 20680 bytes .../eclipse-edge-initial-log-output.png | Bin 567524 -> 77872 bytes .../assets/images/eclipse-edgeapp-resolve.png | Bin 70581 -> 71553 bytes .../eclipse-io.openems.edge.application.png | Bin 14423 -> 10832 bytes ...new-osgi-provider-simulatedmeter-final.png | Bin 12524 -> 0 bytes ...lipse-new-osgi-provider-simulatedmeter.png | Bin 29880 -> 33737 bytes .../eclipse-new-simulatedmeter-bundle.png | Bin 12750 -> 10465 bytes .../ROOT/assets/images/eclipse-select-jdk.png | Bin 41726 -> 39872 bytes .../assets/images/openems-ui-backend-live.png | Bin 0 -> 47928 bytes .../images/openems-ui-backend-login.png | Bin 0 -> 7731 bytes .../images/openems-ui-backend-overview.png | Bin 0 -> 10081 bytes .../images/openems-ui-edge-overview.png | Bin 78878 -> 49384 bytes .../images/openems-ui-edge-overview2.png | Bin 0 -> 49338 bytes .../ROOT/assets/images/openems-ui-login.png | Bin 11993 -> 10972 bytes .../ROOT/assets/images/ui-via-backend.png | Bin 42338 -> 0 bytes doc/modules/ROOT/pages/edge/implement.adoc | 143 +++++++++++++----- doc/modules/ROOT/pages/gettingstarted.adoc | 76 +++++++--- .../$basePackageDir$/MyConfig.java | 2 +- .../$basePackageDir$/MyModbusDeviceTest.java | 2 + .../resources/templates/device-modbus/bnd.bnd | 1 + 28 files changed, 168 insertions(+), 56 deletions(-) delete mode 100644 doc/modules/ROOT/assets/images/apache-felix-console-backend-configuration.png delete mode 100644 doc/modules/ROOT/assets/images/config-controller-balancing-symmetric.png create mode 100644 doc/modules/ROOT/assets/images/config-controller-ess-balancing.png delete mode 100644 doc/modules/ROOT/assets/images/eclipse-new-osgi-provider-simulatedmeter-final.png create mode 100644 doc/modules/ROOT/assets/images/openems-ui-backend-live.png create mode 100644 doc/modules/ROOT/assets/images/openems-ui-backend-login.png create mode 100644 doc/modules/ROOT/assets/images/openems-ui-backend-overview.png create mode 100644 doc/modules/ROOT/assets/images/openems-ui-edge-overview2.png delete mode 100644 doc/modules/ROOT/assets/images/ui-via-backend.png diff --git a/doc/modules/ROOT/assets/images/apache-felix-console-backend-configuration.png b/doc/modules/ROOT/assets/images/apache-felix-console-backend-configuration.png deleted file mode 100644 index d07922258979e783ae71009e043445bd73abd489..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54804 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 diff --git a/doc/modules/ROOT/assets/images/apache-felix-console-configuration.png b/doc/modules/ROOT/assets/images/apache-felix-console-configuration.png index dd2866a7a0660712be3e607e38bc141c1dd92a91..ead1127f3989deed06941d8bf142620ce3fd584c 100644 GIT binary patch literal 53953 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 literal 39697 zcmb@t2T)Vd_bw`;B1MqiK~ahzARVdF1(YJaBS`Nh^ni-=CQ_pG-XZi7NX_^liBB-eahNx?X|x3ttc%Gl}AJjM0f7od8GPQ@xz@v zccbsz!IOXZ0C&ZQ^STdL;CX#ek-t+l!nBJ!xo0Q$UhdAFngrtOkN0usgzj&Ryzbl~ z?f&=2gSnM_x^u@yLRC>t*U$U_P8dWzJuiF2sw}UrtN2Ni1dsZj?zhj8T+N?riK4km z0NwRrGqk!nmC;~(EbWIWD*yvzUiXB*c?4vgLs`=cN*RHAc5IdQ}s?AGmBvGHb-9cMA3UE zxQe2po^}cuRmjcjZw|yWIXdu6am*iZO@OA?61KPW;&j4!f1#0*Hsn@NkTf_d%tU5Z z{js*vuiHISZ)8v-3maQrS#E&AE0Xn;--^IqUsxFa`neNGFRMEiYrwo4U`a+-9x{>aNzGZjQC0 z*#r7E%*~5n++f9-7g@nu9@g}`1<*Y9v=zTcj6TxAx0sc`sV1(SpYedGxj%g!GM5`$ z0bKIEX1?(0fXK*5E`L1h}6x-WlfQhDcdNIBL%dvzl}S zj^)udZhd{uDiu50AzJ{7eg8GI=zTh!ng2baQenppL(_>^@B!GmRT>GTVIfqMb z?e}#0ai6+dzHMdNTsTw4(V(V>&*W&0Eo}Q@=hSVy%z7}t;UTl(s zqN1XWo(+DF3p?g$Zm_3s=mM|(1Yye;BLq<1ojh}IWb=<$)Qt|F1UCqn*a8?0j z0Br_+!6$(BluJT;nq;vRI>&bRr6*LOnf2bPgQ)=3so)j^zINkvIOyX6Nc(`-dN^$} z9JV-FYdl@SM}GX0XWd4oAJkyp51M5+FW($@RIMKhFuqI|B80qz|{zYBv##p`(Y zrb_dRJ9ofFr}omHY6)Uec;8APo7ZEZl_sZOXmB&VY~VpO_+=m~=KK)mb|u_xH0-pR z#G24mceuR@m1Pd=fCdq8PT!ChuCRvrLd#+jI;2TCnf0QVTVy}Zq6v-@|G_!2CKNGpysBPEp=h+u{w8ro)hSK-VeP~Z8e!GJld*2f z-=GwmGy^U6PcpvWDGzJcdKrT$%@1#5nOsQ%=j-gi&NEeJZqE4?23Ndt2sDpm zYkQ#VG;sMjP<2*K<-|o(#aN;eV6w>JpX~Dec)|$tMBg}7abRlHz#_O1(+1c}PDp5S z71|r{p*|9Ep1RZEI6a~Bg<*@Bl!w{h(q_BLB_7$17NeU)viRCf)u+2c#(yd+DyEpE zJfflnUq0Nfclq4-y7S6RHg8(>(eb8s=FwV~MhRvev@>@kYlykz*Ya$pTz)-8JSZzm zQ#^|$d=0yfC4=68HG0wk^Yd(21u%U@#5+%DM)g;Ap3grl|G?Lt%SF_Gzl6)$7uwwC z6NFNiyme=DP(8AO4msJ~=hr_<>Z_$84Ekh#iGV>g$BCN1EhTYn+CXImX0EUUBN_vP z2`L?V=H|OnV!)!~S@R;#OVh8ufuCtLjoGpUQ~W=rd3$ZQm%t=cWAd^oHAl_GviBVmVz{?xVN;hQm6LiTLN@6yy4-O_!sOsn)hl|~e_SS>U)2Kxb3PI+g zMYvVFsgGL_{2f?4|G1DZp|U|=8(QVX%atfI;DQ9VGE+>q5f)Dl|A4ig<#RwbG9#4% z2+Pw|nm0Rk!WGQF#UesYYzpqHNgxy~t`0zk@QOh6bFJ@peiJQNHAMGxtEo#kRK8O* zN<~@0i@D@-@5RaU$*Om5079&!5KSv+`$Wt410#;i@2OP{PS`#{Ig*0MM2&XUXQ3-8 z0nUG_%(CftqwN~VZl-hnS`JmcSLzJMpBvWuYD%N(1#WDpsUXEb%t2GHFdJLh5>Ea2 ztr5aLuH3kJOeL_c*I%vV=mPJ!-CnD2*xnGp#45fV-eQCV6CN}MT}pRgjtM?R$f$6g zQFdTkXyPkVa&@9Ox0IRE@c!uPM0^k25FT!3t5y}VIZu86te7fm)0w+(&b(B|-` z)P}pJxZzVXxNAqq5Eh&C#JF)fjl~ITZnMpyDXDJ0Z^L&wSe?=#^1fG{rETJvjkopW zN&LOG<05LO^tFbdOYXB>FTxS=b#LhhB^I}e>!sV^js-UoG1sNOi}Vyd6M)ghP2eG@ z2fgF*aY{N$znW_jw?7R0Th7~=0Kpk&EK6Y1VVB4}LVNubC0>2XOj3d32(H2sSKRLD zZrC8ZLVUno9@&+>_j7yZy#A^r-z10W!(OvKtWHudKOY>Sp|=Z>j;+sAEl)O$uIHOI z?h1baKXDu!A8v_?@!`1FT8ko`_lnDZ`vICFFv6olm>Qx6=y4r zs`HURRgRauBqOo|%RkfFQy+AQ<5M!b$J zd^#&W11!3Jbj#e!FVWP)DB||J%OWG1oO4$GmIVt?i%NPT_w_+wwW97cu-X4vyu=#w)CE|B*bl>pDIR5Qb z&#N|_R{p)Is<2IZV08s@+Hl_X-VN%ME6De(znQz3qxKpsMX61MJy`AEBc6t$9+Q*n ziRd*ddGx1y#X$=#f+Gw>_kY7ISl%cPr-`?$9yiwmtglyAAEzE%vS3K>8bK?HM5n!qn%%4g)dSdA1qBvgT`XVi(9zb1(AO zl_c<^@ND5i>{wenSal-TYvkiGXQkMCk+!jYZ*XF~OL?V5Wm|23>jZm)s~vx8`CRH!~NpuBa_s{Gn~4-J=qPyDUrsxytm0u2r0;zE=XmuXOy9e zxQjEm?qz>#OhNyIf?Qj|G6#1@%Mz}qlS%(oL{>)O4*C8K0Xcah=Y!~fL$DF`-$P)P z^HvuxUJ@yOJ=$k-yo_Ot)wse5!Y%nA+F@tTI-w_&jM{2F&?yco_-@l~EXCmOw88e; zR=V3CAR0>_5_mzLA98t8NGH7GDQz$hz*(?~TO($RQmwy#6DoJADJoKdld*#8`uZ+R z5@$)r6l((ow!>+CLlsf@1i@q0+^P3I&YB9{gY)l%>sM-s^t#N~Qmd~Rhwo0>{cSz$ z5bCA9zM@$Yb*vX?mr@PR@;mXED#4@D2zs0kTYBIhiBA0p6*!Tv0iF&A!)YtF*`?jl zX1;kN$AF`$sp8`)ozcIGZcvdT)lQ$u_hNrJs9y!2s1hO#@HY0Q59uaH4}X)QjGgC0yuT2@J7(qi_5ndfYcwU@Jv^ib6b9tbYZ*j+{`4YG%+pOpAURz=hi1 zZGtrys=ulV70~MX1aBNXS3mqJ$*7Q3^FCzDQF`XX%;vLC9mNrQU${#=w`5xaOm7w` zO2yQRp(QuH7PrG!RQ2y`hq>V%2dG!t*8@~s3!#dHpSgMZO_UzQP&liqTYX$rP}bT} z+HzRQwUW7y{i;YD+H;}}u}eV4xSVcdeN$6*FDf;fga+Jl?noU3JY;-M z)FL(pvC|F$Z}F4W{71g2MD4J&%?tm-w$u1yD$ll0=0-)1QGlpv5jaGc zgV%V~qlORs3pB(u7;ztLVT@!$2!KR-rN6KR^B zLZNtmUU;!WW(3sEhYN%-*yYuIKQ9BJj2}NXc@M_7U zzNN3-p-A9hH&-wl7-;IxRD2#0m`)=TUF?A$ygdLNPisCSyaqeG$Cs$L{81O5tCo$xF!-o!d2)=XU;)0W=Yd`{EsUD+Am z;Xfr=@KC?NKbw_&kdN_WeqKMMP{lxdhH9=DWDJk*jPoM~55&qW3R&+9)N1wSp790iEg|2&>nrJ7AJ0CCHlOGsxMUjUfOs_By=FBDN=VpJDe4= z0(orB%5Zn@^QdSqsEU22k!Q1mm+@6cYe5}jD?la#HIWJHn)6?M#4RG0qA@P$&0h_k zB=J1pT`KaMs6Rxfmn#jvV&^PKps2b;Zun_XT&namBNw%d-D zM=4+G|MKz&>yQniIGrk`a#WNvheI9_3UBZf&(VoY4pO%K)6H=lY^hQnQ@fJz(q zYJB72{Hwjj(5@LZ^wQ}Kt2g3M1$~6IJi&uJ;ui^#5BZ!GyQoc={#r2j+>_NTW6|xN{ODgFo}WFZhRwpXwC6R(Jk!C=@PWb8i&pZn zSke{Q?Y4SX&y5u@-=79D{Jqaz){N_F1~DxM)GfR+-V3)6x3%woYbv)veGi=?3@p#9 z0qTEuqO?mJk$AhyUHZmjwhC?tbqyVt0Hvga@poKk4pm86ynp5pSUCw-HD@3F;X5CG zax$)B5@+!DkNR>A7w8Yqzy$``b%kP#4W!r}PIr^ad|%pd6sJ_8ogaSu-0S6xRJnm( zuKK9*;7Ak(;TjWOL?jzdZh(}===-chb0b&N+J{IZ?9@ofT5x>7<`8^CL&3Ub$t0^{EJ zP3>PB+26`LEF!*LO;|O&N~e6brsgH)qaROf?fs1%`znU|rB9z*g6(h~V;2wOza6M8 zRPlJmL|wppM=yY@@dk^bAJk3ec+X&s&CPt=(F`_JLNEA$rw zjK4Te``Yx7?cp7~U+^|qAfVGDGU}JVcwM>er;;dWoUqF3xh+X-)nam`F+*NNycRr>b>%%8b#Ox$1Hipa!B`)H5({`9< zJsG{ms1}jVU%FVc?LKf040BayySw+xh|{&J*~8XpcV)d@oT?o8VL?qoi%O5(u%hpY zHf|I3Ul-w=HD$ykscyYS&RNIVlmz%+toC1^p|^1`*6g**Nq<3&qJAksx0F2oz=gy2 zLKcU|-~3f4nhS#PIp$d!cU~d+PJ<;()RiZ|2ayfI)Zdlw=2yZ&=CE#V8?$9ZR&A$?z$D`G9ohPqup@*n#e8rAFhwfi8Z#Ych6G_Y{Jol;4G~qCw4w7v!AZkA)huqow^Y58 z-C<-_XFJ+-0dco=Rus<%a8g%r{wQ_iZr*I_W#8Ghm2zs^SzyEdr$wbt06WnJ9dw`&RFpP46XPF+o2eiJYYYQ%{)^L z_WLvmZ0!i3Q0=dZmm)uFZyJRQv)WF2>HT`;__k^H|2&~CNNzYTtN1Yx!md}^q;&2- zs??{h`lKvHB~6#N;HB1cx0AI!r>gb^zoxu4QI)=KK2Oa&Q`f4pLTzHrMk=@UjIZ&op3@fS3uF4O zoQ=aTe8|b$q%t0yXF-T_ClnQT^M=UkaE>IoHtNqmKPfU&NQ%?pEd#7q~SMreC$F>1|jTZCGhp3d0&FwX4ulSX9yW=Hxbv^+RP zZS4Jx$aGtastNs`J^L8oINx}>^K!~*?g6}tHHLF}DT?*%8t0DF<12M@`nB1=AVoZF z~J=PtdAw`q}IW75`}D|7@l{K4`#?^J@0 zEBav6vbPnoa?n}sccZ5qoy;&hLiUuZ)^|~0^__GL8{ja4#R#%;GHzTEU$&QC<2O$W zSwWh$iTXm333F`$KwYi*%?X*lDXiX1huwISD(zC;zR;gQQSs|;nBl3bH$4uuU?-tU zIheHgO#qQyz-yDbd~xKhIjydFwwINQ;XPc-Oo7bS-o9_cCL(?3s|Vmd&8%+9!$C9w zC~!HZyhK6$7~wO!I_-A$id>uFV@_m>-;T%aD{Ut8o4e)@W2v&0EHKhW6DS}}gqlV7 zh1_LdIf%OBwnjfwYGXy~+xslBR|l{w5VAXxhff3<;%Hc$D)fGxg{$E@r^YX@S-2|o zQA_q-7x@o!=}*kes^$8b;eCUw-)ctNW5DaGJ6bQIS=;a~=Vuz3;wl1M#saUMxZ+x_ z$>z?M3`qt?y?Q2kMaR)_zifWMn#kQ#fNMHS`Q zHPMPw!<9hahG~yfxWT2Ee zPq&7N|DqNZu#CW2a1YQGxm&Icxy>;uCPk|3?(Nb|BB-W(V1M}{I)Z6O>Cvy1SwSw4 zxK5x~Xd@gGwBCUIkb3M4_0QS~7k!j@nYdZFY$1D2*mik5+p(Qd>!N8znnDw}p*Nzk z)pGp$Fo}npT|M*f#9}mgZf_Y~0oM@Uvnj8mPOVgHCi!jYlN9sL!Fny$+3OM=eqH`< zk&)%;UCOlc7IP_QYx{Rz3I5Wpv0CkXWZ!03reYw>OB87mT>QzqEfwWX{&qVG?S}To z*(JNQHy$5KRN7-pj`aOvUhL|0#>$yr7OU#J4 z*{6Cz@+EoBI$fpn4JEmg5ow%GJ5ti)yQg8#6D_%f0Bsvu zq@Zzl?{m{P16@YBT}I=(J=}+(?8NfckSXE@Z-YB}5EGVN@*vkSGuvfUF0ERd{+dTe z%TYqEs@$SQu};Yt;)%2&MZoHn_$Xyrj7-IcYH5~jro(RZ>DX^_m-RlUOs!&q8Hqgs za(4a${GJlB-z3zX%tsgQ;@IUe(e`)336#D}X1zva%@gmQYY9vGKK>})EGk@Nv;UcM zNraV>@Jvc$p$6B(XjmS;1NbvXi8}Wj`HP2CI*GHo_o7ceidSkf&q@L06}ER%{0~g1 z@4&%|FPs^?(bZ@1i3z?ccseryxjyC6w&Xj*v1SUO@ zd@@NEe(v^@YR?=dLRPDL$MShjDOqP~gTi}BQ*s8K`selirgQG@ArAJaGxA&wq;Tz2 zM0OU;`_-Agob|R;*0XICTE~$a` zpm~|c`6eWKV-6zxA%1oxg}}5jr*5-TI}$(h(U$@#n87HblBE^ajt{VC#Lx0chj;cP zq;<9|OHdKv;!b#(BXRO0mM>PyKK7jj?75Yg;Y-r}pOoB37q#B0`Gmra4f&q=6MYO) zgB7$<3+HckUZO$dfUIWUUq+*CO=gvv(|UFS9JE}@2MHzRFB~nrv#E^D$`I`toT9vh z?;#xuC5f(B0F!9xLYDa59`KaZNI7oIGkb>!e@!cAS~LahAAahfd#s&YPfA5rmXvV% zJe8V3Nrou!ddcq(k(V)50%a5CT``lXpLI!xQ^(c)#Pf7N#ioThnYkW=gfK{} z=CYBrf{wH`VT*IY-5k0vR+$>leioc_HT3opr01+?VLJH_35XCXZU*K5pFNXk1o z(%Y#)RwZGK$i7ivxC{p9xQe*JFNmqA!89Wh{{GsV$oagC&pXz4aM~37K<8z`3dl{f=>AOmS%#F^iV%`uS%PvKQ z#pG3Us)1QvR}s78qO{3cQy5YjE|cJ0rGBj%A~5_Yu9mfc1qNw2%x5yBhZq2Bm@FkL z3!MX{PFAIW%CiwyVtlbxXzedxOpG`=`?EM!+xdjzq7Gz`ua!QlDV%e1|4{NCsKuQ1 zWVd2F$ZbDC#WrCu-(2;iXF=~MU~+-XqAj$AEUpJN6Uuwld$1(iva)_k8GNORjt+CP zY(a#5A69wy=l!Fz&k7vIH%Q)~cHrHw%VQHLbO>y$4w2tT@`)v(&b5`mpvB^MQy@>{ zj(N>d+~!EFi$6JE+<<~v#^ehFw9{c#=OUyjPq(+tc1yFf9G!WeIynqj=f$~CwrpF^ z6xx#D%|BUgCx|hP=EH64RmVH0Pus2_b&Z2gu8ytSw^sDA+ZHlLVGUJHJHm(Kj3e8I zqSjqj+d5C_x6Qw@Xfux8&s;>9A9U_*M%|vM#W23wcnNo#hY%hjmOqK~9|uZs_DQq} zuV592l$wzoXv$SycO!mO1FQ?9r$}lRk^m)>W<(8#Qo*tTjL0aNw>8tj-vbN{wP=fd zT3M`F11P*!=HsMil3W``yPBzQyY`RpEjQbwebQ+=ja&rXk?hiW2q0+0T|XV)uss7h ztDvjD%gBWc!Q*dcE31$(=#Hk;D~Nomowz3CSN~2{ATe}595Q<{JR5It$aJ;y!qnf9 zCf;d`9t0O9zfD7hA6u;eF&)3js_u*p1~M-Dwu?*Q+tq8LVy;I1>{fb%i_z7qKm%LZ zE1`uQ&Zh@vtx=L^B&jZQYp9uRPaXf$g|!6m`Y##4*>`n)O{#Wjyi1!X3MKfddpyEx zDd_lIzO8Y}oR8PzCXD-G$s%hzQ`fADA7&UUUhh|Wb)_P z0%Xw#eZLF5V%vM98WGY7bGs#`bZfm20=S0nLmITo<7XEiak~eKI_owJAzy8oGMm{s zPBoK`Pjeg_D=2C>FmeT-_>nVZhxW;5muK=nKn4zqR9S$_Z-7^my^93zH{)*AUxVu;*xTxHm42nO)(d&<8oy4A}z-u zK;tM%07rn?zN7ySS~vc*2&Ho|yV=9=7!&nT)g5>@y-}5E?PFq4^K$9Wal-AEbnd~m zYHlaENuqJ)o}-d}qpPV!@b(>6+2GrDWQ+yekiY@H@^=;L{dLqJv07opwbu!Krd-E? zf9!mHXhD=bbpCwZ{gNd|(c$TP{GJ0pj=f?-Qp>H)Dm0C`);+Y_2PQw1fAT0gbdShK$&+%y?W{F!^0R zr7GmIjGpCBc&&^socZP37TV5wjX&#aPjyNY-fc0`b=cdM8!=GS))*1_fnOTG2OWJh z`q$SPBY7mqA)B&sSif%(uUAzLqA$)QJ^ei--?rM(QhloE9DGQ>BwZmpGBT_*xo#bD zk~MSk32yiLGnuIKES7pxSkdO0FV##y!@d%L8k^*kA_9)6cHO_E+pVHZ?PmSB)+1&W z%x%?^)ou!U0_~hAJdXj=?<^0VI}IbVZ!uOcnCf`!PTz(9%Qd%FY7^0C!$_ zTyVdC1s}TsY2<{7@q=id?4J+NaNA?a*iHVnzs=xR=8LQ^etW{*20HzLm4k5ERZib` zT3|eicF51y?v_(P6;>(n5zZX(Zxh8=YZo-ulp$JAv^XA+?jxO}=CM>nb_7_fuLFZl z(C*#hVhR2l-H|qA$SSv~)bTSI??ZqDKu$eS8R}0uYo65yuDR&j4^_wKr}XVAjNg(2 zXW!O2#j|9|!hR&k0T~g&F8wRVpb!FO)44NC%?xp^VE^X=5w^17yH)CUQT)4L&&1CV zvDNePJpPg3x-+!>x zwXMo0d35wihuFEdFm&RaNBb8n-yxmY16AF6yn{e!1z3tGD_Qu@;#T_5H5t zUXrP#5>dsCq7FdeuQ`75czDk-B$}9Y3S6c|y8n=u{_q%aKOP-4O#BD{-DuT7k3C12 z1)mtF?LLz?%L{(}2OE$Qn(MESZ}AQhl#tiv>#S=312xDiV4kpkT*R6%8-Au;Xt|E! z>x>P^<$G2Xp?_F=@-$tK!5BMmF_xp_m<~2O4j5K_X#-sKi9SiIom~&A+Dnp@jU$$K zD~+x`_)=#Iu2VgMC-IGg&1wPX7Rd1>@Q8TFg5eBm{osurE z9taD8IeSQD?L(iNYW?HZs43LxFc!f{0MC4; zr7L>l_8Kb~gO=ONPyw9J=OL_ON7iXSf2fuA$n>JPrKaDdkb3kuF+#ZYEa+ifar@nE z7c@%1Jra=K(Zr{~AAw;TB)EO4c2jGuEPdv;!8`<+3I6z+5}0jHLj8Q_Bmb`wgHhA~ z3;(anpVc3qaiB_Q0ndV!e)!^b+^*`94AfaPlB8<4iaPGyMC-mj33!M=cF-ir)LOWY zyct}~?g6f%{|a{=nsdu*28z5w8nhAdKlevebOBdB8qx%uor=q7>^++89{p&>R|_BA z$?a-5tpTjkd6&|L91LA=@&hac1;>5&r zud=6ED8Ji8mPorvq0=f@Mdj(L`1=B_;k4t~<=6l-_rsG%%_{&+*B%#5d{fB~9-8=F zido3JUtBs3PuY@4=_b47Q16%Ej#S>iUcS5n;eGDS?IUaO3+g_+1dW}j&PGTGOY&XE zN#>su=FYVDek%&GG+Q}#$3_0OB3*NBVY}+C${_MGc)f}J(AOVh3x0fN3rur0&xIW> zE^}udX*`^6bZOlv{B=|M28(bgqH5$)W_oyF_<@SSODRH2$UsK7o?0ST4l#BKWuW9X z&f_;?=f2MyP2RuVP{E6HcwEMz1g02Adq zEsEE;MRs&*62TxG$;7D)@;5D!t0T=-4^JR#w}JwKn1QKz^uxzM$Qqhc%>QCz{RR^7Bj{KP}^b4 z%t!c$^;XSZq2e8-wy9%Z$ljfNL4O(`oIfxHeQ_Kmi39b@eovf-E&js{sbc>J3eod?ltW3zs~NFd#Kxi>%R5vVD%%$Pc6=ATXIm8Y9XG`ZVyK>QPg|0@E{s<6D%O1d0k@M2uVR+ z%A*}xK&w|Vd-;tl{~*|Cl>po=tT4T2pul@kzxo)33DrfFj8{na7r(J5RjEWM{|aD{ zm9^YXCrd-Wo++hnNjSjan1h3`oEpuX=BYy#82VMC(ng0^zLS7{oL;GG0I79glU$~8 zKv1Pb-yjrEDLwcVZ9@%9Rx?sh`gg50+rs<=sciL$^U6_kiGyo|n)dbIM}4E^p~eBr z`E`4_@0-ZE0(=RBSB8FR#Fc!@J3NfZQi*z|IZn2^6QLM9_ielPT$vFeqhl6s1^W^4 z>imia2ak1^V6>d3O2Q-_F)}QAt}MNG)PxxMLrKS#7DrO!_nZ-D!^OqVEJASLb6$xh z*MV~@+WQjdWMIHKWw)>$K*~=b&w6dG#l>HzX`jYId4BnOEVA(zivtAmZ<9k;>Aq-j zA&GN9riq~;V;0Kwk4lfGUN7TND23Tkj2gdN+6>HECZQ^!0dMl^LbDDNq;GHUqde92 z#Y|UzQXXhElK&^MILAI`*d-i{n&;8QGz4k98tj3ARc=m;gd%ZDq@$TzRgs(FPj;8P?jyP>#3lwzOS=*Du-xy zegEz*vMNcnK4FRQxuG;5y^=iNUG>z1Bgk;@t-~%Y7mIOR1MBWh_2hB70Gu4L=2@@s zQ+Chy=V_sIkk^ zR1gTc>2c^@&Rkp-Lul8(HWj?E69Xx}VYm}SK&; zU{qZ@FOc&1Fd=V&W4saAfvoG7Vs`NaKsRHgxu)BX%NEatr*O`N)u8_VOnYk5VuR)y z1(3bkjl9>#E!G;56&({E+nsDwcXYl5oNIDp?U0s_bDZwLO=9g7Q1F*E2VRd_^qkK@4!s^E01}*8rHj|f@QYYr#MnNJN9nR z5UKsFDLMl98OF(1`S%GMF-*Tp^S}+d491kjr}!rPR*xr8))nbPgb9$1bpCkXTJ5xg z*N5>cOD!|{*VT@y-Rh%uYqjQafRI1MIw7Ge{6qmy2c9N(oJ(eym-APkWwU1l%UmeZ zMJpzOKI~jrQ?&7(m{KFMvc5K5*{tmPlL}F~`7KuNVsmQKPKf5EkMwt)TjimZ+B=lW zW^u%e8*EKCiI>A^|24D&>bUq2OxKfhk$oC)7>^5(1r&IQ6cdXr}=m#Ec%7 ztOt41&K;A*1|69Nxg_tBU94rYEggZZtUv5sQ(?6RYEa`(l*EjK&C`b9l|}!jG@}tl z#!K;eizdIBdDO~RM?kJHB^Smg>bF$J`k{@@C6;XFi@P2lA;|a5SZZ<#=0>3==P=o+mvNpjzmS^ECh(~BPu1FEs3FY zzJ@*7=n143%oo+zohB$o$SJ_fkJh$kU*)+lMSYr501@b~V+k+ywy1@ET~Z#M3*rBb zv@sRjn+|OqA5o84jByW~QqO)SSW95J*PIo=1u(`XW=KtPaz)UW81QHD_WfxH>0mtG z-)q)8y}|`o^-LHDqUCRcPj8qFKL|g7N|D(!ityG+t*%YNVTr%d0+ThXvVk>g33`VL9bFA|$NexbcQ<}aqfO2^ zVdXet`Tkr3`lI)>HSh4E_-_XtTN#cvC^0eeAZ+H`JI$vPbvyU@);T*aU8A%VR{Z1Y z!>bS;{r3#tW;myO>iKM0W*I_nYRI#t_LuLn+AW8o7D3h%DCV)3+?5aPzt7$Spxl?r z{Qt9p%az{-uf&v)kn%zZ=cTiIX#)(uLh(j#Y3slfp*X#*%Uava+&ms9?!}O&?ZMQBi}0ig_cn_P%kfPp~c@ePwSC z3NL`1sEM}hb`Um_7tGE2gBIR$Qfm7~7UK=}PmT4_@EuC}dQGq?SA^pEt>MLG$aY#3 zogCNR7VEMb!Dhlyz}Fm8Kh!o5i^qr?mb)ZW`Sr>OUNV)h_^78hM>HZDWGpWza!jSb ztTb*buGf%QZzZnk`=Z=^?S3p*yz%c=I@%wCMgydo-08k-br*|I%yj?-dKa3S-dSzu zesBsVGG07|tXn(l-)`{|89Z<{0IVi&1-znuE*{5ScVH;xL(HNvtbrqKt1;mBa8@1sQDVhgj0Fc_srbVer052N{WZA%$EL%p;%qlyPS6c?Kpus<4(jdCUeMYq(@pkD8^86V^zI#)RnlYq?QpVdACk*{+ZU$wV#SvXBMzjsJHc$W3(pXWQLa z=R$lQFd2#+Y@KSy@Pg_mfu3s;EDwOFv5m3K-T3oQ>_<6wv+q(VO}ucVjz6GCieSO! zm^>~-RZ|SaHviGH#j>kWc|PsACn&hbv!A0N-ofO~0G-mO!Cb+ArWki^qy2ZR0)g z=t+m&2+>?LUv}86jhruo|NWl^BbNySp(bSmf^EE@H30M6g}FvtkK=oNycE6avK`$V zgMxttnq)Iqr^-n)EqBLzKVAKKb(zy6K3Vs@DdkGK*My_ql(9aD?zo$Er_tiKh_U&= zFVhLVF8wcv7k}CFiC+ymGXKco;imZh<^zEkcU-@9Q_hnT0Jy?xQ_lH@E?&7br`CIv z*d(UH#oYfb%(QY}$p3MHNaJ(3_aakqy}pfE^?mG_$g0$Oz?qb@128(RjIQg`muE-O z8(qV)KS9@|pz046MDa<{^7a*HrL&>qTd3O;SEf z(Ob0&DMLJPEGY6>$xHZYcxc&_=MjV#jajI#&*!040MK%Q@mcyX!F;j0_P z+^_uW!D@>Vob|P4u8kJ8DIAkK5qd9sWsH`{PAd*-B^rK?r}6TJp6}9XzA68F_Oio< z!Z)C%5!ag&a{stR%m;?g;)mHsb|(?3=DKWB0z>wj6Rfw9E!L$5#J z937nI|6Wf-#VBn5SyL99{Exys{ow9@(m|rh zSpPR^BLA(H3#YqZMnG>s$Y(eS@(gkC;SfU!&ZNNcp&s%}Q{ukyzsUSB%u+M&d}PM2 z461^5)v=emhs;KC%_POgmSsc?d`BC8$b=2F4eNaj(NY9drk@A>NiWX1&QFR)cu_sN!Bo7z>YRMfwY>!xnb? zj%V({Y-JmBdCgXA5E6f4+EK&Ul%7On*b1^UX;K~auAi%`s&aUI|Ly%KYL_0*b$p-| zILr_tpHNOU6(#FKLhXu8E)$OhN3!BmWJPYWII95&%4W6RTmKY>Z8gcL zgA<)L@23I2vIJ(W=s#ZzYxx*q0R^Tnml26H&oI?M^FaEZ6zC7l>VqlbvEiDK(Jr%d z8vNOg$O3A!<+|cqWsuFcjak!Py^i4BtE$oAU%05s&?5cWJp!=A+JM254Sk@oDW)@R6$WEQ`H&}1$~yiLCZx1m`=Wk73cG7m~uGD#k$HZR1 zHgLzqG+b{zpUA!Fm&~SoTfcNbD2G=*EpMu+Bu;M z7(hS$-ag(ed&fb03Gh5Mr#7~UOGTbC7fnNnOec3I@XoC`Rya#V**R=HlKK!XIx23l`DZ>)INn{M}iyd=wWjyGFE5oJ(i&F1|tfJcdPv5FBsiu*Mp1S8jl>*6{ei|DEVf$VQJj8?LIMfXC9TW z+>KdTcF{;^sXWIeCMr$grVDiQ>!~j?d$}`u(^kxR_7S9lBge2v{bnknm#+L8vi$jl zNu}ozx7&;2Fh5U73o!fLEoUS4oIAtmUEQXDW2@xqh-|61RmJH1{KQ~?S1B0I-0uro z^eAq?R`E%Up4WPj5v15uICvD0H{V(VX4*a!`~2-^)H7m44W%ku`Ufzrc(C$}1ejif zIq$%N8(qNKoQ=OuIqE|u!LC=J5Uu6V)Vj^?aaJe#Z0t5)@x60)NuTKk^+TGTjHI!( zbsN7{?hpmjV}lF~fo0xJwNcHr`PoDgxAKc+^R`DOtgZChS-w%@X4sz$=4})yHHMXB zTT$&atGtL0y5PWW)){?YQ2zA5?h?ZF0ZSkYPc_4B5J~felyA&?62hv{??QB%jHf>T z2WxK`7G)FmjVppm2nH#oAR*mK3rKgz!UEEbG%Vf10@B^mT?fS~@5V6!^n@R}v@Zh-AEIM`OlsLa!7fg24ecm64%+!myj49-kJ_6zMuUssmsiLwn zoLtp%6}a9BX=0^0#auuHm^oUEX)`=`j91gYg~S?u(=JsA)#;_1DhJ8)8|q>{XACQ~zbtev^?-zPJIk$8}p4GHu|hFbxjlRT4zvmoL+rp>l`_K6ib8`m*W#;}5q>zm*v zH4@?5+1M&CS1P0|jaGpQjf9^5fiD(Q&S@0U^e#Wu$=vf2f%y~RY@<9*7Ntw~0e7Uq zrCmiN&^2j-&TIQkwa<8!?Zfy#Z|>*?k^^#WMwBbCT+xKm5>XcCG0S~=4FUL*@Q{?n zz>f3_RC3;{o_dqZ<80N@W!5N#MosSIQf=Sa2>+2M5Hpw~#Ab^pLVsSc%JJ_x!K5A56 zY{5}uBwJ`Mqlskp_Gz;BiIMyIBWGX7rlK8?ybY*+$O9bSTVs!X?_y@XqN*vbicZ^?+x?>g1inX~YUd$_r~ zN;fX4|7=INX|*q)uu-}RNgr&9gp0+8)-uTnVh#(h;WmU8Co^ck^Dr#PZbA#l{e~^) ze1PmM`<6>@sESSR1aG4p9=UX0lJ~fL(sS>ATRb@?ia|W`;^1O?J=Wweq#;b5ot*g% zI;m<|Q_j3Bzu09zTD*GVlbpwa{zuV^*MIpi?7{I}{PZ&n>B|u9J1O1+xTyP+z^SZ@ z(%9}y={aWyW1M$=-2cQ}qk^OVub}9+I~1mG-@g5kzzo~NdT>L(Hd$*zrmY^o{%7TaPs^R{xDDuv0t!?>3=&s+VZ|ub$N= zvj3~+$%N{EV;;n*DvbWOqF6;C?f<3?s5Q_2i<^3sFIexU5&hubl-&PI1-3x4Vc&J= z4DtJfG27KDuBOD5Z=g7MaX7MrvvSXHlPD03kmy+ zJP$DMZpD_*-3VbFA=(1`1QU8(xJMhqn)AZ;M%V_Is`UnAmKE+Rd&}?#f`|Fbw}aro zKU78#fpP)r#r7ZU_e6R$U{+$k}A*VcmjV0DM5t=t3wsJ(( zPX|iUkh-1HSQ~h9j8g@a7j6Oze6F?;J_` z=WiAs%#qq+Hfsj?OK*LyP@o>0H^hklXHgd}5$pzI)&m*WpjD89VzaEo2LG=3!bFjS z2yc$0X5b2Mt_d4vdQ(n!4#*n$u8%pKnylmQy9>nh7cL++1jLW{{XWmY%nG;Q0Sj;X z=aWG?gm>N%c>@vhBa0wWf9JZfQx|#Hbp$MLL)QLiqPfS!67?!&K-_BgVy)3t*P73k z!{4vPsU9nJUY@N)jh5^2{~>BMXq9*s+MR#%e-q9fL7{&BBYtLWqvYr7i<8EvJ2Luo zceKaz<;`ayW&a$(=D&IP|2N0@;`eC=5Z)U=S8^MLe%TmhEYz%~=FKqcLRZYN)>fQY zmzP#=v}24zAwV2k;w{2knx0l{6LUyOrb&`#n;zFJm?W5TnaxB@ub~6$EoAU|oJu!r z|IP!#_i<(NS^ZG6`gJD#(VJgdd%s4L$Zx|KVHwlJJM@Xh_52UDqjKfcG+E60)HW7@ zQN?0P8`Q-V=!J!R)W&*2-+w1_ezt;^{*uxTSMSvOtdLwUJ@iiEzM<&=M|IiwdkRKH zuLVw!lg!YQA{jcR5_YR7@5KO8Sw5ohAW-l&)VXSX9?nhtx=0w}SNtq@feIqz# zEHI!@E$@XYLtnWCos%-i_6c?8xBZj%@o~D%_J92be^~ z_xUefKb7Hgz0E>aGzRw#m;NtsnB40V1JlSMtL975b z#HXyd_xmw7E(R?etOZL&gVIPi4HO($R&+Bx51cX;1LMrHfkM?S-u=N3-q!7$dUvyz zCG~fJ(#so7SbZvXwtv?sZX5Hi)N~pphPsW%ue_!wU*w38LD1IeLuMjfJEv^V$5cWU ztf)-r1da+WJmF>|8+C=lm|@}QVVlbVctRsy@w%I(Bjm0FSn)3(Sc&>H9Y&5YIev&< zVifZM+PxNdg{t9M}D4b+zT zS+&g&x&=>Z9|04RH01AdlPNvPfUVM&)}G2b7Et4^^VCt??~CWslEaMGksiOEzpt=K z@A53?aJ8<`Z=G*G{i01ob<#}+8RJAGIg*f=RoF!vvGH6s12;dyja2s?)=&G+7&~Su zTF4`7;a7u5w}c}VgI4SQ#v=l0j_N4zQQC0hz|-XtUqxCWLHQP|1DPe&-(?%O&21Jz zySQ80|MVi&wA~^p1()gP?=%wPymkHQwbzSwWuGzo#0;QQXYnN>ULa@vaICb&sO|nw z0~3h@$bpP6F|RcXt7QDwrVd~|-e2u*);TVOGLtNz^>nZFUA#?_4HjCa5#~G&Il((< zk$AEL0-ey-KvZx902oum36iaxRbQ`e`BLG-DuaqhZr`N0((l5-`O#y)EI(Q#%OV3t zKh3KBIcZsA#$%c=wefDF_n>)!ky$_^Ia$xJio3$yZt+)-M7Fd-`7X8=i{-TgnUqy* zye1$#zIbKHB+PG~v()ILb5Li`$-ps1Qh{l@_pjAeiW=8i z6S>!2y3Dx|ZYA!vZiUzy#R7qQn+ja>Sx%qg!a2+GdxpMDOxMK@mPndbP4x}2`uHJg z`bAunpkG-ZhQ&vwfnE3+TDS`KfKUnfgG6dcU(b=>q@#ncM0QUYsG-*Q3d7saac5%W zs1KLE;=l(HCex80`|EHnvvWNelMbr;u63FPI!?3S#LPWpG|)D(7~temQ&pP+#>Hf(s;|X36Rs z&7nG*TJm7B@Dh{yzRrk)GNPUsNiY-eOKKs)L`~_r^ze2w>o9*kuf6sqM6yiZ=9b}T z#sa>N*%v-nQ8Gq`DNrhmy{i3eap!o7&JPT_rD*@E%PA$xr&rT)gc5KIJcEst|dv ztzbZl|KU?RD2fm>v(8-;3&->DoX5zS?#~<7fm`zEqaEV1s4e}O?G;Qj&(`{fDwtH2 zqhi+igqVu@RJF%G!vl=Bz)b@YFoLKZdC~E0vTmXY!5T`QA$bX-n$_V9eH+(rZ^zH> zEs|$QMoJ?HXvSn#aTf9jir&-B;-GztpV-m_pR7xzV*`iM^?7l@C(yB)jd`uEGc@C9 zLRkbR0F+^(#B7Fb@F0;}$KwRT53_@W_6dePU(Ff0;i|CmBBXvA%j)4VV#hCt{K7d{ z*l2-XyYBuv+Q}$?MUzG7q)RJ*)kKbk>BrY-2&m-Go_;^Eu$ru&P5VJ`VsCQdSbHNI zALu#0&F7Y1BKofL`wu;Kw@ICOmDh=lRFu>XM~w$R=8rYNK$=B?!Gqrn!;zK_)$Wgp z1uZN2rc+mgjh)}5Jjj@MHxm*}+^;2F{4rZ2(f8;0*VwP;Y~k((cuZ<^GfSAF zn|nMgD_XNA3q1la>VwUm^ofzJjj?CxwxAE%q3Z?=Uehrv*KZy0kPmN$wTr6bNzBsG zDn*i4eE*s}MpYK_WPOd|2t9qy`(!9F!j9Z3<@?2Iz@Vo=me!!P&#zbBLfs)^3|kN> zj`Tv0ug2*I^p1Nw^8P4u+CxN~$$ZfI0-Fx0V>#Oev-~4mjg4~AsW9M<54i>ja{~1D zC!DP#?WJG#Y-y>f;ku4FpK;>~?AkYpXRXWMnL`V&wyrD9Ae#T8onx-UxUFs(R-gh?SkWhB>;p z+!e&U(=Y!Ykaaec4m(5bmJ;*SnX=cDOuE{eGPcDq=W z)CGY%Y^CN&7sARE2z5Ouxo#%&_p=5FDqL5ep|6Aw-C1aPK~ZL+xi`Uv%vev=cS&5F zSc8S}y$a8#`4oj7)+3(YkX+>t^kLc(5N8>v#+M7GZvE=>D;$t)YL+y$AaR9pf4*)X zS7AnyU-YG}Q0p8O`>ogOAJ!rwrLQWRk&@KMEX2MojxWy1m??4a8A~D%mHFfLID54Z z5h|c>3AqYgkFwH!McC@vsZTQcTeTGPFH^S1yWC1Y<+4MH+j@Zi)hj*1!$?=XaM-V8)KnVYD$-fy4to zt^vn#g6pza?jf`!|jNJD(rWSUmso-=ezzS6S=UhvIWnEQ~I zN4Fcy&YCv6G@~DF#8Aj2AjP-am?fo$NtanBL|?|L)paG$X0S&_?V~Xhc^;%McwA<7 zF;z(vd>tev79u=sKZ}a`6t8 zmZMOqS-Q8oQ0&3*n7!@u6HU4eD#4a#9#J)3q@=?4Km}Uz zb6fY=aG7QumMUZ+#}gb$oQseF;BoyJURb~zS3MCG4}g$-Eq zi4z~JePCx5pT^Uo+wdrJez!HRA3x96!rb1xJ-R4Gr_tKn_p=%QD>LOf#{$Bb4h@;9 zvlrL0_0hCUQwO;{hafjYPaE=dJD3eQkTGt7xRaj@vy2{|0Q8GbV^Jv_Mwxw&Vyt|M z!bps+fog&oC1ant;-%+#0$y_N91#2RCRMzcYEZ~4+hWBUIL^BPhx_taeA{Fc_q@7o0xFllUaaG9GXGJ3<5;H~pE@ZFXBJu4lGc1g$P9B3K^&C;PPyiV zW}7r8k#8f9-24t+tB72RPM>|_lENq6{{e6~m_IW~I1s!o+@qeO{&j z)Zs8KJm*Pfw(FUkSzEUl!xjEOT|TJxAu(7sdn?d_COd(YNA5e=@(3hQ~pZO&;xs#lBr$Qw(obUDHE)puFo^yk_YHYw^|U8kGMRN045 zwDcK}cdu);riEVp(n`01Y;Z@9$m&{o?Iw99$Md)@YVo~(P!Pk1CzSAr5g}nx#PP%J zd4#3=xc&#M;2=rjBezBBHOsf$g~u0t`qx3d8knTqRpqtlOhk3w@$xHgauV1NEm1{y zTa8K7SM!J;In#%jq>_9-$63$L9mms!weIXXC~ZKN5K3(2_Z<1o>n|P^RQSiz<}F=Y zKYV&2fG^ql5P!<9>7d_?%7nb7W!Kp)!H2A0{Lmf z!lM9P2MW7*C?B55-h9crb%=zdlTYE1;Y34NCt;_IBrfp@vr%O6*##h;t0=L2HY@*( zvvr!7GH^ZOaJ`PJ!(lD-;hYDGb52Y+c`Q>zB!u*85SUCh-fgqnq5mXHQ| ztvRW^SW2#<-dZG8AP^bxvFBQ@5l+QSKYhMqL;2ivp1ohiE40Bj?p>Sj)OsSQ(bG16 z@$6geh=h(XcxP}ZMEN>*w`}ZwN(ynU?!*OiSBnXsjo6#LE)EEcrsSxV@7LV=caxo2 ziaPJQ>#>(5XLZ0YTJEt&!$8a#=X=Uo-Q5F;irNhYG-|L@lK_~ZQU~y;OLvHN^W+@P zOikW)g0Uw4cF!yOc~|TqO|=tl*XEIn-;PTnHAEQf9@Buw(TOtf@Mtgwc%+4 z*x5tuUZ)>YFMa~Uh~-!&JCwA;y7RoqG3d=HVMM|xCAM*1AE($qQ zzLUTXhS%TxBuRu=>%S5LC>c+XuWZ%cq2odCw{k+wG95>SE81QS$!j@6_G)UDcqo8u z8ljVkiYAdR^CpQq;fXe)cH?#RUL)p)ZO9B~xEhgkuen)>*UaO-7^=Z3Ub7aF_*Gr4 zU^PpQ0$`=GfgOJPW$)Udt-qQ#H$fYz^t$5ibuLecW>CD*B@_qLD^?*C*6-px2AA%Wuk@IJhF#PHf%H zDaH)I?LKBHzy@UL{;ljFtvRXT_+6DBhqS_=p~RodEXs&{m zSIzsx`^?(!KTKTq<_{)jX|r3M!qkGbM%uSwEw5$MSr~E*zlZ?O3Uw8k|C|znU!Wb) zf>|*g!S~u->I8Q^F}o|jnbm3yvTu!4`c|LLz|(_4@Q5tKHt!|k(;1=zrh=)EfGv&E z6A~od3`CQlcqWriiecDmwB!YBij_A+d^f^cbM#V7E3s*Cl1$li3)&Zrcj0U=a9={r zQ0q2^nwgxTSY~=W{4Z$kX>W|@bMQ=6)YlBImTUTO@biV@gqZr|Ehj_6UE8h9f+|QKhJ`#SN{qL}4TEGl_0b99c_B`&(sq!+9A@UPXbTR&?A{ zu)cbXzwuoi9h6T8xt0e zox7lqPHL3PrYnLJ@t=DomRA0MIZZ6a^Z#?2Xr1`9-VeeC%%|=oh&`HB#WdS}7l$ZXf!hm!W=Z6@l*{A$T0^Eg@ z%of6omnS3Zi=73z(4hy`As|qH^)(1)K$A8rptDmEZ^b~bg45R&UC;8{c=7(}xvCcK z^EZ(UBxKWjoRYa#(iSMnl&1gt`v-zy@d>rN10RRPf_~SUa#3_i;p1`%A4K4-ku*hk zt7?KFLIUdcybYC8#8%bvs^8X_Ii}NKAM=ovSo9921HQbyP_UyHlsM<`Tg~zt1kf4R zbm8SS`);Dd`|nEqQvu&S4U^h861+uUHJPcq{naG{HN6tvimaj1 zk}$u1VAu29|M94?;t+6D&nV_Pz)<&4RP)9rV!Gme{@Yzc{$x|;mo0OPq8!p)`tEtt zlvwe>rt?c};Fn81;pxedW#dN}d9TN}5n*pyXpid|_a66t%0t;z^$ODY%iLgZ(>kiJ z*FEH;J>A$3O&3ik&v&q&X(xWOiifI}HqRD7QLHT=dPJMf-3HRGFhn+b*2^E7f1}jX zQ0|99x}jjrbI=AapHK9~cD)da4slooH6^SDWrwKCXus0(_B_$=;@sOLwYH+Lh>0z^ z%-Kt1WEv8W)Rmp%B<0+(U?8`Phb0QqGe;8=fn=j?l0O+B!OGEG*x}Jua!xg4Uv&Ms zb-d8V*%H}fYmniQ4bD!urr?*`<|2n?0HIxHETp}v@y16M@``lb(3scMUOKs_27@vd zYqd5tTMrY#V|W|r+jwH^So28xhiihCl#NHWW(tn$He8LxKI<)oRei4uGu(f>l|Umy z8V&Mh%$6>>R;GxCU_v!lWdP=ZLm#PnwV6X=b;3rWN9Cvk{XG9cSFIAa4*nn^zANKM zJj$R=V)G`iM$}d7I#>!@PE9uKTi!-|0_!j=tMHhLS+BL8jGZX9TpX%dk~n0c1<)Pus3+CyMr-Y9Jfta) zRqC5MpS}KBoW2{$G@g+Aq3k<1lyVzQf`-+EZPR-sD)_ffu@+ ze9dOMiqrKye{2SAb&A3#zBcTnS-!@x614U*5KsYA-~qJgZW`V>bOgK2^|%s}52|-C zRV`j0xi3$oM-rmuoC{ymU9kQV3&J-+Re!vE3m2?0>q@lKj$&7E#KX4u!OZfrkc5|* zd?Lnzv4qXy2~Wl{?gFJ@R|m=AJun4WdA-R-k-N;xJ=iRnwE57Kbwz8{#f=XNv}wCZ`3Lu|GIquI8qCX zQ=q*ra!R~^>?z?;NSm#qv=HeVA z6>;OKomtaLi3DLq*xP+a+GHW6h?r_9H2sE6ZjP{yJytJ(baw(qC7ip?q;={ z%re=BqxL{=gx%Bo{af)I0K2*ih&%cFv6HS)w5)%PElf4yJ@d-_zSfqrqsH5g5x(`> z)8F3n`V2}vl{%D~xga7VU^?#aX*VL5J@ySK*x9p7v-GJpAnpykF&S3u_buVeisV_K=#XfvJSz~qxI(>5XW=jAzu(#F z@zkFj3bnE*dPc=F9&>yee!QY9->%4^AE}5@eXxFxpsJf)cmKK!YF!gx{&`8cIczs9 z$r;V%z}9Gf#MJ#8**v?_i^`xloX@P&f@tzTOY{`};W($)wJd#feo3%s1j*oEJZK&= z@jk+;j1bWGA^hh_7}N1rwueo?$^wGN6FQ$7H4iO%*ByclwnpYpz{ zeS_)K>iCY&wMBu^2P7H6svR`urqlHDKC%-*Xm~!{^%FtcAXcHBvHfWPI}0(w@9S!6 zh@r8P>*|}K_kf#xNbEJTpa{e$G?K6bUh8uAh6#Azp$e)#P5a4-Mp;Q5a!ff=k4)Ir zIHw)~0pZ@6o3L)3)k@5s@ae!JKQ?EpU1%diw;Qt^zG{~HZjOy3jW+$j>sdbcLJxoxtsnl8QtV z7adP{TbCJV+uWaDWCC_Ly|@#+e7GBnV%N9ENZ?+=&u$ZpxYzg2&xDW-y*1?w33SSUzv9=I?9m)HmiI^a=oBDs~@E|m21Uj#?tD2vP{ z)syKAmIw{IUTziiQ@cHTA=quB2i!3W#L}Pf&HxydE+uOoZ>*i4yPs3(3V(XMrJWy? zg&Q2v@N$t=^YYlMTxhu4r^G#CqcsD3US4$VMB=Y3op6*Jf1AC~9hH>|`ABz#Sa~*e zl4N#s+oDP-*!UZ<0hza*pFFw*3LM7?t8x@Ia8PK)=P`?ROU=eW`(4C$NcY*Zb1do9ekFj;CU+f9 z-5+M~5sE2oR>)j;oHFD0zj|G!WJ79Wl(2(!{!nes9Q#B9P^XaQ?|%a96L;ZMs3)E7 zJV*z#_U}+1O`eM}|80-g{&##e(|gc4f$KQo>g#KPO zshL(PQlY6eA4MZzkeXlwu%s+R(Tv>eciRI&W{!&LzU!N$vTrc9SNr(6gOH|R}5Lwn1 z{OIWDF8n`hq2k~r!?m8S#(1~jmdl=J-C!=dvytf&$zwA^Z1i_ot5&GElZLef0Jy4p zDF>@gQ}=TaJL*Q8yQh`Mdg?oBL!}FC<{Rt&Yen5D^0#%mI`6+$yM=*2t?`#Ef3``v zQYT!~HYqe{#-1ANhUA`0D~wROFD4X#sT=$3Hm3OJ`MI*AH`Q1qKrz3|Td7`eV#}=vLN=9Gr!f0Z1%m88PbyQPp9) z{%*7<0BNbu?8MV-^fLeDTbA?wQ8&z}hVF0{68%VuAiS8D%aInUvhq?BWgR76C-(ps zsBs`Rv&4VKK?SaH|1(ar%sCcbtn43+9 zARD0GSgjr-kB3|*RJ*evxjO}dFw60XK?_efJ8d}d)_IGbsyl^ibJU(?nfDF zV$x>@6D3+!Ot}DXrY;`ok>58wA^5sJ?}g3Wf|7m?Jl67B^P$BhyTzr{&2v0UKUgDP zx;t6^T-V9lt%f%vmc|~;I3D5A(@C=`#h)Ep&0?J0N$Mg=5%pg892d@-yd$}# z&-T0?u$wAWgUu1K!s76<8>?EPn z#Z`(F*I2%@U@Q-iY*;^i<<|3rpjBnHbcUwhPAPWBPsW$)lJ+HHZv)stj-;a^? zXP$UC$z8|bn>h2QTFJ+t7r!03`=RkDVgO}e{h2y}Le}sk(}sq>vQm&v!ML{NpXk(Q z|I_@;%5qp0WeCctHWyrYDL@?Xs4XwJbY+Eo&zHw96l)2_zsnJfMLV)#nc&Fy zR=LRR;Y>UF?1#(}RIEYL@{Ol3h7}sii^3#?pfBs`1~;>t3|k|;mF4FX)g)n`R)%qV zNwHTJT~f`}Jp0w1>R7ukb;o}RHt$2}U?{;ET|q%C4B|0u+U0A=tf45#$n=N=T5LX?t9A|!X6m40U%QGZIy$U)z|qwp$x znPp~Tm%*sB9#(VO<%D-W-|>Wr*RE0J2>k0T4x<>Jf!3E=_^ZaO=5YEL?kz&`ItF&G z_Jur{K=%hwCs=K6-~-_5hYJanv9AWG_To=ea>|X`?Ogv&Z?K?Hm)0Pz7M+q0Q z09$r%2SMurP4@GE;gfR)NASffY4U~G46hQ7#=s*<+rQ9wqDk{tHKo4L1m$(6`rb{3 z;x9x6<%e0{eggfsrMS>ZSh@SOUX#a8`rZ<&QT6Uir(FXaTA0Lxw$B|Y@eDUNp_scV zGyDe&`aXt~cTkLgdhr#Y`3DRqy@nl_H`E@|LqR;Z_%Z-VCC;FUM{D((L~tfILSWtO zTBn8971`~kRFdcQl+ub+2{6Hfx@?s?70|1QI)SYww~3j>?~9JNvxLU$#X~YUVgd=Q zP7(d3dL{#u{(jtEa7_`#^|$47`A~S`PQRxkC8`W~ne+L7LQC^#@D=n-`*^Q{L+UnU zKTpneZ|@3~UOft%*xIYjaJ%w@+}|K_eaq!)v*T^#Pvz%=#x|S$P8yeDQk2I9sQD883*bGIb7KLR&_ixX!^u3&gXC9{MU**yBfzv}D?zPZGgb9{! z;<%npSn7sniJ9pht)TaxG&p?O@dB=(7|F(R6RX*VP8j_DRijG9%d*s`S?hIQE$enl zodxoqSI;=yueGRnr?_y<-U^w(ukf*AOQn3FefAJ_-gKV*cC6Es28Iwc8TV$mZ8M&G zvaYdn=WpxyIgg=3Z9rb0y=F-(wlUTTe`Q;vG^uMv+e1X{IigYmdk7^WZG6`rfSW9z zeD?vbfV>(&AsOczRewYsMxuWOW$Uwd3ukPt3hcD;GUWCMy$G;jUWb<4PjtnjmH4q= z4fK2lr7nWy(^qFYbLYSZimE1eW|QHDe3<#}tkISAm6#Rl+`|WJIU3tt-FSUh{!Yin z$|b@Q2%^rHgI|sSZBK3tbtRjuW0)!urGsYPj&}aQL2 z^W}gZN`Qh+i~1gLPCi54iO?hiCXo$>j|7;IXT?)$Uo`U47zot9Xy#SR9CcpDTYfpa zIvO4=jHbDlz~OU!LZnk?X>uoMqbu@c6LaKK(e!#{J`(w%Z9e*nogXcvOOdXKlJJFRQXl4^js-kqL_{H|ts^K_D^d~14wIIa29giUWC=R1 zgL%j(D9SN<+T%h)A2Uy72!b@QJM&}s7Q#uyY>M7c>OL~EPsYM=Xihln(Ujs@U#}3P zNV@jD@G+!WDUtL%8Z#oLjtGsp+<3>k-JOC_8ijd7+ zEj)66ufIov+7J#VUS1M*cJ|PSh)I8AL+KB3&i#q36_Xh$-w4&$rK4ZM+haa3B4l1A z%m7uT$@e(;m&+5Ifsv0vRh&HhC*_W=g7=?v)0j|dd1!b07568O`RM&5&_C*@nd z2HGk#=7V)v5p&T>OVUBgjEeaVBA}{#-deyH&hDniKj8iS%8iv<8=>Z{>qo$N{MI={ z?uYC<8P7PKe)co#3L!lw@)C$)IRm$lkx&3HK%bQ+YUk~JI3~XO$%-WreducA69iwz zQeXwekEG!(EzyVp5XmLlE+FGb&EQyeKZ6G0=@$o{Xl43(#7}7jcQgs{YygzN` zfJOs;A}aHJ%?mQ_Tblt3K3u%UJX%`}Y5Mg#z5gyllU8_}%=}yFg+VALYBDD08d0n3iOt`lf5U9Ig zr-x3x9|^_;U3#<37SAvo{MBMIg&*g~PB%@M}HH-?uv$uJOrI0qK9k>Ln0ob%= z2v58SrCO}(wPv%2TlCYZE z+O4~_G=f_e9tj=l!m1T*crzV$Pdpv@ZBc~#i;DNx*}+PuN@?=)YHw^g7!3aOwwf75 zqjn>$Isho?}MT6E+kLI}T5_V`HV%?3GU#U`KohOF-cdf|sy3AQs zRF1{3-QNkrc=UhU{C;ldiD`uJ3}&}HpCm6J=2jGsgQKpy05Uo7SEMu&bM%;{3nUIl zG$%0b2aQ_uotLmy+V#BRR)4$dZhh;!;r#WbgcteB%Sq4rwM);#_berILng1I1lbTE zuKUhcU1f)7*KBJbIgB~|Y>3hMJk~e*J(9ZI7NHH@47=!4MYRMeY)s^y-rHSCzfhSn zqwYw_(aXU{x(40xiBT4~Y8PCg zj;C^;Z;hBu!cq=N*3q}uB-;Uc1ot9F%Xb90&2^Y#M3|LaJBbqPYT(N!R~{WUi@184 z%{Yipz50U7+!+-)wqjmAlr@dHe0ZuOpHGt7cT_V{U7nOHrzXMK@sFEVU4V|U@I#R5 zS1X#li!7I#z+w0#AM9mAdzvJsls6~S>o)odY1?j-_*p1ZBvLv0{K`8CRZV}$guit! zAg9^PbJ+b*^Necb=uVzRpzA=%74{Zd>JCH#9$G|gh+3MKb-wulsoZV7-1Wr9=u!fN zmFm@_>Dis`{Gvlah&ckrxk+o-dZbE=k*DnZ&3$lZ|L#j*SroAs#x-E)EqRp(*sR6t z-22cb>QO$EA@tx=#bxj5AGcb)p?VxiqWQ z!nyZ9Yp;5+VAlv=V`H{R+bP>^#`f=pB<7?)@DZPj+`G&w+#^|Z6k6w;z&!IriQt~n z?D-lqysgQS0t_9@2JzQ*m9Tq&w1Jn?AA%gZ7oz&{&qwTbND@K?&Mrkhesp4wAya}n zm>3N#9;|d%8T>#y{yqAjKY>{+Q^?Cf5124mf;7UcHD$I&nGWkD89BF6cWwikgv)yC zkt=hj!j9{5J7=qIUX&qtl(tN3hJp+JSX~QI+Nys=yGRk6ALLc(U|88;WEppTt6zc) z%9NWtMuAsJ7)DQYb&U#-Xw(V{3_Oc}L8oBucUoLhK89wq*y>eB+wGTt(xGE!X8qA; zK3pbHjg5_=39Uo^HQ6dZO+hKBrwvYH9v?%al> zvTe9)O?#sz+I0eKlrd)N@n6^V`B69mGXAj-6oRqw&rlbiLPEs)UY!*WFW6+IpQY{fCK-|Vn9Yt?q@Qm z+1HE=3bv~?&2O~aoSdqFHjk67E)Bn=B;uXcTHKJ1B$fBodfJ+h*CH@Am2MDw?1kkW zMC?fqTC+}6>nCC|1*;vw?Upo%Ais|3ziL=cMiB6ck|oKtn6dJVI^`4i&_`KGRq! zRB~O64I%fG%;N|Y=tv<0pk_q)QF}msO8{2`uUlPOeXW+kOubjBvs{MfpIsOgry>?( z{s*z4AlrX1PD6=XI05+cuXTo=4T_rxpGZ)7ZfP}H{0E!e^DX8P@v2*kZfNjHoj% z+Eicq2m7HL9T^4BgwIF}5Z65`D~R}DWz=_WG>;4th^*fFBw*&5i2}6I!kD3wQ551( zhqb39Bt=0z?e_1X4+?mhuK|ei%R|U#Z1@`s3u%*^}$`Llv6QuU-qc2%6Q~12(vlHoJ2vH zk2GSoh_|l~bj2klt+=yji!9>rxtG6rD!Wr!^eqzkscXGEZ={PPx3cPl3;Z1iJTvuj zTq4O?Rb@6m#~-T+0ozo4{z&e7EL_$zE~Vp3QfjFp(T+s1O;U-B&CE|In;8N@-e&}o z`RdgL%)zd!Bs-K+ibs+JUd|B)%6 zOY)QGAV!i~Wz?Ey33CbjlgJ>76C7O&*>A+!56`@RFig?7pvAKX7lT|zejb{9R%~x= zO;_Pq4iWCFiJ{VegI)~9Irc~zFYnIw9175p2k^j=LGff2E-w$yo>Rj&jMF-J>eyV% zw+qQ1;=T8shPFPuxa8i0Yumi5aJ&oh5|K<9C)C8Pv}L5G7PI2fC418&^O%6fxfz21 zJQ#&?N|083eI;Ui-!jFo`>`8CFY!{3YirQykDFH)=J#KHcQL(3UAzBjI%AO!W`O_d zCv>xWjiA2PX1(qSLSSzVSEf>L@!^XM>mYdsU-E4u%#Hh~Yd3I4#+3Idy(FP{-34yAJL;_3ChZ6X|JkMK+O8a$s1yHLu3p7~VAm10j-}lPbdeG?O zdn~AcaO4;=WX2p@ui4jI;M?U-n+5UJUt7FDolg25wh4}j>hsTU9qq?Gvg8@5b!)p$ z9{8T5mOjy$&8|B?*B`W-XPM^IZ;x@8&&|u1u^XD}KFn^dyV$q0{aM|TQZ_<+a1Xb4 z-I)C3DDsj|8q>b_y_=N&M5gPJkLqt{!!9}Zd_z}V9lFj*61?iNCHVZn0vGt3 zcE-JV*t3>mX<9Ke;6;Sdy9_{UMF?LA957tDZADH&!y$gA;GYtChFq0AY_nPoKvz$;YK^L{NN8=}S=DvyYb z7J%bC%w_vr{!+h9dazfInINY2UY>e60ryYzSMtj9v*WVaX!-QZJeCu3T)|<#$ReB8 zPrQ_@c3m017G*l`j1(wMg1ASE<*9Ic>e1X=D7Y)SV`pk%Q|8S>yRUS~Hkt#?_i8-v zfSJd>s89jyaM1tYWe*a|Mu%XXTS?}C_f5{HUpJ%-L1k>Ah4%j+5ZM9$K<7QVN>Ad% znYR0uTdt-b_Njg_KI66`&fU=gS=QFRWDL;YZ1{d?1L2y)X%LBmm5tiY^Ji4%N!9!k zb+!ad*c5&!WZXwhF<{~CY=zTDz9ykdEvEo9Cx4^ezG7Svd}E)VE3`al-f za?`S~$>t#VY&Z?mATJ@3*b8~76NYe{2-%z1XQPh;QU~7}bAE7g%30b~u6u`EmhgZW4FhDA| zAn>?{u+E_%Ws#`^kk-%;%vFlsLKc0XKEe)H{ag)%DRb5`ayHDZQv0aiF?&$I&9^3* zzAg=fL`0Fl(5%vClw;fP_fLxD)tTbY-)Mc8GT+ZZL5m+i7HUzc4C2T2TIdM_x*I;VZ&x9FoNQR zO?#OSV?K+!sg-wk&7f5J5aRT~IY+k3=(Tn+3=%S+B8ncvw%7GGYi`y zYBF$K8caWd3ItE{5@;vM4bZRvqDlh-fwXmxracDNJ>06u1M7trvXQvlUWyE~ z6rC$GW8TRa+bZmj7g6=nM*xXEUJ{5fpfouyD+gWz?xnl>`x38fjc2M=hBFZlJd7;s zQl~aU0V=W!+0ElO?#LKW3xx6W-=jvRM}?sJ$=;(xsbMOs-L18`xp|I(LAv--JYE?x zEv=sKeTntL1&@AkevK`>#N;S92&iV{y-P_lv~CXcfyVvOLt-?*b0ndtySuu$Oky<^ z9wcpWva^@O_qe;dF3Cq5M0cJp8CqUDtS?Nc%Vh?Pia+)B3ZvQlwVFSrt)=|y&y8SL zL`_Q#3v-1idfU^|5Bpugr7r6qjW3D}IX4Nlo0HNJJOn!fn(c`6(OL*V-gZH zfnnk9r-bof%8I0fIPI0U&b8>uYU&BBLv0RzIW)DHUlF@sGohus_d-Zkz=!$Bq15H3 z!z%i%5u;|meKRaKuTV=BF?;LUE-Nhj!p}sw0Kfxy=={Sa{i|?u7j=_cJFbV@YfUpI zhov<8Ad#+dYz(s;SN3(80<@7_uPGjL9|Pk)YmZv6)6tRRz7_O;VLym+*om)6KDGGu z(1!HQhvE(Yl9ssNLXaGgc{PXO-JHB{G~|+vsU3!(t-r9YlDP|xCYI`d*56%?2pJX7 zWjbulhRPVmyEefE9F_w zocdzOlD=klMB=!TP4hqYh{TaV_!e|`DdPQgGAsO?MYP@EimQR0vwPcW7i?p_=Ywp3*AvZ-Jjs?n$X9S0$+x^V3<{2q?Xb5u_VM-+YkamF}1yZ7{QO8*`$ge0e?t+YVqjKyy17*l(U2`yb@sXI|1FOTIfNdfvTt8~5C7L$9WUEKHqBj5Yj)~S1 zb2H7<;ft{Q(zquhX3ej+6n;I`L5|21)_pV&Xq6~se2Y5&gk3X4{xR3-?SRLiD@7{1 zEd3x>5Ut%H?;Q7K`BiB1GE!K)LA_F5zsJ>XBtBNH*Mt+c{t&9<_{&+j{@2h*GHUjN zFVFK~6G`yAi_*ZPC(j~T9-_UoB6?v};rr=ZgjtEG=zE(o)v43yS~0=kZi{K>9I`|F z6Ho=jY2tgc9l;FF>qgh!s4`N-=8cNvyj|k`;k#s-d)A`796?I zFFPQ4#`{gtrqKXgdIJg&XYUO+97{oFTNI*jTW}A!bZ~`8{w+5Z(De=*psoZiaY-br zK23va-&5fM2X9k}e6gyWCBoDy4y3{gi7pF1aU|&A@-=&SAj0;3mNSoBWjGJhz1@A} zthHYGmDa^TvatGh8s_!qm3p8htO}OmSmK~P(X6eL*h!$gUOZI5goH4f`(Y~;*=#8b zzQB4B7GJbZWj2>{WK+}wS@p(g#cP2);_u-qRK0r*G=3QV8{uag#}{-;$#6GO$voyC zp|Z5u;_r)YNTl2OHYcdww&cLQO`9EfR~Pt(9NS+P@T9U^Xsk-<4x(8>j)F^-GRQ%1 zhCJp1-TK~_2@b_!V-3#fH+je?RLa6TKsO29J(I&5JEH=9*&*t+(Kb{RoGr-xJH|PC zQHd~O1#?pF!+j)=*muR+^j`yhju(uo7>w?QRqTX-{J0YSs~Ppoqbhjynrr+oI@+^@ zbp^Y`Kh~YNmf<85pSthKRE(n;4noO%Ha@MDQ?#r=`pq`>)NbOM$@o|jMbZ9UFSfsy z-kVX4WIxis9p5nWqmb5d&tkdZO2O1N0ovT?Lpj4Ue=E0Rn!_Jc{DiQVq0u&LF|&7p zu4K)N)!%j|C@y=O`wB$^|VP)!(>zD7qC!gy%kMGLp?#+~r&l_jDa(kR(kmpY5>%4cD@Z!MEXY+1* z9W?LidF)njNu_4@7sg}ouRQ^L49=Lk=rETW7sX1leSP}Rfj#ux&x5Id9y9JM`mS)m zVS3j{C)l9TzzJEBF9qV~=O;L^y0%!Ay8YK^sDevVT&wT2J<})0kg9*e5}LY1Mn<+T zd($wMMGAKW7uH0c*dTcH(vs`fRo_-5S7Z*s@WG{~(U*;=Wokt-{1P*{6JWaOif%rtR( zJ8ov*_Q~I0=S}7+KQfz@-cyWO&kkXm;*t52!^dwyFS)o8qhBVw?TrqG@Es|KQY8NX zFCgM(W@chuz8n&1q&ERdcVR$@4#CY#J8K{;ARaeoGS&OGnncum%Lm3)zfG|y$`?ts z5gQr#UQJ}kMnUNOFG|Xdat!l3wM7Vegy=(wBkk)OD<8s_q!DK!+{sF^PF)9VQFzoHYcBk!fe6LF)G&bMZ9tBXOK3UKJ21XkX1&RSyCKh5x*VT@WBN3TR&kLH$ z7vVY022B%DBV?dUjMWE354?fDqpd<2IGqqJ1thq z-^3ILIk+N zkA#=^q)zh8w|X{N85vVJ{$5eiobl%pk^?2QnxClK2!09vijOsZqqmrN7K9G!zSytX z98N{G<}LSeqTb^Ts+G6i1S6cX;j`o*MaGe!X%Qc(!GiU0jv}cVbEc4am2EU7VEBFl zI65zv;4xAl9hG~?&q ztYt=Kl!QIl8h?>3ahSksbGS^=>GS9_M?BkIpFIo@k;upKp4*Mvf8*O+@z5UdIO2Jq zZ7VTD{oBqOn84`z6)2t^e!)DGn`KO-Ib%IIjpg+r$`;}+2xCw)5`Zereh*f;c)>^~ zIh3PUJXEh3s>(-y8*n}S*hP~0(;?v%;MQM&drVyjv7lF94GZg0W5As8{HbEY+~mW7)@5p|*zr19UDk&05Z$mh=1*X}+)@ zU^j&PO@Rsjl!HOMxAZh@hQx?7?nR0Kv{I!@6*#=|Uh@7z4GCp;@}YbQYi7N~HW-by z^tf&=m78U;g2Gf zPr&CBLS_#H;*NUn_qVug1>aH6462*IJ_h=0T zu2@}$(yoHD=F(W*XJB=-Poolj#eQ{Fl!h_SLD(7^Hly^(W!fvX}$Y9#=5+^+u=#y)MSS_6X=X}BI P!nLH`l=J`hU90~AeRM7b diff --git a/doc/modules/ROOT/assets/images/config-backend-edge.websocket.png b/doc/modules/ROOT/assets/images/config-backend-edge.websocket.png index be8f17ed319b9d75b674212f6d0faeb791963ec4..044d333e49fad8b564b846238ad457e5ede68e00 100644 GIT binary patch literal 24509 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 SqHq%vUAbguQF77a@qYoo`r$DE literal 25264 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 z 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 VhmCzQMEDt(w2n+!b6u9Stq|{cQrOfyco`U9M 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 cT$g+#pV8YDI{K}l)`;IZ*@$tq$Pm7C#p92H} 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 literal 12730 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 V(dtjvzwp7W(G4@b((Cu0{V%!G$z%Wk diff --git a/doc/modules/ROOT/assets/images/config-backend-ui.websocket.png b/doc/modules/ROOT/assets/images/config-backend-ui.websocket.png index 5dd773897d9ba56b5fe32b37a09f6467d266e415..ae23d91b8904b571c1c22a366a296bef9036f9ad 100644 GIT binary patch literal 24389 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�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 literal 24987 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 diff --git a/doc/modules/ROOT/assets/images/config-controller-ess-balancing.png b/doc/modules/ROOT/assets/images/config-controller-ess-balancing.png new file mode 100644 index 0000000000000000000000000000000000000000..86cefdd24cf699fda11e0861a18dfe639baac848 GIT binary patch literal 47129 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 Jmpn4^{%=()F4O=3 literal 62452 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ͥ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 literal 16558 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

    < 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Ν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 literal 567524 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%0D3Z35}I~v5uN<-rM9P0>Uhyv9}gIm4Xlw2ps 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

    {OeOAss$}0r*>V;v?iZBs>r+Q!S?s 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�UFnksUW4)WQXZ}9F% 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 P00000NkvXXu0mjfv@BjB diff --git a/doc/modules/ROOT/assets/images/eclipse-edgeapp-resolve.png b/doc/modules/ROOT/assets/images/eclipse-edgeapp-resolve.png index 500ec1e6ad48213f9adc7caec4bf58f75c807011..f4ae8e617b916b4f36d899eaa22186842079f84c 100644 GIT binary patch literal 71553 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

    K^Us9l(A@;3;9m7uTMq&@J(@=rv9dfejmPSlo z^`ePS_XBn!+0LAw6F;DW`59vaw9=OL-v+uo(%<>ls52BiVGDYRveU16%gc<)KJ^LZ zf@zAsk9^}!Gj8RzC~%414IeX^jG`rY%Tb*-{@3|kn^HR}*KL0MUC)%Po`E77<3Xp=vjC%RDh@X(@uTEH*Ql*J6}xdfVHc}msh zNAy*@=2ohuvHDk_;XfqpH>^Vqn!ZbV?^@vS^WUy<7k2{1MBF_ZQp>aUc{90Eu-1bw zVX|xH7`X*&t$ghDr+>y_CX z^1Ks9udj+x8b>I;Q1hVVt~m6#DZt_Rmg5zzYuk%b;wnf=ws~sRwv~r7)SLVMo6fED z{Vy6W>28e5OjT10JunQfG#m#B?25e2SX|t`V^A)55PygLNiwEaKRL<6J?9b4M%BBx zaqFksw_-Z56R`N}DRE&v2DfSWubIew!NXDtUtPQ0EQos68|;#xIuaZ;(hJD}do&++ zNS69hAQ(2jj+PYn5BjJ1q*X<%2EWl-hB#~MyQ_}-IR$Oipur>iq7kHY+~xJqMf|5Z zKj+Ghe!;~a7~C#~`uH75bbLGsjvj`Di;sFwr>Hne4gilitDw2~{HY0w(U8cmPM2nK zwJ)$>thqCMw?hn-R{=^*vrup{zg-I^V z`i?dE)Bq!9dAUeh&a<^>wQQmX^p`82jfg(zC?#$U)2o#;t1YZHMt8rA)$_X@`RTF7 ze(7T=o^cI!;~Vmd>8KL<=LMHL^;nH<KR;Vd$>^pwn<=`HuKcu~~w&bf1qsnz~yB(tLR+e+aJs~tg z8jpP4(&OeYD%_B9Z)8;<+jI{xr-YS#6?Z+fcCZ#Yg2XGe{kq%e@{u zII!-MUHu;SFr5L3S5>`-TMOE$s!bQFM#OB9bSi)hdxDj)m;I}Zh+p~Ysm1Cf$oqTO zloe^EvZGa|)%ZSPW32odDqg`fUdKBv`x2Uq_7nP?}$ZLlTj@uN9H3PFWTKnO9Htv%ssIGY9 z#B9t!;qfm62;0T4f3%?2bw)3t@ZwZ;5$=uh!Z}g;sW6ia=kC669HlN9?eUCv-(WWT zLk7l@M5Y}f%uZuoe}3Kh0^JZEBt(Uhl%p_#r|6{#i|M|o;*y6Mi+^X{?8JGSO0{dL z1(3Ef!z#I}vUfGV#pAG}h?-^`XBATlr9@0(B1a(Ne}|`-AQn zD}p&a^ELz4>BZI?+;J;I`DYW{JGaVyG9xUAlymZas(kBWkBzyqAFeZ5Qcap$*_xBa z$}}gUYqKp~DYK-Q4tKFGy(bg?>`~l1BcBBsxhZkhg2z^ew0tH@&V^C$z~DWe5eDA5 z*2qa_SDM&6uYXu38`og1=jbIud?-IT1x&8*g<{esahQ`G6+B(rd|6efo*)%v6O zUL?8VSU`baKcX=eyI7%Pe&n}^s1}iZ7?0Q$QpDfc!PVEd`9_R35DCz9io)u8sPkzT z@e)t(waoA2_eMV~jI&xYz%5xu>~Ly(8DO3n>a8@6DVgc_&KvZL9>iq^4PofpL^s}8 zEvbL3&TW)MNmU9xuXDP~xbb|EU`FNixw#@1Q>g$RktQM{)|1P^FDa>o9jmUiT4Bz* zT%>Cjt)m;2G`!vsV`#8bE!dnT9SrWK&IJ;&(VaUXwO}TUG<%hSSl8vVICRlAC&H z1FK}!furr~8a2S%i)#uk=l56ur!hU#IT|*OE=8&29~DJxpTTGO`o&`(du6?&1(u-RnD`$qS}VWA zpo5TIu^k<9bwIaf5tuntzTPl9cK<$V;vSxkiuJTlkuxjpiNv?3$TSA1UY5{&EisC@ zT+gtcQhbUfvXw>|jx%#pSM*l8m+q5MmdcE0kl4FnC zd{Nm{3H;hRBQ3&#&9q+I)F*~+)J9)a$b==0p$jw^Ux=?+lrS}py!dVJcak7-O@|eU|IA+hEzwF-a$$m_iqCuh*|J(wI<>WhHbF z_L7r^J`1vMH}&&w*XK(@Czty)Ch<#O{D=M2hb2^o6;fWb1tpiR3_eosOfIAz$@Y>h z)N$l#Nx885+X#fuouO*|aP9glW?N(nWlr#{Sj%3NAhQb}e`AUFr~TXR=bnt_$Xzc& zZ&ogKabLKco2TA6u|W2OMeUE3JR7n+%#)+s zMw2|1W82fY>qSO^Ww=gaPM5iFu0|vuer$q3XLm ze|N)1Gdp2_BT?f~ju;O&w|c%l76bx$w}^^h^j`(tq<9Db{OztDp?=?GSNH&FyRzFb zeXQo(um;Ovcyfh^k`c&wV>=%NOgM(*UwAAlE9(Yy+qzyi@6gl%2ccY`V`^`2Zwcf8 z-j-pgyuCtxwo=jGJ8g8yXE(+b5w=HnKU^<6P(_2+prbkZQbUT()7j#ek?!!E2qhMHbc$)g49Tt#-V$Q(V~I zJz>x|$!dA50Os{FIagwDX0Xi8=5(&8-t4f3)I`+4a_Y)ytkg@wny z!@y#-X_yn3Xy}lMmE_z@4=ki@{p>LzoRTTSA76M@ZTF7r2CCONwY3J3T$;qt5?J8? z3tMldbg<>uH|M?ax9%EpSMhdq56V?xMvS<{P+=&=aWt=uSf=-EnpXh`#}StNADyb8K@VJR_TXF1yxoIg8Z zG^*{;?9Lglpk@W`0WU9LS&9Ng*Ht|_VO8u$M0sJ;BElr7pNgW}hxu)FWnnq}0uTemCNauAf-Br5K=$4`J?nAi~T~E zEV(Ho5S<}+Qn_kpE_8QyM*}JFKXSE!r)S)PE+9b0RoG^iMDq$AVLab{g7bvA;=2nR ztCm+TBop3?G4Wdz{EE>?mLOTbVzhzAWh3sv#rPPDrYLpymUqV7J@dy?401Hnii1YM z{QEAb9kKAifpX1JHJBwjMOI$F)?Y0+!V#VPBiIHGdN*8K9%k7u@S;0Cw#xFz5&Pb8 zIPCL{(T6c2rlSwfr7{Do(~4dEU}E#`Cg3&Ftzz{nGD9(wxEzHVhV6AqooF(vM?46a zK?9ZK>$Y>O=X}}(RaQwOX z#__>iPEAP$6UBZL`~F0L!bt<$2EGL$75)m94Ij6rG3Q`kaM?x&)NBI}OJHH&S0xv> z7wYGE%MKofsJW`J3669(l4GrAJG{rDM0D0$YRkvSu}MdE^O_VQtb)Y3x)?#I(q4`) zHdo(|Plp+~9o@JOXD-~(r5iPE>_owgjBb?0hV|>(?RSAKkd>kyRUGDx0HX!P2oa=- zGl>tTOs}igy+v1F3W}~CJS9_aO{d_77 z^vbkBD8{SqEXi&Xn4<9pBh0O4HY=+QL^1Mt%>)Jg0 z-Wc_03mP*Ei;Nos2Sy5j9M*8nB_@V>`ywk0jeUtwsi(&C{n=ybgpiVe?~-y*u?GJ- z4zoJ0=7}6IiyFy_1K~)1zt}hwcYU0pyp$lr#Rc2Z&q8b#$Y%-G!S^?=?nCE5$0En> z>5VYqG06g>~xJ^@EN@b0-Vm2qEOq?*2p8*x7%u$MNb?%0J$sXsmGn?vk2 zr~7Ry{YdO4ELa>?v2m{-6{7hf-Jgq)pzgC68ecFyE#{cqvgO~C0H{AaxOEIvLy^S=q$bUrA_KFcMwhQ3fN z(6}zGOgS9dRQ|*4TDh1VRl>a>#?+Bg`s$n?eyI##!@1RxN6>99C@f`h^J&C)s}D7u z)W({aI2Sm($;(B{km4}?hAdt224)<^DsG+nnZqA3WL1>%Ytz;>d}>a%xVGZb z02=$_0S#SY?*01dN+VMmZ=F(uQ>i0c-ABH)YG`;%F59l{KNGX^h#c7Au&ztMPmb~N z`AFTDnA7ouiufTv18J|73orve{q4S17pj>Qru2?i>r*Tm zQ4c^W9(4nwkl4O;D*Y;n@561`pJ2mMc;5o2b?n23Cv_dlDu%9&+mVwU(EGw0N$z7+ zWwHS@uQ^4JB0w(tMSr{p4x1#1nKT>eC-urqC@>@1py6srPmjv6Mz+duJ>L9y^-D@# zw)2%@xy0S|NwL(aOIs@E;~1y~7d% z2j}K`vyl-QOgX|>K;HviThI7%I>S=4kF(OM^|Cc2gPs>UQxJ#ro|b`0SO1Y?`7rji z{vKqZq4gV3O?{SLS&}R?QV{wve$Y5-o}{}kVFFXROqF|x)J4P(9Om(tTg3kqJ;-%e zHn6^lD@{|%Zb^1Wg!j1i_b2k`Md-moaeVp2 zp|}9|=npP$Z;$_Ll65!nb=s;eB?E~C`p%_CLp$DkM-UvV^o^e^{^)*Xb(d_sp0QLX z5MLQuM8OWsPOqIcYj}+jPB=A9bp*7 zogW-z#qHN~l{lsM3xq5#ljqablEnf_I@x|+F}%_40R4vHxvwLvZ)1UZKh`JWlI!No z)3hl>j$-U?_zQFJA}JV{?}DB@y)2)4hZ)225!rQ@R7d3WXvb=QeG;GO^`9xzw~rmU zm9~KnIv*iQ4WM)mK|5W#mw`>TuE%$KH(9l`YM)(Y&&ttC2>vTLYSTD^Z$9=_3Q-t) zX;@;WksAF7brwHG2r(GMjO3Q7EiEn}CTd4(j(M@OBbN8GfQEJ zR7MWQcsspiUG&U(4v|Rt;fi$77unn}(rOhfk)gITOyX2O0Q)N)x=T1xdip=6Lw#rI z(4BvzLr#VNMLJ~iFX_EQ8i{k4+H6`U1Wr`CS(&6zQll>Wmo5Oei_@*s?{ z`Q-iZxONs+6}a5_td&C;sp@jyEthFt)058Hu>&epWj9*Tlr3nk$^5=vk&EIjuAC^~ zKa5>=7~ocgOjVtG^4RmkEPuo42?5!pyG!))Dp6=dwtdp|va?fJGp^JnnR8%xIl8e? z{18Aq76dqZwFMF3tt%MhKK=N17nE&4sxsCR(BixK?J7P$3`z8&Uu;7>_Ps@=_{bNT zjzuelAT#bbCL&{Te@Xp{LA^L7IoF_DN%y!6ALfpP={3&szE%z#wq8)|5cg5GlU1Ot zLQywzkb|U0U$V$(vysw^9n6{rHBrl|wy}0ay)3-#UZDBtGSvPaLa!+=qJ}twLt8m4 z_h%8hQ44!tKQElKQ^p>q806rg^ddT?(n61)lx~dd7F%HkQv`9fM~-i@V7@{Ew=we# z_TD@mrpm84xk6ylys*$O=)h$!+L!8x{TW#K!!c&!VRCkis7oq`emX z?pMNUaM=u=TEl^XP$O9lr$A}e2{g2wFz3C3_v#df7Vw-Nd+pr5@%(_7h@K2OS%`|U zTW432XMhjMmkGgZF|WUD`;~_a+UlB5>*^z(jfx>BvP5{L-^E22G}pxpWi`h`rzwgu zJgUoi(f85#-uQ>uIV3^6oM^NrUXN`9AUSYz8_rw3?0bf#7!Vyf0@jhO*ba^ClsgP? zW(Cu$Xfjemff`9t!^R!GmR602oHrQ!^6=IvI7PZ#n6NlO#TWi1G23f5r1$iwuK!!= zYWlR?hRbX$lxYmNffFM6aEe>XOGg`*q7MVbhIbm0mu|P;Y85-|x{p27c6nW0equJ- zJnbH?D?yHX%f}n|G@j~oc4RzxbH4+cl#YMB3teVk18x~~4=n(`PNrd%bux1IFhk0m zP3m~dq>60gY;D&j9V{35Xe;|2+y6x>gdum^wzu+Mu2}dMsRr}c3o1#&+$t%!!y9I) z(?+({sH&9s*~|dTIPIOg=Pfqmsn?7!wt9z#e4_hjAT#vpm~(=0Yy`6J*)pKQhYPy` zI_wKhxwVWK=^dduBulQPGso0-;~q@1DE-)ZEn3#TYwJTb{%{gpd?Y#t%FFiX0DXfl zuc@?|Gzjmn zsKx=E7`Up?WR860S?3@l>KX}i7l6n_@jN9#;M;dB9cCXzW(=&o9FF^30BQAPL24y< z{Io9GwlZp%ItSZ7@L0YMS`JZ@ZvhJT`9a3=lTkd!Ys1>pyBe#`gTbzTF`;~!;ihj= z>Su0lY>|jf{?2-#lL?O~@%;UUJgOP8%2a zXnI!5Dt=&Z=ma==fiDH!{K@&8=4P{Wueoa?uV&ti-E{$)AlqlFjHegWZ=XSF0d%37 zbx+a@*v}F{cpR(tLaKWokMXY`|GF|lv*9`je8K^3CDSve#&NMw$UBOw#I$i|sW|XS z=Nph<5Y7w+Iw4j7Mhb89PKRY_adBnWzNxHCZ9|_${`h)d6utBU!0B3hV?O?Sw=U$B z`_DK2`wi3GPBoJW9+$d4KYeeBs(-1-x%7-4K!#3>yE95cV8NPlWjxRmTh6EnEZShY+%}as&0)r!vFyjTW^V@?%zd_4XK1PA z4;%Zz21(>jh{pg{@rrsy(G)VNgPa+fDe#AOt>=X!NNP`h&#KQx5SZxEN4H`89< zDM>)OhfPn%pJil~UdJry{ZE?%GFTTLup`B_%cNuK+o3h#hs(zjYm{!vBmU%NnXtv7 zJa>w|ozDwu@I*B)tM3#~CSPVt^hpv{KWqhg_yCVbxMQhjG_&R>pb*2*{iY2+ZV%vp z{FOL<3V}fuOXG03%uzER&RsmVJb20*TN2KHYQ%;GU;B_O&O=xAsEm#`?Xl*;MghXrqLtrd*L?VONE79GIWvpo zO*^4FfwbYAZ797(!@N1ksp4BsrTT0yQMXxaY3|}&jH{Vih6c=^GGRy0WtGvC!*Vk} zRj+h4Zvd|?%f=9>Dc7j;r+2PV|B&g_%8Y%if4Zj`RSZ&hEkJm8Gh0{h7LPBWv`Dz9 zkd!Zs0WGPwzzh=|DGj~}1q6AOZk`g(igCH?SSKsdSZJ%oh! z)yxw=j{5Qrl;1I0?<6u!+sRPCfCf9{$Haj9A3nnBiWg zk$Qg>-)hx4fDh|NpGi*4fm+OnXii-Btz>K|K?2?+5FDN2mnH^c$_d=& z+nxN1$}^+SGsLl&9Pdx@iDs@y5#+A_czEp|K39FDBi)Or?iCT(5l|fxomVJ!rV+jt z_zWK)pwoFRsGv{+W6g)5Wx==tXl%b&U^cvu@xx?BxJM9a4=M5T?h;udEk?YnOw}_f zSgNeUt6=?=BOGPb^9fu?LZTq%`#{zOlIQ$lfd5il>*z8dV z1{@D4d|~XTo62=}uE*oDK{n=e#t@7565T^AJ)PN2%`34mo&-uQb7tfqV?2TkzTJy? z0(GieZa1*^T5b)j)Q}o6VCA$wKyUZXsg+HMFD^jdA*tH)c;-qv6g_>l?5{^@=Lc4H zhy<8{1e}bktn0>>+)J41Z&j>h5u7sJpPM9`9T%riceDGJ6pkIEeEso{UyIJyfJ zI1x`-14UctkPhE|+3)s#fu^9`=>#pQYTvb@ER>IUL<|bK6(;Ofzn9ps3 zNcT&HPgnF}#pNr*B-GYkXM_pxU?HHxZ-j<)&qlF0xX8ksd=<~U5v!11TG|_f4Si^1wWy#yk>9hhLb5bJU^cz0 zi26xVo+&s>X}=?28P@E1w<-UlAJLWDrSg~5hEMH1Lh8p>$y*L&CGa^e)6Mu37W=pm z%8;y)G)-nqI?KRY60Ee}+2HrfMYhZ!CN1sN>l|Hrg%nUYYClOOLa{*!ZW*XZs+xN? zNshG%?8)GSB1l=*b0RtEq?4x_QpLE_tRkxG*cTr?M)N{)_l6r3{ux z!yCC;kCcb^I%&fUGuYkHiSSTpbXPYX%AVtKVnX3O(o~1id_iNLO3znHTaDn1FWn(C zhEj03*XSs`HG^UFIJ7F5B+ZX!TPpjMGO1%h0`fSmYKzKh$BZ}?%9kS~#^%kc_O00l zTv2sQXXE3er@(}VdAqm5x}gmfFeF>~veUoHnWn`eBW{YHx2S9Y%$#VCw=IMUfvGHj z@$zLRUOBg1+rA6QX`2p@v9excwLj{E3%y=C6LIJO9$x=fA-Mp%s!3LWzY zGMJ(q-u&-nvA8eONsL7fvHUjuUtsF7zWo;QNuu9#!`5pqI-PUaaqB32tXS5r~X+l3NjiOsTFwGK5n|Vd^}=sGLQt<|&p` zvnEd^rEU5Zo0(NDHoCTXb{l;!1ZYi2T5?p$tFBm-`1M*XsTOYt4N8=LC1fP)RI(J`^F{J*5%KX2U8_{|uKec^#Sd&|`uZn^dwiQ7H>CzRXH<4Z=1Q4Wcr711aA(UW6 zMF_nYX@LNObg4lRfzYH2NGFs~B7qPHB%GJ#{?6Io{hoc#eJ+2I=S}8aW!73VvwriN zjc%)_o57KUSDZ^{RKP)B{bA=XO|2^x6?2wXwi4P(8+K80WsP-EmVZUb)vcB*VuuvP z=g$bw|)T!~Pm_JC^| zm4CAJC%n2I0KWuI%rp_D`z({BzK(yz$YCenJ|vHta*Pv43Kz9FEuzH4!|(O2zv&AT zO!oF!jbqRjyP4@tBj=Zh9d%B#=jn8QlhHNmG$$zJH1#y*J&bQdxQ+CO`844$f0=fl z>oS0a7Kj3|!d>s?)b zR^G4H^DWuqjCa&$(F@!k`a@HW;&{HyTnNYlPSxhJ5gC9kSR2$P2`~l|UIREeUG~ct zc4CZ4KD|N<+1FZjE*^$YWllzEyyUTv52q72@m|-XFScRa)jB8&#~X(PNHdTk$mpSH zsLoExK83t)l zZsC_)rK=-Da8lVlU;K%_Z)=y?Ij0*;=DCf?9iaYrSm6z9ZO!u6G!q?jk7nF+tODS=_DU7wix+) zdwvz2#!g03LY|kP1PG2GSXQA-H@%1XRf2Y*XTmOCuDuNH2;k*7=I^p|#b+R0;^b6M z#TaOS!I}I)3DgfTi@;8jK}&LvX8WzrFfMr_?IdYZVJn%?r7i3m9C ziqDwn6qvd{RDj$c3a9l39Zr;#;(U_kFSQOBoHy;?AU7kjS9eef8IaSPXug8kl;e7z zsV*`(tHfsWNZMJwIhm6Pqim44-x3!F*a3#xXdsQrEUWVsJoJb$>v$Y@u%>nA$^PRP z#G^dsWsDuVa^5Rg<+PKUWM#FIox81e5OjCoizghTirl$s7z!uyhkjLU#)4^GSv zU8smlkrTH$8Ys!NHPTz@A>Pm`4f1mQbgZdGfDcyE6vB(_!ewgLR}aQ%)bf5HOq&h6 z-q&Rj{4%P?)g+zYp>_9GseMNlJoq-OvAMl?)cNqyB}lnjOpKuo^kw{}?lhx!Xr6Z8 zii`1j0(1tauPqB^X>YaE37eX})XXIN@H?S=VTFj4H(I*u8C`_rxv)mlGduHCEd>Cv ziENPDOd4k@Rp^a`&UnZp;zwLJy|&j#QM4 zQ%NQZ8!KaNo}82gon>Whmz7}~N_Q&4BJZBGTh!%bAW?V`PS|fqE_k)~F&h%xqQZZx z3lxpav^fc%sZ`d!!fV9JcXC=&Lj-syEf1V=X1Q7=8b7mgnNdU2;c3JJnMv4xRnO= zM%b9ygGH4J-qv{l{RI74PW72$Q1k6qW#l{2e>dHH=UL+c4T&2sn&3lz!xs7=aD z>G-r?lGikb6M%5HBBSm4uXy1+5a9(;eLuTzX9~o0PH6S2M|%3END}2=I}jxZH+@MG zFHw_IGXn*+%QI}y;d7r*!(Arkq-;oQl8?xb(JwJ^*GfGTdT2zzFoME>>`UQe{;(7% zW8>`Auo>t1s6Mi`e{8KJ0t-~2PJkra^*m2jNh_PvnD^J(A1l8d6Py)ds(97?%!bCM z&&jdY@a2W#j#oQ@F2n8_7{wR4wc6;~cE4JD#7i;RdphpL=kpg=y~du>OyOIE#7)kD z#h)3lgMi#f%%FWi6yRjfTXgFJ)tdlv=-2T85)VuN7>K#n);c?=-es4#)SUfF;PGjl zY?*#^&#fMBa^7iU>0TUl`5Yfjl5$NUGGeQQZnP9{Ho|*;a9ssS{%;Vy(J$KJvfRxH z!#eN%Zvv&&-0OSXh?$JonmlDL!KRo>T3OKCtR-qKx4L_!`cfWBI!8u+j;{lyJ$qSp zneony-F(Fq;w7mD72d=XREXwJl6w|mjN z8)$w1e+;xXi_8yyA(a-fU3E7E$DbR%AZCVnA7P*w*B{#9(H!%55z0O?GTEKV*OSUE zb(K7**pU?Pa^BswezhD7^6OTt0X%k6Pa%+z1||^%Srj&_>d`A<`S=oMQhAp(;j8`e zq#S`6JvcU~#+yIj-v(UiMWeoIJuYN!;M0(W36^W>rCaLP1lhBpoVvLXbUl$=1-@N| zv{ESqE}f3@Md)ZA)st*i{HaAA@pd7LjNs-WhLh&9h&Dwm5TO%DAJhectuI5xPW%`h zE|YO%Fjah%yOyXaYJDF?v@AI>c9*EzW>oFvJiEib)`nP>Oj5Mn4KCj9?;l2Bvp0)v z%w3u}Z7%Lb9Gzrca`qs0x266QEYLbKCMFO8N^vsm<{n5)Cy}7u6K}Y8vSn>FtAR_y zmU0x?k$g%I6pnV+roe)tz$at2KzOzSbk#||JeWiudt2fMwSHSz3zOn z2Hss^b9V1j$ldE43?P|hrC9(um_HEd56~?~0wL4pnG~2{mQIqeNoOwsNR8RS%g+Ja z=zAE(;n5v#DhO;dVTVGUv|yx%l%-|wJ5RjUch1)D13U}{Rlv; zkZ~nd?zOFzRjlkRT3<<<%<4y)%Mu2l)X#Qf+|Z*0N!ywBr0}SyOqRGf*~zjLyU;Jd zuw_|8zjt(kC)3;Phvg%*7!0j0?FGZf`o_WT6TK~i%6wCqR5k`H zgO`*{;L=yOu!u6DaOM2QL1;0bW2FmfN9t|*V*lt`gQw(J&iOVQ$h@`THwojxM~2dM zBx6Han})tGW=^WTsQ^ICmGl|<&cx197wVD#Tv{WrZVlJ&glLf{zH#!S(AMjr@%U0h zf!!u#nN8|3+~e0fdveZ4<`tt#bNkn@2#@2x#z%8H(&F`aJ{Y?}pC8R(5yoeD)Xvcm zj5v$(>q%cSf$PXWh!S%S?5Y}1Z)x(SEAF&#Y}GzH>ch))j*dJu;1YTk8800To<}Qb zFoy?fD53k3@~_`X=TDLZo#efPkF8qFLJCP`wS9K~T=#~7Rc|{y?*`b|@KazA(-R0p z^ok?F-VFTb>K1*}y^0>SVh7)z0UKvb;iE9(Blvpts$rFr(1$a)Q}@L!2n4+d5e+;| zf_q2h)zBD2i6K+4Pg)mLd!rB@G-wX#^=r`VW7Z7t-)Uq3vm;)gXIKhk13lyZJ^e-^ zl1~oo<=d?U@`)=o4;JHzzF1^1jWHgYKxAGIPP04?e%11eN9ygL2(;VBZ|-_!rTPf! zGvVtc#-MWNvcMr*cu}0Fy{Z1-YaKh+la;ftBWwnYIEBr$tv4tfVZpg=`Uz0u%~_S^ zxCbEDIKfiA^zI=Q9no%!>5kB6@x1e~VZsIi(qKzONQoX&#?D&bE9aeBRHIDtdUY$U zL3v-;^p7KtM?MW4Wjfa{5PxDSJ3zT6>fT)N!$xgitRc!+Y`XOZAx@~0;U)&@Zzl*( z3cqFKlz8`=C?PCV&Cf6Rg4SKkv3Z)EuFnnt$q1>`JUlJkEbHvcr{}3)=5dj?VjuHe zWDA;j#^?W7|2xM{IO$8bIdeiD8X2m`6UoWz*YllL@z{+FCB*6S=s-|mG(+lCDAiPtWwRKEE% z5eEuSjEA20DqQaskZ#eq$1GB*oGEY@z}RdyYxmwXeMNv3BQ3x0&o1Os>+nOrF^CbH zXakp5*Qah0|09*a?6Vhu@PobM^Q52SG2h;e#me7Prg~PKm8Vr`mwuXF^pNGq#QIIf zs}Jozd@X4=N%KE3YIQF-&zY}>U8CB~E`bXBB89y@I*+M%_B+djD@RLQ{y9uQ$qv+{ zrzFB9i~*|ujcBdsz@U3lOKRm~;1i{c$a~VKYjqJIyAX!Cab0mfGi0fZnc@63)vd@x zB^9(6_}kN%oD6UMT87~?j=9|ll869sHo4IkfVf;c4Y8|9+p@RbK2q)dErq<8hLtjO zpZ8i5J)x<%?*rOP3!*L}+Liv~<%@)*NY#Yi)udnA9#_6%za5e)Y~z`IahCfzCtsGa zSiTr>;A0PD7B=?ykIfH5w7QcGDwul%!kG@8hbQ7dMgc-ZyIcAUveJ;%Sg096Fuq{ z*B@cOei+U7qu+kv>>8t0n?0n&dpNj^QE(?TdD6BdIK2~_@S})4xKP`i&+MFD&eex> zq|FB-uI1k&@) z+q`39X?AtboqD1A=3QjiF&mAy%sCG*skj7ub`lEum3I7B8(-K_cTKh6wJ!4Xe42x&-e8PKsx8U1tkhu4Y9;t2}VZ4>O z!b^uvu)}W3kCJIR>jY#L8>6C4Mur~g_t^a2EL_^NIsI|IKR@)#ugHsm@s$(Ihr6G0 z$zMv&x!}~I6~^OC^~iTnW`ot3ZG{8Ga*yxWVUm-H(2CJsd$EMvg-R&`?h7kRZK}1DZq-u6i zZfLFt3Dz?+6WF;~9XMBzMNkM8D{tI6nhhYddzFH|Zyet*zO>%7&|i>ZtR*kHsyh6W zQx-CkJBCczJ?CaCp#&@^U?beJmj#8E5lZA{XUno<)dNE$<^Udn9m{5vk7_tb z$`%_oNPfJl4)aogb(2wAYA{Nlo~7%!Q!aNEH#+LD+_!fQ^Zj)PZqAEOXY4G8Sdb5! z4m;ADr7%98I9g3j%aqL?z9Z}VFtS5pCkabFpff^lKD%Rt_p-}a-C>5C50$w|J^v0sL$vDz;iAoyL1`YCZHmmwk?Hw_z9K26{wM*mFz+f=ELiTC= z;!VH~Y3}HtyKsBKP7zbMhHj~d^*n-6%zYZET%GNo3$UqUbNZJgU| z!F~dXo9)4`eE%}OmRGw$fk4ep%I7>TkEy1!#d|mT-(?BFDDZQx0qh?)CiZXl>Gn;! zLo5s+S%UuTQWZ`t_f2U@iP~tb9|pL-v)P-l+r;l#AQmzV{08vew z;m)`m3dnbC3T1TxiU*SG#f^=PZR{WNj4~N2TlJ`&f3{Z-?7g&Z;pvOr$E60phPmv6 z65dy_J=x1`8B4!Htz#a^0$UEkH^er=EpAoq**#quSKtyACjKkssAeEj=8C=g)SM5D z|LdMjdt1u=(b&}LCv~S@c|4fA%3~fV>A3Tk#OBDZ_Is0PUP#`qG# z_VCKo(+6u@rHiyg{bXr>0V%{pxvU2XE`pnBU{NhIhD-Z9vWc{>NiV&gis2X9mG*tn zZBRd{GhMZ1hhJ~!R_3RohS~boDo`T=LX>rLzH)o3&o;#%Oy)CoqMX1({6O^> zBj3h)tPc`M_-K=h>UXhEu{Gaq#CsELNga?(75&q_-^`{B(bu~+)g!AT7WCFw#t(OO z;xu&_{WhE3p|_=-%*(0Iu0sRI-%05zoSh6GH{>$Rc{$0Ru3^3vRI|NgvRK#n(Zq>9 z3Ok4T#Kyf5^G2je897usB(mRKS$DfJC1)DYGa7BC`l+YhSZeNqmM;}+`f588~^gIOwxm+P6=JAPF%8l)kH7rN|`E9gCqa|2yvlsKVrOfyjx z?G*n(HAw<&ofyViAY{tC5tT#_6F>o=l!aKdsLQ56{g=bh9~1lq+l5xIm;dlg>`hmT z9PW_MQq7lOzkBX&^Pa>+hAz2@uV{@=| zkPhZ1iu@+FzcB`gsQh-o+BxEYT3jKDmba97VL~*-R zOWxiBa1#QgC;O8gFFEfmm3F49PPkDpZ638JeD<_|;mMuHzlC21gztNYA}4-tDu3Ud z6!w>Jo$9oGVC%WPk@(`VIN0!d@8^evfCJ{*!ScocggysCs;z8o&mXv$_|G&fYSX1u zZM(%;4;}iVlPuHxTLeIDl>^tNC@{`|(*Y9>d|C`+85zd2`V!r1JX-?K2X)TFx1IJ6%dy8mT4eY}d%og9t3m>qp=!d#bcV48 zd(Hl2O-sTLANbf#KliKA*3tZ)i8%UOD9Zz%&}$~ilVb&_fpjBx@Apr#V^4tH_x}Iu zB>6p8e~CGmi~sGHRHxBz7k{@ij(8C+8noX8%m3Xt%l(JR2z(nAbnw_~u;oBKKn*K1 zzZFUGKp}s3lI~*ACH38Q?km9qMP*?eAG3YmUQ5TW*TNI(_vUH+_d)bOHQ*zaI564t z*6KilRKDE`M*!aUBGj-%Fp}Zw6A7= zi0N~z)J_!I>>q9+6PuqT`FA@JAoN@R|53|?6;nfv_iTm$#Rjd*`+LgR|LpZY?WyoR z0|)VgnQuunmg(c)SNve)eu>WCqw$Z1Y`v+qsp-anL_nR{_oG*TY3kiVm$L_>2l8P! z=mOG<;(IU{{(m2g)Q6pH&8EhM!}}}qnnSkQv|oee!N$YXKy-FB+S3e+zt71(T0>zB zCttR*?a5%`wfS&lxI@qWHek-rM1HHTQk9H^qYisM>qM!$xCE8+}bWi2L-S~Yh{9SXIc@4?${@tqE zib(lwUH`l7LVh!Dx7Gi$>|AY@jf(fHb*L_D{ocxh4&?uJ{`_6#{@%2IvlbrsH1Hmb zsX6uNjfexuF)h@>2jfeBpznVLY5!lx_xD8o9+H2g;{WJNVHG(Ks}82yWV#jDy7t4y zPFnlP^S}O?AYDVlOQiKlcpLAYE=Ia`$oA7F?wY{+c19%NrS#Dz`YzgAh$QTN>-`p? z0ES`Sw+zhxyFP3koKIO30N>@oXtNy5!S5;Z%3dGn;BYl)@Yr|R0zRg)#fbzp#*6N~ zMdGck%m3Yu@t01E`spM~ak`&@MfBTyG*b{%F28<_asu>H)Mq3t1Z%IW<}>;sP5tMT zxprUnzPK7Tm9h2bumaMFq7~u`MLQ__Xg5o%QV7w9@tKsG*66Qt+gq-W9UY@~5~X?O zfq>8CPuGqph3C}jxRIK+=rIaV_SQpdY-+KRI5psIV)d);RK~ALU6+B6tiof@R@`o2 znmXZbx2@;xrQHS@8pP4R9?)a0ce+|Pm-&oqGQ}a>dy=|^?i5XPKyh4bC~cs;EN8B{ zJpZW_NLbrwi#DT-u0jMrPpRqQQD$Xr4X}kilD`-r1<(`R5*2L*;N5z($r-Xw(vGlq zIQ|eUFc3$RM{BiJFn;Qg?bY=YkL@HmY?FFTT#9Klgj^3-8xCIsQ78q%8;JQS#FcNe zjyp{W=$=^;rJ0JZrr_(pJheI1^?3Z0fZl+Y7G;%gz$U1CdfJBJ#-9PFnPq-BC`cje zngOQ`bIM}<*}yriK`L&{MH8#hKpLwhG8hBYK$}X3%SRQ>5vBMXKbobI`A&C!Y{{mt<6MpD*xJhv|Fn#D)DZi^(o1bn%kb~3mct(6gLb3 zqz~3T*}7f1_dY(BRciwP;+X)VzIK(Y*{?yyGICbcsab2~o z8>pFTTK<#B$NH$tS%0()Q+x9{%EbcMnt^=sipYBoQN~1o8^D&(7v(*kDUPW@iO!Mj zc-yxrFci1^24pn+?DVUTylO?5sH;+o$7Yh)iS3_^lg-MFaeH>`tV7HhjqZ-KD7?q> z+RyX!fmWK=FLc2xUl9ql)taxG#33oyB#o(7@g>cZs~(tPqWtMB6m-=ZIwXx->fYWH z;#LNPLPoL&I4uhpQ&r&-@PZK3{iWI!t_sJlQr#gs7D=0sbCS8`vije=O<}!i-mZ;R zHoH#Q`iT_0>WO&!Zz}Txx>UZ&qnWVTn<$z*cLL;)gvYQn%YA8cnul9qM96~s7lF) z8P5!j#$HzxYJIEW`gvof>2p0yykghn30NKCL?v#p^B8&RMUUEu@x+kt`p=pN?wx%O z(ie|AgDRGxGP-37qe_P4iFP`aXVp)A2`)kHS5Yyf-4N&##7%Z3N}kG{A7E7{H#+)G zc8K?vm>(Ptiwh46ly@CP59Fd-UcfB?Ib74`1P>xy< z!Elp_?Bd~N(Ifdbj&xowFM;TDAGn^h=-OCaf0NzGz7$9r7@zK|ms%VKbEBJDUO^Gk zz6C(q=-K4sA0q~hSbnJfVCU$G3^&l@LUqkKmJ4_y z67nKwV5Ln`(_+T?(HQED1|9ntxs=5+MUiiDeli{g(KLCP~* zMuHcPOlPgO^k>AbtNFyHLczZf8<+-jB^0rsw`eS)2dVA7`*n~Ne zYiWxe?Z=OcqQ3^B>lvP=b?IzGRp*JwE7q9uoiM=-W%WtA=zMhqe^|N8E^WZ{_1(j` zZN*J`*Lvt}R2JtGm&I5aZ^EKVhb{-U6Zg#3x>J7i1B=}NUobRVCjY9)&#B#5-mE0v zs^^SNXR5qDebejMj=?gsqT+A-Txp1>T4#;3R3#T8Extyd#VACy%cME|a;C0ucpLfi z7oYLGxGh(v+?g~VV1ABpTht1Ja^{I@;1<@7Fb?bp=g`Ox9@xO@E8wcsx#JeDx(Y;l zbwdrmQv1ku@4X6w5oeDHwZLsu{PAlYK`;&>c?knHmUN5gB6pjDO*y8R;Lv9=Fpi)$ z8nPM6^a8`N&bxd<(%m|%H=*`2QGxC~46?G^3&I=YTQrz^U{fNit_NAqAKCYO4ViG_ z+gAdFZgyu<+GVo}e96&;_*R(V+-BU@5RQ$GGeejPdh@Whf*uXC$QaEX6I|!CqmDP?;c#n20@=YQu zSS%*+aE2kJ%4IJ!y8`T1FH}{3a)DWHftB3c>d=a#;?^&K;JcFYnf4py0{z4=+fDbFeBt!AC{7da9zSW%=e9L39|zcM)2==c2y1SF<0>fTR>p zJ%x<&TGV1)j7d{c8^f+b&F#dsd0oyBken=QFK!{sAlD+Yv4t12RuB?Zg}9--XNbGW zK2FM+TwCUD#6*=dPW6_1qHF2suw%{0yK3$7M2Cv?G=~bC)+ZbIi{3z}x2A2^EDPvf zoZ#HV3IQt{hR@Q%pnBBX9ZBk0*lHlHRTv0c`_cW8kwOwp85|rG)9#)7Sf`#B zk)?ApXV;gkj4hDwW>c=;Y<`wd+|U7oJ3&U+`aSR*pMt89+^(qF30BLl!4t923aKOrm`+$@LV!pdOyjl0As$9%=Es$%HE6Z{2 zWZZ{GaEdK4{HEe&GkbUKbqg%Z!ljElG4CB3KU!v)M@iJ}ic;A~5MaLagWaG+9kL;- z-Phj%FuE-F11N`Q#6K`@!{Wli9vnD7cW<8m?Y1}hm*b{G1gLk@PKZ-9svkh`~0WZS{Kv11Vn?tVEyB<5{cw$aYh zoCn;`f$L@fKT>>U_t{u$=0gsEXsRR&o1M}(H;O`qAgav`I2+|iO&oiNw1n&IkEZ#8 z7F8BM4l3$}P-UVSCj-FF7d6cCtvafelRJ2z!WvhUM< z3bCF=qjS5`Ot7#_a+6AOt8#0!(IS#8td#$0MXUl7e%MB}q`qKVCt1R)W=p=@^kEr? ztC9Kk@cAOwuf!>nej{&Ye~|h13X4^`RyfXo#g5_6Mc5<}bbk}YH(GsvaZ~`Z)lf6q zZb@8$g*npXHIO%zziEY=K%Cc(xvmF~wpdIVR-G;%?r0GzI}?pFhux-hNtoux$g55M z!lB0;VP`7{2`+?yVf>a5<(kV3d@A}uzU3;yWa?ozig!CoTD8d!p=~H)Rd=H*Z&-*w z%<=hB#5`SoWT~*hQk8dX?otk!4>Lws`8wzzR>O+wZ%`eK<=-yth2DJFsvnmn@>4*F ze}L=xQt_b6`mqgisy!wlrkWdN;e9th#j{vKImcdEa%;M{NTHV3)9hk5$bU^AEmTey z?$gK|d8SG*q7a`_G+*Rn4KRVSS=4B{i3N^E+7hq^hQmZPj8J*Yd!IJBqy{03y#0bT z=F77a_+GXsLrH$fAWh51VT4n?M-@JZem&k(5^q@1?(;2*+!lSj6en)*-1x~&Q+UY`T6B!|<~bs~ zqHN6}(z<{)(g)$wk83i?#qiAII!0hhAq!{cV1>3C-u48r%WS2jaPw+oa&XI3FF;@9 zmO|;>LVK23c&a7CaOWy?C>%I57x-ykf_In+!esG{5U3PZI^sBU@m}rVb`NWXQ#(hu zyy1{JBft2FYv~N3*m)pDsdfo%(W?$Af`j_xY`5J5lZ))O-76Z=*TcI|6md`$Tlf+= z7~hope9DO^hG`lqnx1>^GEB$S9Mj4L!L@u-9f{IBp9%J^`2(D9fGY2o5uhp9e5&O_ z`%axm`KCJ6tKivE!ncqfQeC5xEa#Q9bCFP^^^)4!r4V#g%?zk? z-hjc4c+2@MaTsQmo+s&Bs#WC$$BD*~HiCsL`bVQ+6QstI-%-ghu>Xv>_crx}4;ksw z8xhP0i!y|~#aRed%9}HCh>!S{_68P;VrS>s-OU?W3Mpf2)yp~6`kSc)np^jss;$Gx zq?wJ%aep0ymNol^kHfhb?+Rsv2@dqof`w92FDLA<+HW1=eJ5mi`(>v%S|EZAGuIDS z?0?zKQixrRTp|6jaz8@2S8m=Ng!zE(B$PAeV^>EwbvDnTC|65KE9D6^&FeTX6eZX9 z{30BI-u&QRvz6|RvaDg*fZwzJh}%5N=NC#;b?kLfqL6DwTyUHzEw7@BF)fDPI2*cX z1>DuMJeN7{CHv@&4Eo=uH4kSOw@$E>qo|=j5Q{CY`Xu{N>=Q6iE7UFTf_&b@^Sob^ z-?T_+`H@B;C0-qIz3X3`8>6+3-f-!ysByb=wlTA)i`QN&3I=8eZ^{Iwiaqe=n%_=v2D^dMf6cvf(@V3$;F6M<^z~M@!xZD#&6HE zZ`2rUo1h-{yAwkY*^{!y?xcqN5xkkJmQvA{eAG}q_}49>i)D^W=Jq1I|G(QbhRn?F}Tf(aIw5Tq}IQKQC$sN5f4NeZN0_1a9g|yJzNxzL>+uc3e~i9_S2mN3p7*`$zrOFTb=SS$uvp;&a}8yp+qAa<007Zb6$Nbo z;2IVHxO$F%6Zg&1!_yet#}zkiWjR1Ognk?MaNR~$T^0bSj3Kx*yMcSY<*Z`h1^^Iu z|N6bs=TvA30ATZ;D#+@3ne3v8{O-;sLZg_D-oW2*-Ws?mem|P!Yp1Hc%|n#!u8LAP zEBV0>Ad){YHU!EZ3JzkGJjH@>A1^0j98;0EJzZYvi zX3zWmWm2T!9QBi%Mpwi27w@g?fJV!VruA??(`DM8Y8d}LdHv<4^CdT@yrPx`0Fg2tNxY*?Mg$B}mWG9NawClHJopHB$^1B*7VCwf{v+8{5q|d*$XruS; z$w}Yn*Ndr(hSNke9Tbn6MDW7|rp{Caj4u}Ix9Lnb#93AjatBs1H@BM|P8Qfq$@-)c z5%&?MIZmMU2*IPt^X=MpZ0!Cgggb*SvOrhRIH8geRTE``xZp)S~myYC0DalpQtKaL+F^82Gf11eL zwD8vhqkT%iPN)GX$`z3nY%&R;P4JxV$G=9RiJU$X@$;Qrg)&?#SsyhoMUwpPAaFPo z?Fp@br%1I|c4}UY)P!9NtULg=Je2xf0}$IuU5yD{cRGdVXkOu*V+7pW#SQQ8((koI z4#dfdMo00@QF#IYiHTNA4;q>F2_6Xa*H1$UJxo@Jco!Ibb zd?7)$$yg{w=_|WGToKM#h)K`V!>+ZDtzs&$dD_diS$awF169R}47Z8ObKdVXPyd!I zFj8((&~!i{n*NC#HupmF6%)XjmT6tW>_`LZQoE^M2*Hk!q;B_CnobG;&!3O7A=rhI z#&dedLj(+J&ZIGsNEF)S%AevgEsWvLyA+yTuTnTAya05M>Y@&Dj$MdSd z;-Vmp*GY{3XhbG--4DR48x49c=Z2{rg~a7A=e94nWz^aGgkT(|eTpxsq>mH8r6|@6 z#1_B5*G}%lov)I5$Vq{W3i+AE$cao%+pr^$X zjsFUE^|$U@Dy0A_1?!{1KQC9sPGb^;kRHew>C5vI?BXTUR-S`&HWG^fOKg6_dJ;Jl zVXrJVY@bK>G8U)irSBUPs4N;kZ8}($E3p=uxDQVRWS?IC2pFO)PF=KbLse$=^cyL< zrRlB21E;ih{6^aY+4c2xM#>9~R;x-vi}l_78G%>+7Nmo>>$86}+iT0NA%bJvoR;&l zYFi9>M#it$FUUO4@uGCD&ShPxay!NYAxLyNia6Ob9r=>pBAq=D|8R1PZ+&X~Ww|jb z!EveKspkqXNEp3~)hc@7#n*N!h}v*0&h;t&Op!Ica5D}gG1@~A77#OBP)MnPC1A4@ zo6g8xPGnb*`B|GlQByjYZA@^n7MK@(b!A17z*4Hx*yX*+_b?(8;I<~^Z>15EN{|l6 ze+{@k5YK&K_K|OWlyG_%JDA9Ub0O-hq-kh5FSAxPIVnQB7puL%MNGgRM^+(@jcE$`-AH&GvvAa}wSU%$WV zSlODGG;k$$uHst47(sFLKJs*`(zfj*|La#ff9xpnRSdJcYh-#wMLAf}76O`an6zCI z0V!d3GtY}W{E5&5-PqJN%y)v5IsF&o>!!wBo0S#i+0u`ZB)TVgAX!bRw#Yhr%t6gY z^XMZY%w65`{6@W@6KC{$lr8tPSLiC0?tXqeaC54&BZT*#@g933+5y5XYef{BnNi=}~+-pezb#A?PnkeLvmdBz^160DQ zl1J`DriqkK+UO&diR{m-d#ws&NRqnHE?G8587DkclA*=%=KEGc4TFJJ>IZ5tP~<9>P(y zLY8`eyiCVc3FqHBeI0xa`p2JBuXP~)?9)o3&Y0g)29gq|gZ^JMxo7EUr;r+(X1?C5 z9#vkwG`5u*`wrIhs?{T+oSNS`LaNm#f*a%!FV&bm9x^0Sg#7{BX?G8Td>mbYixfha zgph?;+KioMC6K7N3{!fe>awK1UKHj(r1g8vmYL!0v7yeE$#C(u=^8f0c_D8-U^73~ z$o;9`KIkF6?_=|{N$JL7rsm6(e$+Nm)8PP-7~96>Cd^$cK@x;W6%CM2Lg;QZ z-ybA;)%@31gC{+RU@0$c?Jpa0%YjhA4*k39KU+F)2}x9 z{*#Kyjeqsa5=vcQ5;_MXmK0wLWf`qLXc>h(5x-0igVRA;E=)gCL?sf;nc4JZcx2e@EFSBpEK^UeudFzJe}=6 zB7$bx>>}VF??^`C{s2Oaz~Ll#h`@IYKFlH^IXu#dAFv@y69OTDrOI1z;2q`=?rbK` zE)x1ked6+=kn4)5kV6gveS}%qRza<-;aYt|b$9z^<|IT|?jecomJ znHrucIKw8)MC@FybnU`*#$*tu?@~v4WsSnuc?(3hvzguglcDVG`Nwl&t zZo7Jq-VM>#?mE>NrSB~(Q~T(=z+eFOC$?*X&3qqg+D8(wc^C~=BbR#lneaD z3ta`!(+hc(q&JFpzF+=cWh&Im%riD6R$fx#EnP>O=X-hboary6dWogP7mETJfM;Qw zVHaT;{&oe2OWtzQI~_2Rhwf~dObUlm0~E;3HyAmu8FZ@i)FxreMFPSLg;Z~1Ud*eQ z;$eD-Kk&oza=x3A>G2y!xqsKd_gmuoyWeRKzSBt?_X*7X{NY*Tewg>Q+d=evJjDya z{?2vg6#v=^)i!?=9KX*vci=-%TDCD~sr>$`ssY`^m4EFm%w`IrGS$&&+gGSwAMM-N z_35W($DM9Ri{XULeDY4AJ=JhX-3LL0n{SuYayV68colL7rC?y->{<3j^b>Y=(2nWgHa;?$5zAB!`IBrrcVOsUsGCUQQ6Ii=OOG zMmS`iCfm`QjH=4sySaT?KUQ4IIq2G7ObPT-;oW(~S{L_i?C8NXuq5|6O*#4>Ur25D zb$tXkZivw654z|!_qq^rh{oCL*LdIz7j@CCSIJWY1T(h^OvDaDonK0h4iB}w(gtV6 zM3`k>+~tnJY+QXX96NkDn?RhXlR>`>)XeWdL~9nKSalYN{vMAC!>%pd_k!Quv_H`K zczzs;2WXni08A{-0BLcG8itvj9ZPyglf~M#IBbxI2fgF?F`$mo^t&Ck15indO~<}@ zlf>3wV9$q@F;3aI^h5_O@IT5Tnpj!Y)_bQ)OE$5{Q@QsG8{3L0~B< zQZ;H>uCMSDEmsZ3aX;|srx89NA6zk3)^yEegQ=XCG#BkZeXmo5`RW zy2+>Bpxq6gEhF=DraBR=Azvpr={r)|3Q{V8AwL+EyLLV}*VLK$Qxk{TL3?0vK#5j0 zqZ`lg9b|wJtM=aYayZimO7O3p77< zsP;e0?4HD2YeM^59(oY!&87L`55JF#KJVs|G3`R8)+{zCMe$t3J=S1RxT%aDD62V% zZ|$&`u%6lVZ26`8=;tg99#X_8I}g+Oil!nhwGLK(ByZ{pnK!x!$Vt0<2@@owN0MuuXd=+V>1Tzsf{$ERLNq*q6=`E6!dmKA&ILUVhe| zSh9AsTXUq*dL?Ys>5lT!qbw@RSFIXAI4GX0PWxC+f{v_AK&xvz*=x_8tbRG)@oxnH z04yn-%$I=IrFQ~oottBy%3W-C{3ocm^}Al+GmFi^#2VZo{@}w%05de?71?okEQj39 z;8!HxKmVM%04X>Bb-n-poX7s3z6BC1hUF@$dc?u;f_(>Or}yH^Ss{#Ox2EbfXFLzQlRFZ7!;k*$e6v_<<56cZ8Hauo!(_ z!|b#Cc4S&thxTkFT@gDVwu5hsy+d}Ms?#PMuHdYxHzUqMJBAy6()!xbN!h8OoHyqg43G z&=Fq@d`(f7wJg&}acBuy?8_vyk3(}11Jrv&O`*gsn~yp8=5ig^ct2O(jaNPp(1{tz zS3u3AnmXXoG6hr!ctzeY5-+vrDKQc8y;1I;^o5wC++%)d9@$CsYMlms&Wl+kx6+SL zmZUVt?o(fKTDeYnN-pH_gl4l8GNymA3#A3O#^oLMfLqM5vQGC(fyYcx zgQ-qv6guUXG?ag4BYO-!E>>grlmvZCiCd7y(LT;`u?HPV272XYf zL`90Q*a4c4o2pLH@3n|}ZIigR(ZrnfHNKDte+&gmT|S;4d*(I*>ybs+$F!}=lh$88 zOp@{da=yQR@<1|a>G+P`{?D^2Mwj%__Qea;VkbOtsmnGCbQ{&uj{1oYadG#@WaBf} zX5;lKuJvoPUN^ClglFeH&5YueAWu=DPTvSGXct@@2XJMoa>o%=BIHKX>hFbgzoLTY zsxXxmS(4?R`I|K=V3R}c?(I$B$$RAbtLKe|Q~&6%)1JNJF;HR|+LAOcp1EBT?YZvw zx=aUr{@dEHiaJ^E7uy+lX8U zZ%FE!K;_6@r}Orjzj6(Q1m&Xa4`++qg?rR3y+^oRN~rE?LAKmipPT1_ogQgI@=xE{ zOiG;KNv@jg#dwx^eX?()4z8Y&NG`5xI62Au(4u4!PBnpB(A%Q;s=^PxZ_waN4n(t# zN-UXVYcF$5Ik?5?t`d9rUHJDQ{dv5mU_JGtS9iXW&2mdmU33VQclQY#ADDHtvzeTV zx}cilw^~CE8XtX`cbk3)00fTpk9lHBiVRk4*>9cXN~iD-ka-w$BYT*Zm(6`SxZ**a z8CfCn%4BuUTf9OOZTpl9)0H27CLgvr3tlM6*t7aqSmdN>xEHDq+=INc@zs738rS(v zia5oxT{%A^x5+heI`Qjba-h^W)CeXfVVx@{7y1gFDOK}a-K+DigCQ+dw3+JihC z!Vd+*=G2lTts8Rra_%kC_Gx5Xpx0*V&A|HSZdRSJSE5H%72_sk1~UO7DR5z;J-LUsQtn%(#_r+{fA)(FE;)1bw+`bs zP6ZcRym3_lalzeS8%5jNq{tjXnf!OY4{*U5Na?G_IAMo{6Nbupo{g5+f2mFyAO(8K+(5t+!LKasDV*2W@?3Q4DOecI0^SB_6}$5m1n3TD8rwtI#$HcH1R z#h6e3=>dOm#E6!`zdpGQnYaCp36Q&lz=p9bpG0N*+U|bVI$28@VyF01TeiUlY1tI8 zPT4wh#_SIpznZwCIhcX~9Vg5U7xnA~0}L5ftkR#h#v#Y#(`_k13=7gJrdB5ciGSHpOG!gh*E$kCu=H`0eW? znmdOC)MUlHna=E-u-VVTp0L)E3m=?LE{jX$I7tLhp^-6tg*_Xhz``)h)+vnpEI-Tm z_Xv~(J(r)1}=0k5h_f->cYdQA4B-A?8%&W1PyC;Z)0Uy+BvnRm1^{d?{c$T*(8np53dF&%0SXNePku>_KBi zp_Fp&-b@r(N3&h-&^oCVri^-K9KLYqdWuyj-}jk;9s9Ojr!e*wysi@qTdRfnHy&j(3>L8KjPGAH<^KDyJrnNz4343&SdB z=xU$#O|-$2-SMQIXwjLc1a_Bf>tyu&z-N8IOOs5C?KK#TJJ4?8b zpj?x8B-rYw!2mkYS?CHAhdhU9Ha&*6XNcJ3VLcVVH%z5$-VYna4$0n@15dnN_SjEb zyc`hsj^7HmPxQP4G*rCgQE4%5g1~By+kulmR*jPp8K(hR&SZ%n(OjQKH>B1VUSKvD z)KD5sScMrqWXE;n`#~O7*|h&KZk(anFmPAyzAh9L$q#`Iggkglk1la~c9(9}?i(b+ zU6o#Zx*%*eISG;r2R-3n$3EK-_(R(TB(4Trr=UG=`4;Vjht-b)CIRxxo&Cs@+Ir$mDKqdtMvGSXN>oEu^$TY60T(uTb!G$L=Hwxbxc3?s=J|I;%=&IbkpSQwNU+3-}MH zKH5x_hkZEPLp{$0sbJgmDdUk9F|0K z17wSiWwdQlTE*`BpMcv-$$oplK+>+B#okm5@Xq_vGp1rVHcTLSdu2O*yZRj+JF6eY z`H)0VSqy8MjV*Z2`X|Q45`1gD?=!BE_WmV!9~Er9@hrP!HgpETv*a7~;xd9~T$A@t zq~Ue2%VGmxK8PVWK^!XymCCymXPf9aO=JI+QS^5S=MC7koopwT!#VP5Dyf_$@rPrl zboIKwGL`-=WM;UX;GaIw$>rK-35#33>i-@KWcVX#BnSU-1#7bm~u3 z7*Kk7h_|Y5q}6iyRzw>Ek>)w+BOn(wgRYkG{T8(YO2Y%1A8PlwcJL?i0Fdwb7D0u_ z8en-Mjx{g{5-D$PPD@Qq-O5WgiR)TS|A%A-0Q^kwyW8u?is8~PZ{EDQ)&Ge__r(jP zQE6<;-Ibp;9QvY$e*^`D$9X`(HmBzYtC~FRm;4Tkby2-hbjleS8Sbzdt|o8zL!i5R ze@$<%Qc4*7KG|>i0QedU8KBH*(2sDotwv~OyZ~BJQPF*Oj&J*<9S#w{8?IpaD|;e6 zdFW4*d(?J7Obnx>YL0SD5=3hBq}uQDsLvg>S3kMv)Xrtq6?}_7DhGgz&CBlX?NvQf zQ;V30yJ`4h;GCz5eaG%)&pr299Yq{fMb}Gvre>gc>oiv zX2c?WHjan%B^1J4+kK+*mTR^o7^bcKif^cSo)^EFj5(j^srjC1qTr+VTLdtPCbP+m z>&z!_XCYfJi-7_UO5KtPgZ97*D$3$zz?(D?DZysVaC(}(*eab@e7Ejw`868{-3nmK zzdeMA^3Aw~GnE3^taw=|N5?6R9C%o31$DywbgEH#kj)_Jqw5PF5vi@K6E3T))MAM0vh&-;*?Qq^ zMQi@vYP)GkMTNeyj(YVA^;|uXlI6&Ed+f728pXyu8Z&mAx?HXtJpsmB4lZ-|VeTjM zu~TW+ZCDJA6&&6c)b0qW8!j8vzRf~R>({;!0MjMcG?i7nIoY9;>!TaT)RK;SlrA%4 zgubI|7l`#+{p?FTzH08>%-0lenAR>o_)<4*5T+u&HBb50TZAsImV=bu;RACaQ2;uP za9~SK5p;vqr2S@xu*cE@r5r;RUxGh4+C!{jgtyP*rPpbrGHBp{ZOAkGz*DD6)L!*M zoKNp1^3gsppM0LwiSR3=c42`*JeU}^%6Kh3Iap=hn}*O{e@^TQ;HbfQ?R|mYf$yrD zdj0M7O&+$ZsJ-2G4(Uv^_Ze)y-9{RWMr+w+AfPmtGy>LR!zluGJ5xo1-p7`yQvPDC zsQqT^2LLDN=H|dTW)v)~Ia_X=gJXFIs zzZ?iYioZQE4*4t=HvaNtr=qQTDlM5qDX#0F`SIPj3yo(KGg2QtGg5l!uFdPVxD%Bb zh$`=icX&UI)Xks*bbOE%U9TH`cCQT5o`2MRD|^wgdBlCIFPeb^bP&6WuxWJPCd&}_ zjMm5yV~weFev)H<*Y_oYon_xJw5U0kE^C`>eBH0Kxvgv1h?IO0Vx?LXdiH#}bn7Td zPYZ2Oe6@t}ZPPO2+y2&Ov}2P6d|~h7Pl2z&Yss8nb|m#`hPfP9r-fU0i}jWmHLACM zk{a~kEpNRgsC}tBx!<@X}2)?a|k=~-m{)V%P z2K*7n*TwI{HU}aC*TwQR>M@%Sm4PW%i4J9D1zhDtGYQK26e zc8IdYpPRzGz>C-S__uSaTM-EsF7&&=hPk!bhid2*^lJtVM~nU~CQPJ=Mrl)N-HdkG z7@g!p=T{t+{xlngAIf+>NyW_AIi-CXb+BLTj>*~fXwJ4!pm%l%LjlPT7w$Gh5!iXG z(X;P3WbZky580F|^d%@#F#&5QaW-Aho~a-+=9U-A?Z(YZn2q)2V6GzF?|2t z8MLs{>TC5G3#(`^XApIkd244kPRt}#+IsOLLY2TxyM%+SOyc3sWzuY(?$Ib`cQke4 zZ1_Zr(TFB_w%A4+tHeqVPs_?4bpeeJeV-zOkH5*8QDuVFqWOxrN)LCdl%tbwRI)?5 zW9`VVIbfBBTH3PEcSi?FD~E3Y!{kvzBqrLvCSL8dU%LCqZ=vc#i44zM>%2X~>JmMB zpbKSqLe&k!HYC$aZ^ZZSnRwf@lBv-q$AmkaeAUaZ&|CEJu{Y>7laCeigSjs_y_W1< zq<>$uvnFQT{V=|0Hjgo9)Jig1iVvo)D{|m-QX4$Ew9vDK2%4t|kvBraoEJJ#D`%;v zLx-lm;1u}|pUAyq2D)hErF)S@ov@MnVz<|mirNex{EAkd&ljgR4%}=qTAUoxDC#X{ zj-%IqdLOBY*11VVs%M>7YVea&_ z?;~>!K(fyAjH=}IOq-r>m~4b<*wVol@}q|lG;UWUGyUr)tBMl}y{|nb&y{@RkqJ+p z8DkXg9SZ1G_xy|7CE|_WQ@o_$G?V zqpUldExOejJ^xMDa7yhsmeV4Iw1$y@U%2#QJ5F?AX{N|}qN%XDATn}|bW%g;t-(%Y z2Jv&#?@}bL4nENiQd2TDmRTk%FbS{PAeTvo8tnQ)x!jlJWG&60)bS{R7xos5A2pdi zU-+|ydP8pd2S}C+=-;-B{#q?bc4+w8+2j)58y-zl1^*E;y2?EofCtFUP)`${mlAka zBZh6nf!wVzW}2U)B}lh9Y4Nkv+uI^_2Dl@c?O?*=W7*Dda<+{H0L_)K(rOUTp2Sn& zCVK{Hw98|m#Z@2`cVVV}IQk0%&vKXS3fDy41pl^Iv*IG2+}1pM!ptuJ-3PDf)@MC} ze#K#0LWkM;i9Ie??-`yv1N2D`E~S6>?#}vc)GY@6@Sd;m$k5vy zh)iW5_ongc_Rln4ik(M}9Z9#1C?`F8=-2T7`nI4nU>Zz9BsU#&|8+gFM|flfNuFmgd7XQ2{=06a`c~OjCq`mE|U`y`oCdt z-t3uE>uHj^<$R}zH4*_udxe6T2%n`2hBq}gZwHY`eZgb$i_Hu`+NCIG(+{6QQVxHm zD;m&iu9cQogu^k79B1cC4lk+3;sq}F_3jP!KLx2-4Q9R+m)t#{D${x1n5J<%rnws^ zw%GkM$w$plWRF~R$oo`|XYulb1z6*XUU_*wElXF2OnQXR3dtTl>r?&U;^Uvst%iLu zOykA1sKS*e6yvjsu*Jx4Sc&6cb&qK0`o&xp^B|k6k>|1AGu+l|`S$GGT-m}FGrclP zDmBLefyYk}ty49U$a1&YcMo^CQZ3TB2DwdChrTVVkZ2}*&F{P`D=i@stMk)%KVxUy zqY7~yoD!W|ZLazB+KBxAhy5?7&1WT=&6itZGU|@de&|xl#lqVZhIpvb$Z-*WzRtZ~ zN4GSqo6o{y=3Fw^Cd(=k30V*_uE* zOBh_r=~gi@HonOh-24%zItq_Q*AplF`aa^lD?boj4L8fER=^pt0yYe5O7x(O7{1X^ z=VB6PQ8*f*pPBp1fD_@#IpAA=84%8iL7%T`QczF`y8KXBX!hh`kPdiNjs!t7JO>p4 zi0Xm@WlS5^*mGHNR0_o@c`_#d^V(GgVhbl++G({ee$$$5(%4Ok1@1{3p^5Bx_UqD| zswQ7>*#HKmAcNqa#q&vA-1)K5G;>I1|J2J!P zd$r%eA#tcj=D}w+BiY4`BQ-{ep7kSmJgD>pdtOsMULX%kDvzeZZOA8*W&h+43&aKi z-K!3=(jLdtM6@il&+jQzGZ?fE zQgOf1u$odx)0+>?Fgih zag37>)t}o>uT}~fF0hCshQhjrJfv3Tk0eFu2XgsB;Bf{Ie5ITjuc+W%-bTIt4sIdY z;XzfpZmLFeUqsJsu*LspVuvPdxSc$6n}>@l@e`mx6P(0=xLA*wjMtR9)d%9O+0CYf zIiZ3CyQ-a!Vn=YV7(J1TIs&Oqf5a|(q}r6;Ccdc&Drxiak=Jyu1;z$AoA5_;rjrB!yXs_^ zaY4a2Ewg_EAkJ$eBCEPa;clz`=l%iRe-eTLcclJInJY93p z_``?DxLPK_rFnT}l%wghs;c;XFbHVgp>Tn5V^;<(reA)~jtww4}99@^h-OF;#Z!awT z5X~U|FazVc;vIJX(}!p47P!<|SDHWXy3SGt!Qw84*qv;RPs~G80+M2+F`pgV(H=fH zjv?>aXI|NSm%AOfI7DG5;e>kOKqMq!j-K}RTU-xkrBED`o86+wi7&Pe; zdS7dCaG0+Qs+!r+9``ZGTiAA7w)r_===;((f;AwMdtsnGuem(Q5YfkL`I}BK#mtnu zM8XY^S+ByD=JPzLPw9SCIqD^g{rG6GG7>5~mx#~jphET*TbwIPs$lvOI!=_f{{TPB zwAn+cPrEFQ*({^klG+~oKu&CdrVw)1IH&x<%ldJ~KJN(eUb%1G)@r7Bl?Cm!GsD%6 z%NkiiNe$lojnbUCrP6VYnT;g=J2+Y@)(5vUf(|?JPCBlUj7^v57nPN%L(agsD^;03 zJCC0XjpE3nUM)a6^b!Gg?_=;BLmjkN)CA@}D$HG8a4hD$v$%ZfZvIXwwRhr~TS-zL z^oo)xRfgAhnu(dJOTLk1)XnMhA`8l<(O20!2egPbC06!j)mRAy#Tv^IZFa~{2bcKi zF(pN{nX%NoZwVR3&(uaN2JX|Ne=@TBmOkG*9+|WjGh*L)nPbVP)dMf@*OKDusb($= zTH7*BOqIBD8sf?*Nu98N2U13@>F*X-4dXu?WhzMT6tf_yM#TvlW_I*bz{ZxA$=?$m6oYxLFBzNh97$R^_g)cHTAv z)!43UZOR+h+eH&C>z|)h!>6qYb!m91jP6je%7UjU($`_r$)y(R8UPe_*>B6^}`R7JxIE+c?_|3K=ibDcInn^ba-U zq039#;mFQ{l|+g<(X8ypIRSv#{q6Ca_Zl_>HJ`kl^G~wN zt*Q)2^d2@^y_Hz_wbnOLZLV2=%U?wCY&Z`kdjH7NC&7g92%{~DyoP>#Pb!3wpyp~M-ClFawp&TVtB#_U1 z6)Tal#I! zZRBC%f2y~4yg9TGLO@m1)+V)wW1~`?k89WaV%~mIQ4se!{8Hz6VB&zqpcnpE(7k0p zcR|Ewp2mcm_7&0Eajj=T)NYWT@D(o z`4RgoWpyA+#k}ela-7UzPUUrHImOb?ndbLgV6Y2^meVz+M4Ea%_{qbtc>eh*WBzn$p zROY%M7{-^x80!X4YTp=X#&8XZ+T&}7Ib~>|h)Vmsd>v{S%O__@ZVpVrv}49`a8DWh z`Sf9Nnx`bvu*=TT!TjX1bN3_JJZS}S0Yl4yLxq8KjFIyHgyr3ga+=d;7a&r5-(T36 z@*cqmR-h2Ey(Sc~BLxEOPG^PwuW`?cuDUAIyVbGH5TmGdJg&C|T%uN*Nz&^S!x`X? z4fWrm1!Af-JQ=pPNt{BnM}go-V0y9>i-8 zaC&1O{#h(!zok%p{#)Mtc8UVWp@w6Qb5)<~hUltnsqG|;Zj@UsFMQVviw6LYqwKF0`3)6uCV1Lhn@SUub;lc6_i&64 zaYELUbD69+Y?Bx&^ZKHErt+EI^(pb+ov^TyoLXAo2~`(39Zo9~f0=b=^;}!mX0SZQ zYww8GqNrn}Zew>KCMqOGb~L^domI|Q2P~v_U zysf7*My^Sz41N~*V9-j8%>|;fBll^1VRN$)My>+~zEw1((KW*?k3VZh48FB(h2J>) z>#pD!_xiHinq@^r<*)``?{RYSMul(o-}rB0EstQZFa9$WS;Z|+bZOw3Sml~Gdu?-xSX7g(e_G{N(^v`S z*bJ7XJ>l&SbKec_tV>$Xe#@>ZYN+|JKx@8wV84k5Y`Pu$7TNip7(>gisZSXR_6f@; z8n+}QMTl22W||*YOfx?{;hBW(?S$;N#f*D|I1ND8Jzbd6(n2f>#3sy2^Tj5e}~?vl`!7 zW6--}i|R?358U^u)y~6-$v$R#hdjU-VFUabN=KVe+@!S(RhLrji{}2j8zt*$G2=@I zkaMq>cX%|b?oM=VS}CZfZ4wsT?Gr43Ue%*t;2As{`8kS#4l~uEXM2XrPUBYnNM!N zbke>)&QKa~@NI~!09>%KNSl3nYwauQHSl6SO&MWxN^=QJLr}3t8Ys0!J_!)1pKRIO z`3#%;`a??N6|nJ>@v}UH!<4m)`_f{H`ZqNM+i3Pb2)beRu{+N=({APdo?y(b8P?U+H#hidX(CcWMK1qzO`zzU(}o&J?-S3824nxZ-;-{_AErj#r2+xrA$? zB`*)VkA>bv2FaiOm5ldYKE!e;BN}WYM;9aO8>BvOQCpR{prL^O zK&kT7AXw^SPw)5b(2T2Iz$3_$w^N!{`4*8kORI5#QW0?8ghIPf99OcS$=(_t@7>j? z@YttN5ppC8xk??=7zhyfX*A8Lt*DL++J@SXm-c3wGClkEvV?j7nV(5ND>E3s@xG-@ z|7i{6QhY51Gcln}bITcdV%JF7u^uC>p(%y1v=+m>PvX)Wk40Zsse5IM1K6qVGhosh ztsoMr4Amy#*;F5Dnug$mzMbc?Z{dy5n&?5Q%Yrd zCNb~I%)&xkY)ew{1LS)CeFcsOh~)K3;*Ph*c*|CgOiiUJThUNQeX*D;^a!V#A>bcM zv2SFSQ(_d<;;AqMSNdnjjWLOB;jZ0&AkchC5~>Ti?EZ6s6A<@W^a+-R;_UZLN9%( z`#%)d&j~pkMIS+_nJ?o(>%r90D3LXoDHi$T_}H~FMWTX9LSgkHzM`b{k#Y=U-cT}6 ze7;>mMh2I3b5qk6F5AG>qrJX9IsaM8eHOre6O5aNsDtD>_p63`F%)_2jR6K^a6dtJ>*4w zUbkqBCT};MM{tqkQk8e z?i>Z_5{8hWBnKFJhGyVB0QK~|>bdUwdAIHT^3F$*nZtdK8TWnt*ZQsT23sYsWC0Vv z!FI4so2#=HE5&zW%BsBWf~TvWb|cy*3z#fuJ#87SXR)y$hh>`q1MIT$kELb7<~eT6 zT5r>)#>I`pZsqFQmzCOZf>~~X@|8ZjrQ#0DL8 zp|=7>0UdgKT`NddY0XI_11kPTBllRSSuaSATjPMYJ*QTA!rC0r`bYx92aO= zHD1V#Lq1sqJn6NodnShb^O@g2Ttzy1L4U7mxZBLf>0yi z<+UD#?~Gk(;}5eg9^4yD($BR~_}VkNL)S^d%ZXFq!ajqQ`5ociqb?%<)v+qnwC`Ww!E%J-NU$?QN~J8?gd%r zFc!+pZ2&&{0}9&B$NOH#Y~Gu}0})iB-a@(%Z?MSW3g`al)RdxWZ(>B=5qYv1(m)iEf z$L^|qE0;=&bt9NS3cL5R3{R#azX|UujotGh!|U43XWL`1>$QQ^6-GO#S1+A>cslIe zMM%uH(I2f!H35|bWZQJ1g;9DsGztNMNctRoxcA(r$@MnhfDAoOBQUVo3#_b^(VWK6i0mqdNRM!6ORLVq|pJaj55W4vs2cCg-1Hy99=nr%{s1@x(q5& ztE`HkAqoBVpeXT%dClOwIr<>{A*RU^=EU&wqaU;t@E=wZ z5A>0_l8LdW{CWuhCR}$3lnOD^q33eaFYy<^E{Lt0xQw7jgP_kRm=N#>O3hHnA6Gy5ANu z{pphM-?WL^0ro4CUT*}lcgDo>gr?$5Q5u-H z>sa&9VolV+gR=4o+l&4nJnO+O_TVGdl_clhqWs203Eogfa zg7?6?_I@Z&6)4g>&PCZ&ku0u=M{ulTy077h>T-zBnF%2*M+I<)z&_|cXWDvjwEW(s zTQ=)fPV=mge?PCc^L^E-Fv`C5LOytx63<5@=O9Q{s8AU*2{gsJRy|g-9EYPyi!bqw#xeZLWqO6ga7#>2k(^AEA^K-Lqc%{BX~ zu`qdkE1o^U>Q6Ntr|=PT8DUIyb4f5brX}c3k2j>s5w=}es7A=*>oI)O43rLBrS+B^ zKjPZ`Zp&9{3azi_Sw=Nz+N?Vv<#j~n?Re~)D}TxwpB8&m|1N9by*QIKx>qN@ zy~S3t9RSX5_ru0&%d^?+;uP{Ui$EmgJCI~lhOvxuIB8oj$lYHp6%91BnC!_0_O9;)MWTzodwU*%;VtY2YB zj+&B{A5)_gJ2w^(fj0?_NZ2ywXL_>&vP|l&wm+*6n_ghl5uq1DtO&|BxYZm8#yUto z%WQo;HHFe-7x_=BM;9HSdfWn3k3%PR=Y%n4PXCKjB?h#wSRag*!3is~Re%$dNI{P5F9PLzlxzKtxaunDU zSq?h%n8N2&Y(4HDG)#_4x;bPBhOJ*V57WkF1FwP%v&Rt8^f?fE;0?s`T{F(YPHKUi z93L(vd8|c<&Q}4NdJV>pSjds_(gt$I=jhRov@nrWU{a`g%zT$^ix;pVWPJGq=pei* z_brKy)s`5C0DC6`BLJ2{WWQ;iGq&I#0*|k5BJITp1rqWY^SERMhUP&aRr&V^)FKO7 z`iX`~0tKW3sjD~fXsvx=F?)*^TD1DNzD-}fm=wT9>O=+F)upize4-U27`}Kyp+$& z_+}Ab9RmL@{l+^f($+E!6oa5Gvf^jSy!%r+aWheZ_%0-m4M@c)w~|8KROzsagcbyN z@)c+JoJOn@s}~*a0pE|v5oOGv(#{gQ^)4$Wz1p|Eg~64I5E zm9|qNs~V7l=t|Q*${Nxak~AdkNc~G}22F$fH+M3kQ?yZ1ixn*6u?)T}R9JB@nx*gG zLf>xMbJUf1fd0yiirr$rU93>^QLBB51$#+OYKkXjiKCCvA;}Cg+piF<8EH z?QlSmoV(iZI{v%I+Ge`dYzA05BDfr1{3E294nS{{1o6=otf<%+XRJJx#!$nzcLO9| z3PAa>21cZsQ`YQcOg%VX6)ITBmh0Zxzg~9tF0er``XO!Wjqw?>-lvGUSB0j>p3cIA zzBK}@<}b&9BYBI-*3n*;Xd2b=k3$RF>Sjo^XmIXMwl0|nggWKnzUJA&T)^NU$FH)^ z9G{m(H(RM-vE8kVi+A5vn(Hp^2r@{6MNbd5UNr0AxG4aY5g6kfIN+eXSKojxP_3&8 zavwrq$L)iG4{662qG4YHz=xf(*9Cww(*XmWmz34k%_TSL*tecEZ+&)?eUK*tf1xz) zV*5uSlmBotU|gm8E5z~nD*bGt&oRqMW+t8cQbue#;7Rx`i`?bs=-j6wCH-_yDpW~J zi%j_B2>Hx40b2NcTH~*DU^s)h%dwlf4={Q|ZFR9hE3L=O#;ffIM@MCZ$D8>jxEg?t1)a93U1H`OEQVpyI&xU|iw1W}B(FrU|JOmXsYqlXx;ivK&+gashy zBn#6WTb~F~?_%%ghSR@g212WT8y#2L!n{jmtVj0~Ap7JjH-gLn32SK56DeN+m@0rf zKvgOYz=R4q%mval&Q+ifcP+QKx0e^Cv$XOu9SVTiEQLbidovJWMFDs?cZACvWQwk2 zagC{HTCxqK6G+f2sEV{5>G~_Hn#Jo{b0Q(|RLH_Y{jwxHy=nGRfrJz~; zWC;$bs87J_fJI*o<@tJj5DRiF?{9?4NQJz)QKXOgf?UwX8Yd<68);{wqLd(Xv?>T? zO!xkRFK$%_vUUqrd3`q?H&|{(90ypXE0|-(4L$&nN)?dV9x@ozAI(d=fPm31^KE6K zecr-{yDRWAh$vHm!3i+u%=|ZD35S?uzbfR`)||%)ye~P+a-^^yfZ`Zwg-_4jd}f>i z%RG}$c62jCkK(xWMXo^-h;QnqN<)NhwB+SS7eZB4xO|_^^-iFgc!Pi~eOE&YZJ0x| zXUG&)%FYpBB#99mtOa}&vH2-}CWn^+kFV84)yoITbdM{j=Z*m>>Kb;MzO^BJfWyAx ztLQ%zl^|!6T6w(@H6N<&xt_Gqfwjl+Duc47hjS4On}r?4?Wj%OI|tV>FeFa{N!;(L z6c$tP{l!I{S2Sx~H&Z>~$@2plvQBH``~slm4??X?^C6|m03wwpxU|$sYv|?u5f#6u zWf@&|2iJ0KgDByK6Wg~;Cf~V-`CVoac(9BB2}`Q?9bCE)*0Nas5?b=Kyy>!{Zf;Me z-l2CFlX%f}^Tf$49zh*-W%VZwd^0gqVO=+u;vVmo5XCt1vZSh%D;~3-1Ugsrq+ed3 zvgc(sW^vlpP2F0G8lqGd>%9qcY+X9?k;offOLJEvu`Pm?gq03c4j+c<<7n22uasqT zDe{F>zJ_JT#`6@V!PLE9elf4O&kMH8{!t4mSLjrTQ2Ctls-mE&C2Zg)_HbWPC20wJg81 zph685AYd{VHdEMFR8Rs}&Tx6sRg8@;7^i35#_FfYE&|o}S-)|PL-mhNdUlrZVeSY# zU)ivX%J5$IE)Km_#cqTm@M%34M=W#H7vED7@fH4qGNL;M%Y{*RbbqtHv7s5F@PLP+ zpS|@`6_ihdBldG6l}Ev{msQz_gWh=swvV(I3IT!}qD8yxc5+fNEIJbMr}tAAinJbd)+J5qTdqGF=T} zKTV?pR=4s0hWI6Nc*kEKqTbC@ldic{*9(wQX_9rb^u5GFTb7;17ABUft#^i7_;&G_ zm&OS)`|B;ueS>0?7t2qSmJpSR8u&|f5WiFeNrf}fex&>`or4NJK#N;k6uGZw+I&y%t+SrhmN=5 zV!SY##+_AE{n+>9yJ)~h*bTbk=MZn*H6(rCeaCiDwj*BeBiGxU{v$Ml=Xna5T@j0M z75Tge6gU{p$)E6ZGcUV8=TinKM}?E(gpLb!r^-eovs&q+Ip(Y<$Y}hUDKYx)`fI~l zdQxl*;LFi@PelCmy*5MLmda^BE2l<_%7!zeMH-Q6EV)n~o9UKpjwEe|clKz8$yXgS z4Jp+s%VSd3zp@m0TJA~Kw)bJRJ7fcInfWSx)oVoPTo?t-GQXuhZVv5A6=hQ%GT}y^ zAXHc*FlCwyf?ILv`tZ5RmD$=@h*{IQWrI5sKP@h&CzZCf^W^k<#3T}i(us8*MnuWF zS>qWcXKWX%MM@!2-(`18L!ekip6Wn;<}>G0hZsLozk)gCQN!Hf|H_pir+esHLO`h3 zkjA;Sc~yH`n2mB|$F5jr#zA+JdCS=zUSWb)K**5=Wf!-ZQC1)Bnc?=hvvJT}E`qYs z!P-qB(KGy-3lO3>KKvQ75It2jc1~4|#|Q7NiS?dD{7X?IY47f|#^t9nOCmC$1p*-e zlnkN(>{mKQT8b3G3kU*PA*7B09F3Ryr0;v;?pmRW1ab|)Yd)p?D8t<*^2`Hk&A5QC z`3PKe;gTU8cV58tXGt|W83AI1EXurW&#c%sIcHikYTCTZK2L|bSr0CGx6ORcs7DA+ zcsd%sRUw8xGw6B=W3|;wX$mvh>xUP`EhQ!RvsRzQvMNsx*nf{hQt)7UMGD z+$L&A@nvNdp`74-iITiSNjfZgTglN9Ldw%K$Z1>YE=_Erk3~xX0v1%r-z*DWv23p= zgd`wIkcjUBwWRFOq}}#dxksX`(@j$Ap*4H(t*`%an=clde}@~$T8C~o7S zSW;SAT89z{pyWbzY#V4zBhEt=^vAzK6*9+1Q$U{L5rN3wU12l|$`O|SLWG5>C;~tC z{V!+76PxMcLcrP42sk@zemXl8oCABbY<~%YOM&L5N{4UjgzLs{?0+kL;GPc;#ew+kc+dkBp>xU%AmK`rK-(y0Fx7E2m2M z2Kso_Dov*cG=P>L z8%ib0s)0TYZ_m@bIvRyS#eo#|R@HH`*N&F^VoE^wl}iGbRcq?v7Swy587#KafPVQy z9`2v}Ln;J3ORs_fHeZ?(Yub~TVt3%ZzJnYze=;H#ePVEX zy`25d#Qvc4YEMP)9J2XVZc2O&&WDDJkfUo!L0OgJ7VN^hOyB!ik8Xn#UEZ1Gls`L| z{W$8?SE}xC&3xA-=;{2XxWp8AeXIC$Se<$E)|J2=o+pUW>5xa#F^{#Y>qD~5%MzzQ z%zRuxJr{Z>e)wcG2}>>1Sg_wpO1)^y4<}ZIjJw}NtuiGmB!--m%%u7>ieWTf+)|}B z^K5tsD@?$(#e__o5p)I6L`uwbDw8vg#QIAASrG{wrhnVK<~uZPYD+nvJY>2tV!fcB z{m^9eKP53*80~CLd2X@kKHiUUUg~*TEJ84jxLF&!NU)>1>l`oDUu+3+pFLl`X`!&7 zF)}YcMv&81VouY#b~H})rdx^7TU7h$8(&+vcj&QS@2ndTj-@>lSiQ-Z@UR(PUmMZk zwJ39O6%*r?9IZ9ZF+ab{TH^EZb-3tB&C%+K*Wr#iFMGVz8*B0k;`qL05RT0gBJr%hh&lxF3db$u_Ff+)l}9fSmoacj#)&s&B|i4GyzxNT?v&XWs7`*k=L=p7hn#KLC5zU=a?eM9-$N5nmpHmIsxN=Kmrgxpi*+3DEi@rE#BB zq1Q*!5B)ouao6)t?p0!l_P`Gv|*-spql=aJYqbu|edsDC4qT8aO50V z>`%)m>*GW-Q~4aRPk0}$qISk?W#b+iypfw_cty@@qvYXH#|#}9NG%U>(l$FKm=cV< z2mqQZqJj9vt?jwdx2N?MV|?9X30HLk7%f)~`d<>vZBjgA(q_w(Ao1MZST89`10gJO zqJ2=}bgMTHX$mT%5B6O`{)U6tuzl*-@U)s0^z~L5aQgNwen9vV7Jh&DSWU*4Ca|%% zH;tTULgxBoi1TVlXofcLzt$_7EQoBgkOI9DiEz1{euaJqf)EkZ0p-IDs9^#0TY%$? zJX5a>$5e_*gGl8CjOQ|W$dfNusJ>wm2tCp$(#rr45By_3fI-TAIVWq5SriBG14&)5 zOG3i?pcF3SV(XBT#(|3*B-8D&!%CnsN!Z#`CZ!zqo2mmg*1T%&CZy)iGY_<<2dVDm zsxy{&O5}OV57q-a&*9@=X%2BZG1SAq@*Bv3|CHaL+UjwtR2?jl?0A_P2-i20+AqJ} z=l_Y(3rIOcg*|tT zQo-s896tnl`SQ{CD)$ZS=}#)Q-W-O1o#H53zd)BnnHcpWeL3-J|9RWX@zXy?)LoMd^@{7X^E_5Pqz7x_cw6kV;H4xg*R+j~$-sco9RI|3czPijJ{9 zlxyE@%(^NLAN{?=a0wXKaa?=#Z}bRR0(O$p&cN3()bp%MlCEyDC_XAgVbMdRx@$|4 zj+6S}$XN5BFoNik5OEsxoqQY9u9B^yZg03dAVlP5TWbyQH7Hs;JCH)IO^5Z^`hjAf z;jIFPnf^v6w%PKo&wYAc9z#qb`ngm>2d!fYVeCX^%wz`>TkcDE3NBN$OvxfztAw}a zik`g1(oBb{a!9ToB$sgc)Hu8;QiTmO9*{EiKr*(?%#9-KN}7x0V-6Y) zzX+(0w}$tN9+BB5YJ#+^3o^7*c-`fGRI9%{ zvmv`c!FMN#Hd3N-h1usF&o3T&uTKx3yUC(U7a~LBP7qn&F=}^PDXB%JueT#>vW$J&uJK2rYX+?pDO{p$GS(aZ zWuF%dBmBjRhs%J@BHMw3~A^OvFbPGpJpNvf5$rz2T7Fs zULmmX*j*diS)VFuI6gqYUSnPDkTGV*A?Iozz8fgO>=kY+i zOgzMjaay|qHJhv`|8oCT5@~+h8!OIU!(qp@cEl*JBxMo%EN{$nl;DBXOw2;3d4YQm z`C~_BdIBEv`73NW!eT#Fi$qrh;pGYnOP4?&JXfDHatv(epMenlQ!Ywle`_5lwyCvlBWXpn zi3`JYl6_R&g$xlCo6EnD5Xfo}DvtkBe%p7}sgmjJKE+r86U(wEyj9$_U2X-dD^PqJ z(9UHdNaGK$wR~t{-TDvph^fM{qRF4jQfN;(AIcXK2qqS0KV6zB&dNEKH~XLeeXD9P z5u4_&5|{`x08KaSH0fM9p-7LB_S2j7!*^N(D2;G;#RCrN0}on5Jx zrFc~(U%RV3n`EKA>SfoNsJHplcT)`4ZB}#L3jOm&_N;) zQ&TU|(zBR}ih<9&$+Efq6;Ww7Q3nUcqJl4pIT5a-OdV-%Wop(o#$;J|di(jn4e-kJ z+B`E>AD31~aAVqYzJPacTSbzYXoChVX_mF}51tPjE)iKa0Ee?xOS-ihJhNDd|d)Z12Z*c4wAtsKQ(?n z{nq#fCaGYot5%)U*!y`vdlt~H>$k{!$fBTWe55J3U)Ks*Uv3q3G z;oQ65Pe@JsGTi_TgKTl_%ejPMnD)5AZ?^c{w2|Oz*8cMErVaV%n!5uIOpcW-j2HjR zqEMWyN(7I5r#SiiCn^FH!Z{}1Gm}{lsfcr5=us!=Xjjb^U9 zQS^7)M%veN+s68D+s5F;1d9*#4`7#-o|Wvay%2G;JL}0j_kdW1vK>`V#%-7}pMDeJ zbA7`&FM^PHUB}^?tap_R;FaqMWgXtzErJUf(02ob(B4lw*i~N6HMCnP#u_NiGdz@f zsBoWmm#WO~zN_)YkZMzJxW@sp=dxW}n;yNM*Q{C={NpB-Y>8LV8C_yHS|xy1^(#wH z@|n%!AouvEqEUUOXlSdp_d;m(oeQiEbL5QPt<#64rS5ZPG_# zw*M4qv1DaRk2+T}-uzZFG{~L)R5I!*BFlINlKyd!#yjE$3AhoNs<2dtXAs6!o#M;S;yr!qo=<^OXI}G5^`I|lwm%zniM+G-O(%#I@dMsg($T&@ekux4>sr1s=mi7S$^ z#FlwpA*X$i4J02n7`6T zr>rAhE<$~$^mabJs0WhaBwysP!iMwYu^;<3bQU`tV2Lz@O4U0~0s=+)pVPA$#md4?5Kwe4T3(F@<6DOcWw(tCDp|S_}@BiSYbhRS`ywCTvx7j zxk$g74${#}8KE&WpUfv%^0C^(!)rH8tco~z?-}%2fWSn#W93ftXUKg0YwsEaxBDtL z_4ARm-N-EMGEYeGA4>bKsjuo|GnBsDCf{kt0~8ya5R ztIL5~>2t#%8)m-Pvw9m==?bG7_(<{288<_rp8#D@Q9d18hg$C#Vg05{Th=iuD7GaH zJ`yHezfG3vm{{t3-?1T1K=_nz@i2Azzly?~n>qyk?@S%XFDwst*GHBSh#}=4lE}Ow ziy^{_S+OIbQ%a_-S$$ddK%SmRrJCUPEN4t;7R9&);|Oq+StcQs3h_nTaoix7|nA&(I z0pQD~Pyfm84PaMK`;^5vO?%S;RK&nsN0j*iY6-Pgx8AAcO+4A#)fKglP8Rh^D*Oe$ zcufW1Eh=HC`wB__D3-8x3Htyj%kG@8GvdXwlJbgV?J8d7<4ErEVkOF<4%W<Y`g<1bkN`=ug8gqiFUBr$^uZm2)0AqJ&% z(_0XB>f2qc`AF(n<^F+{(`X7P%2@|$St;EHq--}hfZ`;x;V5@BLWCuy!rhnod8Stp z=-H4rK79AuNLX@K-Gj%vL=dygDf^7%q;*O~OSoLjp}^0iOZL5hVE~nJo^(+Kk}g$0 zlP-K2xJ7447c+-FqrWFzI>!D;xT?hZYdXRG8~mO~QS6_i!101Rbb zwQy+^WvHr0{}gG%9UyZip4{59c@35K!^-da)}^_tCM-$D*5J!J>!(rOcxzYapL7u1 z#FWrprKgC-5uWw6Z62&LCvaW=+Rxg1T449W1op1AP zR}T@fi~!z^;;Hs^2Fvfsb|;$Kv>pX`6$%e}>usvx)5pP~Zm$c95<%La4QGzkjeF91 zyF$mS4>tyE<>9LmjbXrB5@M8Sa1`_h_L4Yh74CK1-PsvQeNw+c)wdN!)+~nY?mY{_ znB-YH2gHIh46%Nx6|=)Z*M7n=AU8D?Kspb$pR7G44osXcH)|ZA-W6PYi`2M`W1`|F z&!{#?zvbwgnndz}P|w|PgArQ#bVpfTopC7pzU^A(K;PF zX3=h8Y04`GRlaE)Mz^~ZKqC3L%7XP9%a(|WQ;s?=(f=eUlJ58BDU4+WNQb0jHs(Ib z$hzkJKIKEU)=rm9F3z50>-ZhZ>_)*}q09U#W_@vsn6K9wWmQFl8*=yxnod{rgg! zDDz?S3e}Tro(EVtociAV`?K9q;3!*<&rmij$0W}h@2jws!VBne=9{K1V7syD*QWZX zkaJdq~2E`Z}OLMm|UPT({3*9OM4gNb!~ze%tmj$kW301B9C3Wy2_YbAoZX<@JigW zygo^REhq8LVW4`!gw%Ggj6l&(Geq$-#i`O*Gi3wmUY|4EisAQkfA;>eTq zpB#)kp%K+GmcM0#y3Tux0^cMW7HE_NMq6afYpS7$X+|bC9pU2WZLOv_YGdCULa0-@K{ohHO<;jb6XPW2+FWNh3du7PAP6z{R3XKjw!N@}@9WyOsc1u5 z!UW&QdjHDP$EQ`cE)FT*>X~tmd16+G=oS^+5M&58MzY5rP5@}z zb-nyc?NKVybr4xqCL{%o!%!5nU%8zdnKIObznQUxz2_8d?a9mL|MP6mrTVYW_Pi#n zJ=gm{Jx9hU2(mo;EX!X9%kU)>|u z13ab55INIqV*VTMXyTpVuD{bCFS5Oe(SAu>N*>vW8t6jbSlDe*8nu0Rb5%N{H^qnn z7J+@=@&sskJqwIM+G-%S?C;|wEWh6(AO`PbAj0}z&KTu0h2bNcDR6dDGo?9|C zBA)~O^>00F+7F)7)PB zcQUQsWF@m__5+M|As~&w`vZir2R=4Ua`V4-%|A=<4yoSfF!4QE!BmLQ!~E;KuN>IZ z`KF^VUU3bu`Sd($Bz|#pQH*KHc1Xj{b}{JqVv`;4Z+hL>fs17*3VBCt`uZ`jl6Q&P zZ!{5Lv|gK5dNt-JccUrg!oLf5{3cM49@~{4y!a>pvS2uNP*b}#G&15NW#u5WbDqPP zdl!CzdW6&AkABnX3x8J38zmz9AoEj$gA!~Fs7Rn`f(y8i^T&NDN&XW|OiZV_c0#5H z57OYtK6(Jt0v7$?DEBw=F+46G=sB<+-F&J@`@hRy7||$5i4xuMp3oY#WCYCJfhdSCY;4Rp4j8j#Jh|)h0P<#;@cP58(-egz zLDam}SVWtP^l1mh4T?+c&{A+rS}Yg~DVRBh}rz^O2)^?k9qmwFFn!Bxnk$%Jkw2ET@o+y27S2ga>b@{#AFgV?DlxP$726bc8Gid~mO>*d~jV z9V0yen3CtP)Xmz;E0O@lFwwmqnC?GbUs+rp${QRWrZYWMOcs$>6yE-jIByDgLuMH; zdL56C+-he-1s4H!uZH%e%hBaJWAf3LEvP~;0FP^d-J>uFsw9I~oN9m~Zhr)9C7Vka6$w+7ha9+wd{+IOZs*OHP( zNo5N|RM90Qyw0aS1wL0C`Gm_e{-3_r+P3_SJOM3Yw34L@$&)D2rRV>Uki~=NZ{0k|*PbS~7+__@d?n-^i>os~8ZvMCwfEJcm&$hmQ$?4{O1WoQI@Y(RH zATfsbf*rVh4DBHR%>^n&LYqZ9MK0Leq`VDiD6r5TvIipiqXx_yN$cTHHw3e z^Gb)ID^V6Z$@-Wd)t-2ud{rm%`>`fCOK0O0mn`)p-e(+aZq-JWk6mRcj?T+8vG%pA zSBg98v3rypZDCjEr|QuQ6VzgjB@f&oK{yy|fmXqdO0t2WsfS z%u2*w$|JIdVLgNC+ie1vBowkYjeH+y)Q@ZA)=GWr)?_%<^@X+yA`U(-lfxK(}XsvV5#&ffhSFS zwpQfLdK9=0!|!U<9l=((L(!eMTSt=ETi_Qg3DW4Gxm#cJpm2ApaaW4*n(K87pa(bZ zOlc(EoI6x}uOeDEZxAy5o#%ZBdw$whj^py~ZlAy!N~FSP>cYbh&+A>4t!b|)zVX=a z3Q|&2djf=|rU(;+CS1<3P}9jh@DY-5kLN(-=u%!c>y*&nis|4|2Ts6 zxn2a5%TX8Ub~{IUDB0oDE4B%(we~=7M%4%Ie#F&51;{~EJ3=SRQnbciJ5LYkkJya5 zl2E?o8#|bEcgtV0C`nyVKx|9XoY-)nohscb{AhFAwaWh6L7!y#7I%@w{d9p;daaV> zs%9Q;ARjhVDD%I9Z1#q3{=3Eu%d~AfaHaP{=}f&?_YjfTZN7uA`Mk;#^@#qF8wyCl zs^i*=qupa-y*UHsMyrC=YKwtTx`4HI{IvSy33YJ)!5nCs&!n=?nfclXNxdj~xhyhY zcevJd#J&*MZ`|~CEA&NQkyx^`U0zu-X+!9vRaBm6P<=t*42 zBzTqb`j-O&Fe%a=ng;t7|688L#!sGw8OAwlljFwccH4UV&trv7=Sh!Nl*!Ma2mIwL zdt~%!g+?T@f7q~eoH92)ns&9DsUStzgj#vwaz?Sy>u-}{^lv_}FGm=6n)iEhGADVjg=Iq^n2RgK#4-snkYpk7=@ zq`s3(E!aY%P0hZPP?UFiDvBg|qq#4jx8Gi01Zp&vGctQDHcNiOHWN&}^y|)Ho1!`E z!m<`JZ!~{nELLz*+|FDOtsaDbNDrU7G`rh5Jm>%u#FWl|HbG3*yia=&-Xqq>I1;TP zDd}QL5_^r}8k=BsQl-KG+ZRAzP@c!!RUzzQ$8BfL97xrBXHr(qwm{lX?eny>|*YQy{$~HhTGr@IrH&&mM{gk_=*H-z7Knit-tjhaN zb@#~chpk<8bUotak@{X|^%7`?c|m&zJ)xTwxG+&5nosya=wXyasMr0<@$EWx-nJ9h zmv(`(@r$Wm>?QA-WX#t}PA>ETfwC=FHs`mGK`Ek-6!Ll@1>TBDLLJD9AHTMx< z(aFb4OiWB{{Q;^-BE7bjR--CjhYPQ+OBx;_E&F!VSaTrrXv(vj(qcR-Gi@lpRw%Qx zKyl9HaunmNnAp6HfR=^=g+uN*ED0zb8P5on{v%sR(c4Aj_t)EJdOdcb~&NgvFMzupOJCnaQQQ&=LPs7L86AyT)kJD88* zV&}COvxdIOGvuPsg<3lmo?EfR$~wNYbW-D&{w&2%vD3X0pIv1ZYF)`t=gP=J;&MY+ zX!VD2-j&KykzmEqEq-im(_QH0 z#UoSs>Bj(XKhX$VX6y}+!99O`wlgXYMziNU-(bt=^&tf}Q=gpuz`$Y3tDg0=cQ0JK z$>A87rgn%vSVSCUasO5Giy=<%S33S5s(ArOKLh|fsr-ByfDFVD=`|2ye_Q~rGl$Zh z>3(NFOUKG%6BwYxFnWt5XaiCrXOBm851#>Dx)b{Wv%k)XccVyyfa;F5qp-Q+-_?rE zVG(>XU_yfv*Pp+5dITlS>0h!3pX>^R<`tw|{`LD980k?F%X^%_PuM2%c%EUn6jJ~C zrAJI;9U|sOaVMgl`IW!^5aZ#UAW@{~F1f{NCH48EIU+=J>(@P|e%+#~b|j&S`8k_yoagtBerL zT3Y^$7ybJf{OayFk2mfuQ9j?+P<#=M2}80j;5APpFb=@}K>>B#i5CJu($D5xh42mey1QRg^4B zImbYw@zn0Z6|R$Mwr>HZ(|f-Y1RZvYYeql2G#9FlA7$v*#42ptQ~Y{q4CxPX{84fz z0#_Mbj`eL%!YPwo2)qUf{?Y@a{p-r;xW~Xu8RT}P{n)+=qlbMz%%qaQP$E* z>0EdAVW2oM?wbT!k6GtNgHSL86?CGmsCpqRVz;k3R1N5UZ2Kp0Y_X2gn;Bp zb4BRQ^BlS1vk-Yy2=?}`zubEKSB>i&l1c76y+knsN^{4QK zZ!or;ayDaVXbnxlEDah0XK@pr^Kn^k!apSV@h_cQG||e{nL}-xyas^f;_Jq zTdY4>4pMx=`=f1j-}5R_C9)58)O;Mv0;}9CstWGetN*h+S&+iCu-`VcEt6bmdw$G( z4P|e^))@df45YNFQ8+6|1_SME(rk=136=2Y>Eo@6IYw&|%Kce`O=MeZpLkFDa!_BU zd>-{}G-LV#WmL{PrtaRXhoFAc?!jE6n4J5hCe!~z-g|~M)vay2C@NNLj{?%Hpwgsw zP!W(0(m{wylM;{`sZkM7X(CNZ0HsNdv`|8ViqZmvUJ^nmLI@Cg3n9rj@hR(B?^VCe?J;+(DOD;6Ff{HT)5zU0AcN zLPz=GY+W8?bg(u5DEU*PbF8+s?-dsy6Z+Tj>{guQy5H_?gZsv)C@+6xZ#()_!Iyjh zEcf(+h8!#wnJAgzyObnNALRk&6Db4CuxI76*x{MYE8m472Y}N(4s=q^=M9SkEV3 z>{b1P?HsXvtC7!tm+LHOJ>0zsFrDwr1Z^$CyHpveA!{)AJ zomf~yj^dVo@62YQUNqDG{)7w{<>eoMY~at`o^LrS^U$EB;ro-p5XO^u5n|^DCRUBd z;zZb{JVB)4!qIACs$U&5RW~zDaiHD!7f5-CaeMso^)ob);KB8s?T2aXc1)<|TFUB_ zw?Kd{(R^UQG~ zPwgWeX6Tdo*G=8@;d-%(gHe-BrD%A)yEjp(ldV4Hhue|Yj(GzOb9stklDfH)9}BQ!tR1^Cd9h5b2YD_<2$~0s&WM>ck)*UD2v04wIqLng__U3XEkLYv0F#U}263?~vbWfi` z5ab=Kj&ia8phfJESbaQ0r~%y_m=UsUdp7uC=bcnBzqB+l!y=<;)RAtP$dH7$5R#e4uxJs=JlM?S!x<@=VK2F!Ij9+s zA*bSz`NWUEEt`4&Q!?}RvefqG#1aT~v5i5=@7{S){Eh5GHW2Ja2lHza?kD+T%C8@{ zE7Fvs6NMgjEBs^kOgvGru$Lpa1hcH}1c`5fViPv0a$zWRlSak2N|k z*9D0D=wvSQ)zP6Lw;hP}$qe?tX7pzd+X2z*4>`?4HdnJN^%zbL$PqwX1*3 zvPUwHcJanr{583=yt{IesqktQ8v}hD#rFfdTLJ!l+~C}0OZzd@Oo43a-;W^CL-+>v zK`MWA%3lTLgU>H1AfSFxHO;(rb4c~im)>}Kk8cB78av2#@W9`S#wDN>;IBD1LdCYd z_zSwtax@7bTi0|llO0D3{$7ubYaZHRIiEnJ8ob$g2lD87Kb{fo)Oqba^7{^6A%eJF!v#xUZm~;I(Z4 z`O3BW4(C5V{+GK~F2R_9;3)O*(6=vNSa{{VUbeNh%}`01$pF>JZ%TU!B>m@$S;9|q zDXwDK{SB8x|M!UNFLxh(kD#wMKL2myoFhjhSzO{qZ{MO(Qcfhrb!#*J4IqxY#>8(y!E7b8evjo)Fyp+F#oSK(v-C-(-E# zu73|=#oc32Qgt7Gs#J&9^M~-FH7(&p0n0q7z$02ZSKGJ^s=ByoF$!E8EeQ}cV z-zQK13w7!s;OD4Dq+LJK7gBiF9F=iDkEWe@0bKHS>bPMu9K{2fzJx1JhT42Pwtixq z)2K?@^KaqPKhOP3`1JRV``*VBmCZi+QNvyTkv%=(FF18I^X$a%iM0bm|C7+^mz1lP zWtHAnvDJd)Qex!82OR50G0VX~S9!p>9UhFRLRViA0(KBKF9%tvuHy!N53_sy953v* zvz~m|$yn|?1gwBe%!(~OAi_C-JK}#roc?>yqW(hODKx*mmq73RDzXNosHTEC&1zD* zLD}6(z`osRKU&3~pw!l3l=iUeS6Y&9=oR-Cgsc(zi5bT^u>E&Ei!xv0o06c^gZ<%e zX|g`S*DgneR7Jyh+B%HW4y4=&xp%7$pHe_q5}v=FbKCBy6Uh*b^T`HUF+LUR-d}gO zH(tIsE+v+py}^4l()EIRFtCs~KPk?`9N%Jja7U4un%9c`3q~abg~VmEScuxx-2pZR zvVmQZRy*PQomY$Oj;H;OQ`0tE9)SRqcKf&ysmGSP4Z0FP2|`eHlMg9_F;%uzgm3V@ zGj$fGkl?Tad|z{m`GKgl)7RsS0%p}=;!{4zC?D&PIeRGf;$>yPn5t5TV#q$au|@;CzN{W{sWGe$MD#so^Qi0s<$Mn#FV(cp}Wd9 zo)4{3Mg2k`z*XV2N@r(231nHSeZru|J5i}N%)o_OO^rQK{Hhs1=b*3?P5$Du&FJBf z5GHY92Y+oiOO>aq(y_4AQY?LSZ{dBwxPbgP>V+viS7>Xa#Z$JJ3WZ2^l;AGj_Z*K( zJ|(?4u)19lQ8;iK>|oRJ5#1wfrD5$$cm$2@Yd#38D17W*VY_JNl5;M_{ruXUv)_|k zhi{vDrzMu013y`wp91391r=>s3HAO>~6DI{+&EOaS-0=>tSN?&IUx*tukOC~>t99G_Vo_(To)lb}P z%5`t++k)Ven6Aw*#Sr3;jYUylTvdDB-%YG^j>ny|J*EG=5dpCOV|cJe{hTu+C@AP@hur zG8=mH zvsmuE;#pZ+((ChOH=t-Ocw#*l&$-%9k`XxKJbvoqfjA8v9(_X#(mUEEF&9lg&jLBe zACsvE11L>z<^3Eu#QJ%$^01G@lH<>Owymp;Y4tx^Bj%kRfh;Z_EUuVU8|ARw3gSrn zHAXsc#x$(&`SRo;Fi8HL0KM9-qPPKEEl#{J;DnHJX#0(tHTM1WX)iE~xmT||NHV0=d`x`mj5 z(hKnUGHq9xPvY!Yv0jGW1seIHQ%+qFmz4T;!F8dGDCO|0kIz2k$=dIIeDgO83_bpG z)(ME7vpR=0d+uskE%UU0{PIm$&?*L<&|Z<=HUhHMd&{yepa&3+R%&2)`;&Dp)vF1) zm#nTl{+BqSmX>`XgSAehZUm#qL4BQp1m|8KnKDY1P@TVtVXYrO^-C2^l$5UHu5UCx?> z>Af4!lSY$gaXoU$X52b!fz#&WjB^G|_`Q>?Q2~O`8>3!8WHZFz4A@AU`tYUlJTdb{ z8^Nc%G+n`7?pauAtgv6m9V9OAZ3uSbWXU@t7D(@bn8P>hsjMa$OcvOEtE)JVpK)Ok z?6oJ1t=)!XvFV($4@)C;3Z6_Jh_JitUT@{EdXt*IW9x$BLv_z7oOfsNEQYPLcYAqm z4m29;+adxU2+dX8OcKeA&+^@X<=Re4AFSnjp&PB1Oi?!FK7^3I0EF>cHMllxS7MHH z>Cbz`1a-n*!%?U{cO~ClvU3*@GW2_I-u#@!zk2L0alm0JcEW{XncBzpXHb7o<2p|o zFs(;}*?X|J#u+DKM)u^U^J@<|J)j>7pa`@Z;`@Jy8FTs=Ii;1o%J})04`eiUNKFmoHENRp!|0(tvSS<_wPIL(3AfqL=cxNvo#UQVxK4|{3SUM}lfu@u3$hpX#~H+OUiGzE60_V&+#9UN5;9h% zYca))4>-KK{t|HKIru+&_%=9YpoqAIwF-PSgIw+97+ri_vnPJ>R?#rW*9+8ay)G^WW1DsFf zoV?33URO0`&6iO-y_i2>Wv9Gf`BBy_<(wm{_uCkTi)r3aH;#e*+geq?nZqkkMOWo7&=#+A}I}M5Q zc+>IZZ)hnPu(~n&Bn82o#*UTm8#lzeKjvsPzvkcFD}Qcgf8D|D@5g zy{9rSRV6A6#Tl5q`0_XZ-pWfA#@6K8&v=7vIRGYQV`BGmA^)9Ef0}ruk=lr1>m49d zJGAcWf1pThRk6%{3$mrm&*mUrhL~Qm?6GO!XlyUNaK)H*k@argar=Ft-nVW7H~JU- zo&C%Mx7X_>_S&*NBa)%hDNdiV&ZAvnrnr+f4ytE38@e-R10)Z7+Fd2W+UvRWS)T2Q zAi#S;M{bzeLqa)3CO=PwXUw!w3N91Y1LF$(a#`1YjBP&yS;s>q#pi(ijW20i51GUe z7d(+p5G~^pEjaodIPvetiZ`Q z_aNt8TqhIAKu`U=@8N?7HyIK)|3fhHg+I^uEKakjbH^ImB$l!DwPG9Si^@+1g`@!S z$Qpn{D7&*$zs`5=ETP^%UO_>@-pbA{yS>r>B(M_t{djO$d;);jjDI<8pmYnXyqTU( z7Q24^BfzZD%4fnLqaW^GzSz6PNgA(T=DYgmn^+oO1MABFy(td@nDQd*Za(YSIBPXE z7*pDqP_^;7Wy%0Dr_xqP`Dh`XT!8kA==y*u6isvkmeVVXobaMtGseloDV4_ak(D^J z+2jUqH=e&%)5i=OG)3ot<@lx2_frHn&X+c?PxOXgF57@>pZo3BT9_{%;WJtP#cqJH zlaFwJ9oMOWTAlE(8wM9SDY_0Yvv2dH^L8?X^<_pK>|(Vl(D24Lm`@Mvs=8L-o*i#e zDW0-?o$?5_4Bp-CkVyQ$Tud{bTzpGub5_+bDY;iq1w!$rGNXJZB?#u*fEh1L82U^h zgtC7A!#_Aq2mh7h^t}W0=Tch3AnwsYjAD7jAz@ossoTg_S$7C89?5@Z zOLs02OM8=i*vW64$G;_(G2r;IT*5Q4&ggDf(@;m}hoCCwcLUz3AAkALGwpzH0(VZ` zsiz?Dx)CAsLuEe86Y5<1zj%>*t|&kjcwh>?+I)87UK*CXfi*#xLgBWyd*yFP2;^=A z@z1))WMTU6F?4eo{DkAc?4g|EdOlg!C;%p`F0-M z*p!qtSwT-)UGmc%N?qeWVtO(+QVJ3zw>0GPQ#-~Dbv0+4pgFJ=($z~;N5r3jz>%pX zUg7WSh6`~BZ>JLN)vH3E@nh{yi?Wh;Wb^)5Evq*Mnl@CQ`%vOOKEJTNgF6?{ky*7d5G&04)K(RdXFe42s$S*KRph)CWLaCzS%;rKw=giLr8V;uW* z@_Ql1#Z{#LFsj>bZ*$eefPUl8A@MvsITtI`FwEu&?;T>;N!yaJo98~8J-UEe-+w0^ zPjS+59(Su+)C3&YXF5)ndL@`ti(fl1tig-42|*|}r~&uSv+;J)H!48kogzW#i?t*^ zuKhQFR9GNSy*!;ldc`s->qw*0p~zK_eEU$wlbgM%1$B<7C&foTdHG8B&%X&^iXdG+ z1^5EX2$U;>&>>ar68WSf%-dGGuihHVjo~Y_GN??%$VMt?S3Kqd_piUEE8mOgWHz8^ z&*2~IPt%#vZ+=t%ZSTGxW){qSziHEZ38j6hck7N>RX_gPL##s5Mcg>dH{644YBs6} zww|ul>uqy_fgDHhVWUKMTAki=ejEs%NI!Y-un)e+>{9o>eXoTc?|uD@d2)RxV=>*S zGet5bMN&{+t1~5NlwvoHT~(4?ZRdHnsDy1Nqj-@Yua3T5Ph-dIaEq3&eof!pGb72w z8YK0ByZx&o95W=N%C%>cMg?^)BGcOW9~Eu3>BeKZI%~TsEyq9!3Oqjd+&_b)Z%sf< z*b-vPJ@TdWtlp0b#`k0&`M6P~qaY}J14GjCePB6-kFceB;VCNl54vQU*$^J~2^nLMmt(Xtl_OKPGyWIs;Zs^-pLcn_wulN75x zH-%h@cF{eSkWj&zgSc>r>c>sl3VN>Z#gyhlmAV0IZ;YF`+T2)^yIk*v*$7_nl^+SV zxv1l!$6e_V`xImy*<05xaL#S~$h>!U3()X_Ow6@6^8n%dJ5`nb9HtUa}P~LCQCN3vA zGGO@atY`FPc%rzn#I|~~n8g*HZ9gIWY+YnSoblq?=fO5@nW0a65)DcmmVDq<8PnuONV^DFrUowvhWWmGUAt1{&?SA=64WJa zDYAO@jZ~^bc^5}eSf%&;D#te0L8DXn$bpK$?0H!2`Ng;Nwqc!~=L>5x6{;EBs-gZ- z&$B@*bDQGfhX@et@?Ms=EU(!ag1Ya|?}k^VKV)p1K`Fz7Uq&hYkbLRgv9l?PYjWhO z8~{*Xb$@nqdm|`%5atzOZ!d;*W0tF{CnGp&k^twchk) zH+eBTyOA3Htf;h3+a0xTCTJkWODWF{Nj)}S-I^!Td9OJ^epOvY`C_bF>V?fZ_~7vl zj|f_RB@OEDID@XJ7sJkI&YKfa7csC=TIA0QlnzL5k5Fz%Pomdjt*YUSv}Swa<{Ofn zZCtZ;(_IiVY5s@5r?JlBLn6*@lCjLOReoJ@q^6sLGevdFZ!RcmWm3>rtG8*M|2@qw zRaGGv8|PxsdvDd;F5fRSjNv&>?F!kR>*i`6gPrcQ7)T;*qO zHA~7K3@E6z9%y}(QrI}R=#tDe%C~XN+a6tS|_gtdYh|8gE^&v<@LDL z^Qh(FiSUeSli3_CGkke8Z2Gu6MBkiz8kIgjVPLqCE zZ!(R1@EI!`drQ@ztq=f7dosEL2dzUoq=D%AL;HG1x0%*C+H%eqq(1tYe{iZtX|G7G8|JrSAJRK7lyYmRM6us?|BJqR0`*^lkPvDbOp2NYHR3~~> zGuCpoP4Ah%c+P%9YR%i|#n%Rv<0Wv}77otYgDtj1h!#R`uwHf>XAsd;cvJC&e5cAVuZs8M1|%;fs|%bLYS)@KvFX^(P!)N~u1 zRGakb+O6@mDG?H1)(^9AP9FrUlE86S?hhy3DfglM%xxQno}S)HBR``{OQ8g_`vR7` zh3}3s){7X2^*qxReA}qnU+U@B7@BibzB{B2) zQN<)3b@eclb{nzr{-=oEGTZ45NZOS94cr4FUx7tgb7jy$CwjJiDw1{#dE=&`vOi`a zzIhEFr=qIJd6 zuit^&&0O-J;neA;^3jiA&FAHPH*cN9`4a7hU7f*QF~7}?tgA!u`6pQY(?fCL^m+{x zt+3Z$)gyy)CIB72g1i3a;!fa=E^zuC*5LyT*B*J**c8bV-;|*bnIUyAW?Rh%8pIqT zu+49?jy=-xSz-dWb!(!z?X^WTt+cYTQVvDejNyp`5aPq+sllAyI$g~@k27|Ab@y}J zKfenWEpd>OycJB*wj4-**LOP7{*BE!=eCRf7XhvBO``m@AX&=;mB)Zm*H!NUqrOD~ znNw|UPmNViH6IZuI0HQ|iC|a_K=7tq57)n6D{^f8)(&w<1Ko@kk#HRk_9DI;zDEv9 z_JcNP;`eQL!0tiRs$l)lK66Z&6+Om1?d+?{2uARjkm}_XirR?ki6Dw=@`g?O_8Tug zwY8#!i|N+h%Dx5=cy5?Pfphkv($VGh_+AxHcs!c3Wl}f{`{4d)Fr|@Hk~LSGj&sO5 z>ccG9X-`vzx9!qx}V*;{WD-a?JD>q!1 z09))|$z4#gto*)jx>^shx@UtoF0n#DCtA`wZiH?br$^gS-ny{<{W^D!wm?*M{Ex0) z(Llj|9Q1n!KPz{d{X43<9G15Ej2aQ*H$-HUpNhC^wE3ZhV!gI~z(^b?O~P@Boi3cu zDf+E6^XrYb&6fJ$Ay*LF(yI9V(vteNEn-$qYpsBg!^Wx8KAmX`#fK~5dRC5l$!n?~ z`n}X(Eu((WHU4|QzOb+gKd0Qk8?BjZrQyezo11(x@juo}78#*It8-8=$;$>p@ZGNr z;ZI_gHS>9oPA{TI7NccH-`r{&7$ zyklGmM=k5+2-nFnFy^GlL!pep0GiB`Cy@Uk*@Dg3Dd3F5a?-uk#|S zTWtUw`1kqMo+Cb-%++|kET~a{il)2ra@#CnKqe)GsI*#SozI>7qqLyV`nrxIB+a2i z;$gw)^ZxaP%GNC9t)jFXOlwItn=Y?HeTn}IVW69RVHjoR7Xy07z5$bH&KVfCeHJtY z3tpQKZA9t@UNzG(4#-bOQidtp8~sVWrBO*qT8_iVdU#C+S_o`R8KB&E#CHhafL>0C zLLuaBZ~;{wx$-}0x!pE1NxrcK?(d|ebIt3h6;cL^+aF`9HvKDmk2tlBHu-QVFL2il zwz&j8lgD>hLwqvatn;x@cVxhs8W5LNCn}@&MQLU_{gr|McB2u00p;;^FkH0SSEMA- zu4vUpaI~(T=7IqAUx|0A7|dZ-^c0EM_2ZGp3fmWlwe$ijNWDvgy#5{Gm6lJq&U8DL z;fTsk&cng|3frqxi}U54Rr4@s%oM%4)zYw129oGyoxf@K055mxpDq@Rp}4@O=@;DT zxxNjthQ@BI(xw-!pz>JxuHb9)#PLeo!5p))Y!F>;$DN`S<9rNBgO0{;2=q2dJYhg{DOdH{t`#DK%Q~w6#2~?JB=~T?O#F z(>tzX@I4J1(EBbu{Q?C0o)_Wa;e9u^kc-9T#weRWN|oRBjL$m&rA|NPnX2!^_tz#6 z2xJy0>k9y;M?Me~oay4>VL-uE303*d4fJhL@Ww!UuZas_-6#Ok*>g7LhvT=u9uIlo zYSeMC=8}AwrFEU-MG*xbFrFCgeF5^`tTNa&FYk1_ENN)>;pyjG)4S! zm6jtiY@np#0+`a}aH~0|hdN0yH=|`!5#z+;u;WBx`^I|RdX?q$sio3h`kQ*0AbeyY zrW7OZXg~-k9=7bj)8y>>=P-DbYqYa9Q$+)oKN1j%Z^u~YDOR~gGt;kuY~)FJYg;FR z_jLPvUxwcjzmB2hq8GDrfW%l^5$mNss`5V}5^qiHLR4Xr1uLT(V1Gji0B;GOhAJysvKd*3`ghn}G&B zSgeXcZSzUL(Ql7a-fsL{Jvsc(7i07TEhs0Qvs_h69k?U&JdsU{GBJZsZ6^bw7o|G~ zpE9HFsIJ1*wy-xMK4}GR`PxEfkIef>JwuOq{A`KqyvrinK$Cl>@8rXiU}h;$U1L*h ze6|&ZLEH_T4s&0ZfX3jxT~`L+@ksFKwaA73n5hQ~n6-N4=9mD>>ZXmiwCXb^d3I@T zxN_YE-@sV!r?Z8%Kjw9cc>3zn?Qj<(^IdEN`b!f;hFK4rJxMv||H(3EV7^EyV(o!u zmAMIWv+BNBhIewZvkoM56w!`>vMb@|7v--BDieCH+4NK@&_8C2J z8&z6Pgk!a+V}N$D)X#cdpL%st@UE$j-9lu9Et4qI`_7u^r^Q z0B@L^Y@?ggbFjCQVusAJyR1IJ6K^%8TrIwd`JgPbsk6EdOMsrKIcA{jzX7OOy9=Bm zope+E6iW$0N#8%oC+|X}n^c_VisRd}D;bl*Ikp|vi6uo|ekIq)C;P&U2HgzP3bG^= zglk-r`U0?);mn=p8unEiVcsON6p@O$978$aCa%uNE-s{%oo z^$B^1e+t4!c4&@RaFyXbMw>^j->LFI&p~f2QxBRw;Zosurtbu;&Fho&vhJ`kPYdY2 z-3{UBe0z*{0FGUDeUPb5);rm%>$;1`lNHE zg`W?K6uGK(?<*851@Gw3DvPSz1ht(#n}B()r1aI2pkN#@>a9@VI)Qx+=YjUIux zgFj&V{IpjW_`HXubV%*aWYw2%a7iJ1e4EjZ%3!m#Ii;EOGpj))$+zGIqjE6JKpe+8 zzU@sX74oQF6Wp2?=eTFu&eqq6S)smaBH2CIUkRrY(oF!R%GJdsJ(*4!uF%ZzpXSP! zHDwK|&Ib`$vy^uN>>jMmboX?3j|ZuJF*UcjQau__6yGd9buZ%BdwuliV&ehs1}4~%FY+?Z9w9~P2yh-rs| zt{p$SLh#ynACSB}pACAPawAWa@m}gg-w)VzWod?u0B>jtDv|Q;`nxD3D zm;4(!2v-~ppBU_)=HAZ)--xlcqZVOqSGzfEQgs)LahMo<}Ku?63Fvx zWyzze+D&i6LaQpjjq}F>12qj4@v94}o z&}p!0Ml82%r+-lcV}5?#2Tf#tJ0g}K7P1_bYx`7lX z>W6W!gRa%W0rZES=F1rpXZ(+xxu*fTX(3cHw;+@2@^itrI`JSxq0$ZGACblG#S}u~R&5qKqrI}Z? zTU!YNW2VVlj4pmSRYVd=RB(RRk8rbdf(iQq zn-}4$1hJwtf#C~5|12oEO#cT#>D6C?(o9u18xDV2Bzl;VlGC^_3BKe4Ewb+^^Eu9- z7p-$&AGwfBmPna{tlOs4_LUx~m#KdNkN;Bl!lW|^rzO%eL2Xpnk~>l`_|xV+`a-_H zY@4oUM96`G-wVRWT)~kwA?g+g`YNdOE2)@C!oxNF;NVKNUT2f=)>;Br-M)y-jbh3g$C=S+I&ELXVvans_+{fzSc;} zeggcG5+?6+WLa(#H;7$bpU1}|*qC5u*qc=f%eHBV!rrkj&sn`FRfBD>PYeD898aC* zbQ|vu=}x=7>iKHx`3L})>H*&D&2V1iCPk#Yz++}z<=38%emxd)$Pd45TzGvT+a~e}-C!q8FcrnY@Cc_=& z+9v%L%(qD#??I`sseh$7tXJJ{7_G1wz{@wZ3@E?&K@$xg@9e zn}{2t1sL-!q_p2un(we{>?Wv48!&TjG%H{0k^ubQdH| z9X}48+0~JL$v~G)wGJ@1O`&0VazK>iRFLFN8yi^Aln}`Co&H=oWVBoJcgw`xjCU03 zW=J#${hNB=dbI~pRCT*M*1j?PnAgUzla94@TF};b#0roNch}XmsKTu`Pfwq(19SJ+ zNp8No95U1}%2PwmOZ(ZWxabqG?bMNa1*^)eKfR{9IpWzk^%I*t7B`&;C- z07(T;WSDg-t?CL64Q7Ps!O`1=QRk$!GXRR4Ak4c{zEgsC{}OsPe>0nY%C^Yw#FQ3c4&BSEVj z`eF?N!1Yx)d~=mYHRQf%&DdW5q~b?oT^L3^X( zcCB$_t3(!fDZCiux_5)5o-}*~1?+l2g?s8JWVA}QSX0W@2g}j;(zaLenjP=LHbvrw z6BWQA;?jtg;;_xuU>!#thbcG9cb0?Z0$m;p33c3slwyWmgH=p_DtN@HcW>NA7c?$- zD&h`0_!TW86dp|JK}TAGj5_UJh4kCJ^mZ-HUm!13RT>INgV$gY7tkSIIKc(q9u7dD z%U$A%!XV+nTlHsrl#`8-M-!gAPv$HIZ?F{~s~^ls8AzK4Z(FNGWvGfnSzpp=1uIkI z-d{-POTTXOJpDoCxoiMzcv8M*D*2b+4_f}=#h{Wf{v?>-X+ix24%?>}Bv0MUFDfid zCo!a#po^$Yd%((|Z}}-8MW%Q!^&TWc*y_>V4ZHxJ%0i22X|s&aZAz)VyTB-J$qPnj z(B=I1nzp`N_ABQMz&R}DSkYpURL!S$@wyTX=Slud{TmG|juCXiTjP*dzxndN89}SV zJx-j-w(%~7ElYf8)34JW-{v|Be9fu8aMXq`izsHQlxRo}-xf*O_{9 zXW>m6F2`ZOM~ivdN<_Br^*ne}ok?9;lK;HLlaR-m8M?3VhL#6Q)D7nxt!&+M;2zHS zrc{&R-$!Kr|L2q@uU|@&`_TUfl%}VE(sacauTJ*Aq|aAj$cJpu%OlESceDq|8qQ^| zg|kU*?}iDEmKC}dg%C$ylpPlR%u9Wz^6_V{|rt)*>C%S;=WdOUn2liLDFaPLSio=RY)}f zbYw|`COtEFb+f49mmc`r#^MtzxNFacbrJ(P)3d|va0}tC%m(gN9@J$h1+iBOXNKA~ zOTWwveP9J$B8>s*-+qN2m83m7+tBbA0+3 zh!uKls|xhj&hdmW}a8sbo;n(D#L zpm$N10LKLgZVgIv5Z$w=K!CTnHddoP)y)H%$VOjDb1JOA9u+HThCx*`M z{%s|)H68W!4t2)Jfh}yxN`Im^8a3rRpzPEcWl8d2W0vhpTo>Ima1CBC5MxTf#LUvz zb~U|YU~6QM|830EWLkbBZYX!|8%_vfV~FIi?G%EEZI1Y_`jdM7Lz#paqhEp@7cG6J zE=%U9b%uLCTY2QN52Ful>(yhFqh zWHpsFFvZh*)>6rIP69CMy0!8detv=JYfQEJS_8q2N;i%jw8^UF$mT zHH3pLpsfr$La|!cX30tV>ZaCmt@UAu&|pQc-n(SKpu z!Nq$wNEs}x9scwM{B*Xpq9KK14y=MKZ6=Dn+S=cd-E&8MJxh;H-{RUU^MHOs`9}J>#@DZ*Y%mDx(W|_pCE{fjbhYx*QPIU*HFiW@ zyeH;+Zev?-^J32Eb{hLz<7-iKbJ)lTMihaV+pay8B0s-r( zM>gM=>FAO7w%R_P^AV8?x=AR5TN_F6Dx-cR*e6H^QYjK^Jkln zg)%t?Ult`Atllwz(Ww;i>-7Kv%%p{5egO-<->TGQz!l19ggJ58%pwJFrG(J6{Xf55OsZ(^ zLq<9A({E#5iu=mT3IUFx+7xep1TfS6E;=+jS@~I^APGMvcLkQOl)2nRmZ78&JLnW= zfrtDfJnf9XeNoGf%dz>%pVwZrEenjed+v?n3CJhVQ`5dAvzWHc<1Bl6W{yO)i_H=g z__KGmv;ehNoMx&(^OMmspLV$8oLG_Ea&HxnHug9f@yk5)oc1;;9xJvWgN)TO?+2n~ z`n&=zr|0_5c)MQkh!R_}IC3g~p$324ZSd9Pn_chYe%PN5qTysKc~a9x%#cJ1x5YFL z?7qB~&iO9}qIv$2Tg{etQK{8MT7J0t0J{tM$?YJ>PigKR$A=E{9baCQ4u1ANTL}+& z=%AM3+H>f9rA}vWd?|JP3qjJWWzjbrar&^c|~JDJLibBd-R zl^o0!j97B5H>s_blp$nY?Vbn_eic3F{Ic%pt7cI8@bEA>sDT;AVS5#{Gr=)6$6S1^S*NASddz7td^3)^_8>NbE44CKATw$62RL1 zErdC%T)%~YiaxPFhSiVn{pw|{odBxdaemF{k@9x-K`e3U;rJI{05JQlX0fqDZ~`n<90j zBEj!Sy^| zX`!JpH!zs}%`d^L_MPne!G=ie*SC}2hEiHVc&HY7$$*u5+n5K}3$nJJi`v%(-|RiT zoq`S0P}!`-9@8E3n15>T>Gw%xW!5#YyOT9n{hV5a-@0j4``jS^{=eC^QoFpJ7>`{y z&@&z^F}?m#ODG=;a|7`bhnZ7Z1cVXJsWp3^0W{lpayEYmRPKhvhx)C<gbQ>>xbTMxH*jwW2**g5@sSjd>TaK!I;?`JC-zvrIa zd>I~oKrvCs&Hvgtzpo7;$xvhC_?@K)RUbfu`kkl!ZHtxeb$I80C{`VuKCm-&cB|QVRjlgbKKgF&Pi1wrBE|o&H>E==|67(MyNl0E)8^w#}wdD^?4CYArG$27Qx#-8D;+*cFOeaktd~3 z3ileZ?4qyd3CO`~6?V<+)<^F6wXD3UEg$7NF%Hvz8?!C}xf>(&QJKvxbJr8ZpZMks zxiTozX1?0PKma>J@E8i)lO7b<9=ET15r+aTG(bz{2gGobDFx)k_V`Cf%UO56tp2uU`luNtd7=HD&@h0XXL4gLuRAwPj8`pk=0IWOlgs9e2` zTnH2n6e&4|i+cn^0{0N@w@2q=y|3awW9tV4+UL;1vAvw_bHbW;knDXb4xL5|i*a4O zdDLu1@Dq6|pQPq*&*nbfRC{7$e{{mPJK3()O*TjWw=nLdWa}|FP!uJeZUW}>)&ISJ zYJcOm1of3Wvxh9Desm_k*n`iP{n-O3=~drya?Q2pVp;cYE(A+#Hec*Wb^5{I5>?WW zv`+A|2}iwN+VxXi9ul^EN<5WfQ_wrj;b#}wZuK;Cq}m=~V%C{-CB-+`A>^biM4ND| z-_HzcS(;>5<;cA`TzNA`d96ANzVo)F`nWjj5hx9p8_ZFG{+Ql;f?kL`NE*0rKdkUbjD-{(-59-~QF42A)}TtNoOO|nw|H$C|SjO!jts(v}J zt7`vi3)M{to6FJ*KwBm@{{OW0?eR?S|KmERL!F{iDj`&INup44ol?mq5*?LTw?&1O zYYa;_Ns&fELs3#8)HG&uD|aho7@OPNX2#f`JCVP^LzZh{i8SU_v`Y! zzn-_3d|>n2^VG9(M0=Ei$M_a3uW(BWhl>D+sDrjXs8(+jBlPrfy|Xy9_TALL4wLLQ zp|1hK&TvodR@rw=Q$i(Jctn#7uS6@0|Hrmb^R?KpXm{!+twSY)r%n1{`xDUq2^DPp z4dBi%S;pUg=I6ZQ`?v?e7iU+1ayMfjJ|AGHXT5o%IC_AC;bq`#l1i(VMohgT2dm^p z>**gR{kGF$ji3RkmEap5KA@^nyL4Lh(1S-P4F}^434{3?qFJ3+@@ji4BhdlLAD+4H zsg%Fzwt3y29Yp_xSCQVjP;-(tG~lru>&H)|?r`Z{JNAg}7ZxzKlV<=%m1N@~ z`#s68W;P>sV0mjx8D0G!NN4hj_m7!u+3s-!vrlNz;$C~`Qk)K`t7UFQ4`=&_Ote@I z>Q$2b_ol#SvBJk_gKnXE8{|dt#V8Z9EuIzJVZsh1=v4}Ca=t5*&&TF`tlwX!faUcN z6UjbF&!3lK&moS9JNlN>?+l#KenhC${-tWj;dnzl=DVKCr(=X~e34!s5|9*`# z4<3#%NG;5Ju(cO^_a>n9`|8dwBVje}uH)th;U+VC{UEpUfyQ={`i{a^TOBsJhtX1*5B#b02B zyqvNY6gw9u!$OKSyTE^)02u`T;K-%sKz?6VSQbl!&Y{_s!)&(=&reTjXR%Y~v0 za>FGl7br&Yy;4ylnlanCwXl0Xy{+RBCUCeQ{rs^J{v>DnTfw(b@&q%oDUu1l$P4M7 z>(usJew{QDe$ZVJ4;I(JNVJWGSw3cMWA6qTVbAhM*)#^_#4MvE+c zV;P&b+9=nq@o(Fr2PiPtOtY3HZn{%L?NWui;R0hUc@SMNOu0AooDSf>dZN6Ztrh`*gt#3w; zExK5w^c3Q0va54X%OUr(Zm>QC ztdR;{+JO`$E4(cTINp@ITg_~L=T6~n*;17V()CvJgWr>wCvZFlHL<|BDP7qyEgk9N zPkv51Y9UN#RC)H>DF)y1CL1;3ffPq0g!Np<_=xu_TvkvQEIdm)haP`uXv3bmd~4y* zO;%6wFq#mb8~y0K@&tX0T@^?#{ZhY(BGHGQW(NIwD9NC6tH2id&N@L_T3S&#eDV)XGo94Y%B&v3 z25H2^nKJzmQGgOVeN1UdOu6&@a^#CegZ4IoA?`N(huGGAbX`q_Z)jj}LEQ1~&X!#5 z$sQ`=9O$BkWr;?)M^to>At_3zDe`5-Eo<+?K2aq!YkShlL_;0*s^m&qfHB&YwW1H! zlCO??>U5k1xYwNG7oON&3<@Za9evwYNi|EO-F`jh^IoBmB60mrFZ+rcwS#g6s zf#rh9T|tu;uKHkpNxTdTqyIK=v_r!o^@FWwDorGK_f5F^pwdb(f*Xq`_Q>dWoNxY@ zB919ZAT6&Xq>_jcHrcHwo!KMqnRhFD>?RN~#ucq;Q}#k#goR_(a$#_<7=p_a4QB~n zUmgaE`!dI8;1(j;n((E7X!@;Pk4h(EnKKUS1*mq*QQ_!{;Bfr7hl7O}Mt@r20^9q*DCpV3)iO&GDM#b1W z*e8hxugWQLdu#{hpEfD-HFBh1#9#^cS;vuLP~%oFp!w?Jk~S`e+pQMCoN#7>01clj zgVgIzE8UUbSKkNbMwkDAffw_a1ec-0i>bL<$;W);=0}VO)K0vA|M!$9^)e-U+htC= z?{aAiYVTO`{yerLmL@En@Ci!iJhyBk29V1$vGm3@2?OWHAt6Fry+3jvr;N|Ja><0< zBQZ$@L6r`T?(==@Heig~>bp+G4LaK#OO`o;y7lcF5>mD7Ory}Wz1Z%_NAta(B62a7 zpPereNSIr%c5fj|zT07Xcl2rW)Yt7 z6`WQkaED*0v>qT*e0a)CmQTq}Z@|H|sg6MC)YPujQrVrF7pX zuVxr>kj8h>CGh8*qFTzEx0%JwO;Y2ovHZTYZdhvdQeT#&nfW6f@OE%Q#|%<<=j8b+ zPZ4F^N*`Q*5(CTcTGvV&o4#98*<-0JwX6|kayy(W!(itnBorK6@36Z*MEaVGQeRcM z->aHl+Oq%w2*eckrw(?~>QLl-Y|o8O(#eOcXS zInYlP1I@;%sJD8R;|Eh$URO^KatpjOP6lg=$LvNr@%568-*`{zDG$D;mo&Drhe)=@ zoK+n~f|3ioDw8oGKWA@bFF6&{@=aP;t*AwtQv`F&FKQ~$Q`xk9&np*AYyWER)2 zxP6Co1q}am?+LrDUa$7lrU%?cSV0c_4o^&u9MdyvN}z1!yJiM-DircJw{)6Y1~(jh z44G94jzMdxsh=QpOI9V2>L2Qt3_Shv(yZI}!>BsBr3Xm$6stfC=$baHFJD)OF0)vU`j0m|Jp!1 zmSIf@a>QNjOi&h@S4;Lf9WbeogEW`qg<5!BiY4_cf7eNp(->VQl^2rb0jYVU>=#!x z%vJF~$~>`2IttTpv)^Rw_@%^>>NkCO#?eInVB~xFAK1zfC4#LF3$9vimqL8y?-KIo zz5w>E~0udGV97AdOt)bz$?7q`;A6qwd?;xjMlQ~~ zhg5ojSyw_o>>Meu19l54eNn%ZWmcW}_VRw?_W^Z#Z4oWTcG)$X$FI>@_dAQ08Sg*y zf_K6)HS8@;NTlVaycu{cv-^FpzUmwze6zzi`@zzQ*YEH)16a2LIyTC;pS@=~wnd~- zC97lDsl6j}eD3<}v^}4fR?WNJ<#)DHRkdTKU#Zs5F|MEf6HJ%3q@;H)f3aeD9q;me z?c$)Mcm7sppX@9M6awS@c@BQ>H+gQ4_QO4y)u&C>xDCKM=qFpB$Bcq6@_zz1y!M8m zx)NIGr`2R*Um~z7S5vP5H%oV2y%So_y^Qn6Hu@e9^c1X1Yih%CpV~D{w?Q|zcy#tM z>=UusmBF!P$^L?5zT3$2fP+!(MTa}}^YV6oP1(Ge_at~}?~;>+-O@cR!LgkTvmR3d z6N7Sqee6gnEeLusBWe3UjJLg)brJ%DmLWbnpES)lnR@juom68P*%oy@dHgP4`Tz%(t=Hc5*oTrWs=0`TS3`8l73OwRFkOip)6mhkBPjSf963xN;0;X<2y}>{-+fDpc^kF4{;| z+LPqu{gEkSiD~8&nRcJ(#LtY(FJC+I&qtmPV}GB|bs?+G)7qR(f2KDXQwuh5^-0{? z0v@PfP0x%Xt{pf=-Zc7@M^8nJ#~pwBXZKn4fML$R=yBVEg>Lw3S1oEdm%HV_I@TQm ztOhbaw#2n>;$FKJ6+g71r4WORhfqYhXlo;RuXw$isAM6^Qm+CfUXv!~rcu@~R0Shs~e-_uO z1Q&1g;U<`^`(U|)}Gaw5xdo&Odbbw;U#=D7M>zl2qDZIcr zj)GMsO+vrPd9))PZy}}mY35g*b4zhHO{SJ_#TM4Ju!*xa;k^Bcr^7m`+6^Tn&1L~0 zyk)at)7qE5i zrWdTtuX1w7W9I+LcNFX>U{k}*ua^_>G$_?(cJvCoL+$fK48Pm5yP&NJ`=pEG$b~0% z$cvV@rmy1K(8c*lONW3&@4T98}HN?rwD^Rn;dfFWWWB>A}?(>l^9(>>UF>zb$oTjCDS|Iq5syf0vL zPq}^z5h7U$nSuIvbg}i-ZDw-RhxaT2_()?NldfRWis}ov>mnlx)1vrmPCysPvi9dff-HBChqh+IAmwtY{Oq6MM2gloC{F}QmD8!ac|D^*qo zX>9IS_)GZ856TIX)R9M%3bQ6$MhHVdqG7C{kw@*4(r1kA>&<}H%`3u#J8Nf{j1jhN zR$6}p^qASH%Z+?h2Ha#y#%X*_PD>Mn>Sz33=Px{MiJMD~SA@#hQhERjIx0|$P&n3f z{OK|iZ8Kl`?FMNHjS0i4eO%nOj6N@Jiz0EiY}U)(e3WndJCm-k&XdPo5&0W|FRfm4 z71#ovhmziST1-m9#crij_Qz|gbBRrchAMH_=ni$!;}`YF>?TCkkvjaHP49J>c$A1v z#N^_9_!!tYMjOnNqK^Q(d*>VHK!y)}XC$DcyUv;vbFRxr|RK5Mx9JS+ycEe&Xk z_Qk(S!_jJIQu#y7QcdX``_pQ zAIe}cM#ne_K}hGLZbm%&1%8_Uacqe-b}}H}^W&-Wj_$KTuctEY(EfNF+ow2DWco?0 z9qfvIP%|$v=Ey>ze^`-~A2n1<&Y~2F&u$i*o8*K4RL93;XqE#*^jykBVGcnb#Z?*! zcT$g4*}=pw3R-9=SWK=r8L;sdWs1t}bk)1mh4mesmI@RLRL!cI)gB@lVm1p2?DI~# zT5jY!Cs@T0A9;`7TkV04i^}WYFx^8KVh6qSZLk^;tXeqn4M!I?nX1Y{t9K2T15t(! zwZCP2R1h)jSC>2B;%|ZiIC`)_e_C{y0aZopxoXlu8DaD(@h&Tm<@eKH$nZ!-e7fs$ z!5(iRb>X~ty^$c83e?)JjhE3ICi3ag!s*XT!xi{aO5$s8Do>ukZCnqP>Ro6uUw70R zG6i+qw{UDjt~0^wToyk_*!<9W;F%QibuJSvRv9!+_yn?}TIYlKLZbAIG!+iyA`UIu zfCTNH$G?~R$B6Wu#Esk0tNF2o5K&FNh0zPKYQ1q=ReVO@)cdzS0Y5%mcqRQo>0GC4 zpw{AX6E+44^3LqVV`zMS5yi|qYf%v&{i3C%HRk-I>%ih@dh0X#A@=jfJTWTkR+|B$ zwa4fGK1B#!ScT^Kg9?k;W(zgm+7q&6=H?Nfp&u2ph9NTxyYL#i-iqdd+>-khRf7L`)Jk_SZswLdM#m1@b=Hwg~+5 z{{!${o)s-bXOA6RfJIdG3h`uzKWxzDX)I9b$p1*Cn|i_6UbREVg5~uDd}1!{xox3B z!D8MIWfhw*zIG>F8+;;uT;Po0a|>5Jz}|s9eC+T1pp+x2rX4>X{|k2c53lHetjmvI zfF>*|6)B>zHH+B&f8fQx2LFUB2ioHn%}%wDe`0^VKz^gse2w9 zhRiFev=>p*4c~afS?1%YpWJVN6|wLqG*OK?FMgT<7>%<2`DK`X?)$|lK%|OgUGM@5 z|AN`ygwlTlc~E>3E+R!j7+fdhC%&NFTi}cMd@zBsJ^L#677#bozCeMO)g4m3vMN4{ z&h`tRSH1t=K>hz^8(MbFiV~`J3YVHSQHN3tzGUXBAKznO;B_4=mub8}_3q>o7Em*h zjN}b5oT=Jw#m>sX192t|7h-N$0Zu-6#A*;DT?fh+d30 zTsWfr#VK$9q2l1|=lkm}ij9DHg@+w3qO9z(T_Xz}0GLnx>oQxW|7Qp44=KLuCO{f< z5U)38c)@o(FJjzsa(<#bz#j@=0JhNO*bry8+ED*szO8x)v;UatfGe7>2 z_J4n69_+NXtg)YY5yFu-`AxK~I!8(ma(cspEU#NbT!aqDvWC#2pP7%h%3UmnEsW|x zqVSj06~CMQ><2&p|1Om9mL2p1kpC#m{u_S%Pb9*n6%)N?yU@ss>SEcD4Ym~WxBw>I z1U;=)M#ZE|7hA)Vr@?l*cE(^60Jx`T`vlm`D8+h#V8I4OseMa7H+-GXKBh=)INN*}H6MY> z+@=0-cXq+^=VvSLuiFl0p@mKKMmBRQ>RBaiw^%kVvADp#BS!rX^e=S%mO*=5D@&7g z5u`c;lnW_};DBXU1IvS;ADuQF*1(59NB<7CDBfdoC&Iv^X`{)y*e_$^_F0^#{#}`* zBOxuBL3YNl<`K~80eVN?%{Ce9pEM<6lpa5fWj?9PAF5@O+_oj!V-M@>Wu6|oTUJY= z`uhCN?<*o21rJgAm#|(o)4wePr`^=COeh7O^MPV|ZlT|XWdv>`TypCy5vU-@#dKy1 zTY7}~NZw1?SzWNd7QYc1cJi|D>Jz&SL3nUG(!pSe+;{SzY$l##%q@W-wV<q}znvkwH{a)1Y3r{WG_F;K3iS1(iwm%JT@Qj3!UzMU3hb zP5bRKuIrqf!c3u3f#-%7%z)1YSunx}3}Gw4CE_=!5|uYIkrNT2GjVA(m~P^QUyv_b zT+`;z@Az3q>{EAT5aR%r95g-+PF0mM-Y`1vZC>fIRQP!&$|1DEesis)+AJh&F05mE z3R(m0GID8BzV;;4^B2B%Not&>ZjcdusLfpi5Wt0Y_(_))wBLD;A=1-9%;2V41O=Nh z;4nyx1JUB&HV=*ssmq+BV->Y3E7O+(h}5w1;7e31-Q-Q#&UMz_y8Ot(aaOimZK99h zf!#)Agnk&WtE0)6-G}tQ$byD_f7w(JrmK7{d|se!2>)p$ha%;8Dp0k~UKgKYj;Q?3 zO7nS}FqeGA?@8KT?%f?~dVTNo{n`ZO8n0^giKU(-(EvV@hDQZ2LwS#F2i7>up}tgZ ziBzOH=x}Af)gBI>x+K^&Du3oB(vtSxKo~V-%D2uqsdvJrf8)COr7K0o)~$2W@CcdQ zJ#zR)N-ppzw6Bg@uia8D5zZw#V*mfIqA3+Rb~X6F>+dw|9wYJ#JR zk%`8mnJhV((k)a0<&aT0>&VbU04nTPr1h<+9vU!$PiqDoM``cMTe)V@bq08PrhEv*}0EX`x3_aWl{msTcxZ z4qG&*k@TG2k?Q5dL2N;)91pyaFs%<5x@$Q)90?s&`+gOO-iX}(82 z5l&&4$PwbHjXJ>{_tsp0q7n0Eca(!d7?YxJBsmTQdFFIwIIW zX)e|t`}$4u;CxJXXV*%$K44Ty)VE88eB5rn+X}DF%dAu}bPvt$nic5&Ha(V7fF$fx z>baYsbVf~}`57gZ0Ue*QEeZl`Sz#rbI|iP$e_M(iL9VXsl|fbdWenL;4O5f{WOtQ; zTt)~Sq_I*wRxRZWUQzI)7_rIfdCQ=l8gufpysbXCTE(_t3obrgI3j&E_ofO7x?@9JT2 zo^5l$*!459c=LMTZPrXZ!HoL>H`SC~W;!PjRy*L;T66)=;$x!e?5$Th#`gBz0klev z#23Tgm&~Zfk4q;+!+N3NNASQ)3iGgME4NaLW<8THk!1zdV7)h78zMbIxPIT+wv4q@ z9wgsqj;ge&0D>++&vGy#&hQY^$YM5$Rp^blFqAsIrx@pOC5xXWbXJZ>z%Yel z>CV-oT*8U^uGw(Cxf*pRc{PzWwqhx2pGo)bWM#3|%7nKn{^%9j*J^sS8`8CJ9Ijb!|SqwsO+W^W-TR5x0b+%G!}$c24dNT1l<)rXFox}F>jPh zJL~I)Prc0CMz-2^^wH+1*YILtKdX$DR|9iWujfF=5~PIJP<#@T|5lsFL&iyw%(V!? zob=GXH@370SWumY$2#VqD2~ZGlzu97n!jq5WgFMgZa&WZDD*g04_06_z4o{b!$%Sgc)8ja|gNM1a1O8 zEZw>S5Fgm%O>Y9_t2Jh;%N7hH2_w*svg9myUp8#~o44e@FeHbhg--bC=hiAu#R(oZ zDPNch)x7fvtD{7M;lzT^AblFU3y1i1Q=}rlWII0sxFojREH1Uonhi_dm}&v__V&^U zfTzCbCYUn_C+(#mezibZ-a?kH=uu}%OYE3UeP-+xKF@&?H`Z`ct$mmTMY2a<81p^S z5cVrFE(kX8c~F!9Afb7dnLJRsr03z(%ZgX}-p1rtj(gB8U%yT~;Eqfs`+vmLAyDSq zRFvQaXk%?%!UX04ND$BH@92*TbqR;Q#Mlj)hn*_wM$R{K1Y>PvtO2uNwd$e0Vh_az zdoYoQy?mthA;JOR$QX;shkia@wErgMs+DlC=?^BGX2IJjuf9(^*S;xJ&!3+ zSWY~%ploHFCC0QnXh`$fL)%bYWz;aKvPho^umu5_4&-x#Ns+h;UpB1g#vEkhch2)X zXnSm&Q3^}$N}Mi{!|5o z>0tPpl;+cX28s&85;lrP+;GBv6C8Q@3{!GOVYs9mAA}V;H@!#Fr2D!zL6OC^CV4G7 zXff!2B}xku6njec;Y}FmAlOYLZPh^c87kN{O&mtMMU)=vJ_E(JjN|H)R{h?W@eAUn l7^yb5AYW%#?_{~ak{rexOXBxUr{^cPK4^a+cmJ82{|7*D%JTpK diff --git a/doc/modules/ROOT/assets/images/eclipse-io.openems.edge.application.png b/doc/modules/ROOT/assets/images/eclipse-io.openems.edge.application.png index 06d7f7471e31cb2952158d8f0d8a99b22375a754..17ce705f7082bb5c56515bb5cb50a1f886cd5a72 100644 GIT binary patch literal 10832 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 diff --git a/doc/modules/ROOT/assets/images/eclipse-new-osgi-provider-simulatedmeter-final.png b/doc/modules/ROOT/assets/images/eclipse-new-osgi-provider-simulatedmeter-final.png deleted file mode 100644 index c1d245d09d10148f822ba64718c04129e5266bbd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12524 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

    !8B=(F12 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 diff --git a/doc/modules/ROOT/assets/images/eclipse-new-simulatedmeter-bundle.png b/doc/modules/ROOT/assets/images/eclipse-new-simulatedmeter-bundle.png index 497eac6cd4d5cc616bfb8d3e66e049514c6dddfc..8fcae8c0d3a294d3bb73d8e14a164df5756a8071 100644 GIT binary patch literal 10465 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 DGf=#q diff --git a/doc/modules/ROOT/assets/images/eclipse-select-jdk.png b/doc/modules/ROOT/assets/images/eclipse-select-jdk.png index 3229d9347d1086964f05ac8e1a4c708ba65a18a5..ecf214bc708efe6ac4ef4e7feba36da46cffe006 100644 GIT binary patch literal 39872 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ðB|{Dq$4nvAs}MX!UCY3(Vi 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�G%g&I0YfitcgAoD##tlcI2h@21v!GI z3HWQDCSl6E!2lXs6`H+h=?4*}6Wgw{waxQ_bY#{E?@-yQuQ6sUQg_RW6Wnbm`V|281>i`xH~f~Lg(pDGA&&A^R=b9B~f;H*{6 znFTs;fsNO6&2_PDT_qE|S zw4OE}d$ORICwXZgZrUY+1SOKkfB6+X%yV_XuB-JLc^ym*O0sZp&>n7RB^mI=5db*2|VB1LC+@+8>`p}2-bE&PZ zT^)5ibbr#X{y(I>XIN8R*EJeI@s?^q5s;z?NE1bh0-*>9NJolvQ9xQi>4Yu{C`uKO z7DDetDWQg9qeCbm61vh`1OlORR=l70`QCGWo$LDiDap>t%HDI$Ip&ySfVT?k9bVLE zJJh5DBO9oSWy-=-{$1Gk?~q=~PxHDt9ir~JK2|{ao9ExZjTOYYfM$*0i^`L+4vmds zFJfq79VmO+TPt?O^nXCwzv%Qe&ZD=~Jo z?uFehx07L3?EC6=^BV8JST6j8b&s*er0;v%fcj*TnO}S^toFeGUGRDKbP6I3A__f3 z@hWd21rtZ6(LZon&*Xc$YpXGG^y&79mRNGHoaOywoR_C1D7DW1i;#~$(iO&1qW{B*+q%@$J(xW)NiIf)1>kgSPK+{LM;sp48F~8 zresOVY;Y9_IhEw*+Q*zoCogCf$v2a<`{Jww9nmKgD;~`FTRa@VTMG%wW>!5cJFmsb z)A|nc-DJ0J$EjvyfVMhNHz74H`13IQVq4k^bkv~!2e5)ZAY z`J7h`+cwCYe5R*@Cv~V6bk1#s`pmBNh(8-SUs0W%I~WFcJYr!>xc#W9hO8l8PS!K0 z<6+}lf|fffbv%+X3kV(euD>gPIzJ{p^@>7Vy?nu)1i|>GBXNz9-B%ftJPUnsogI4( zaS?$?**EiCr#$&z!qp#e?mm6q=Uk)y$v(!G%eN?(#O-zqV+xu@u|ngUzlDcH*NxwU zpw_-^P?78OE$m!7wD;nxT-Z@c?#1H?9o6Eod5_ciOch`|!v1c@)xwC}p*RKFE~}=A z#<|N1ZH?X^df{L)p7$)bFDuMa$lBbu@?CF7GWVKCs%>um=-n$p;|lX8ii*eQe~2|< z8}BS!#IN1>5MeADyJh#?y{&aJPE#O2QsTVgz~2Q0;5e>5-8GcGV16?XWw?6Sv`Lsu zl28{uc%SR~{xGe~{g>Lg70Cgx=S;uq+Nd%FWeU~Q*c6$)M*)Q@xG|NT%YWrogDsx$ z`DsMDd#B(=uWU#EoocR{5>%5JkhAC}y}ckjGvQB^{H^#5ZnxPsw>9uapi|>3Q~Z~W zjIFQePD^n)=h%0BoI<0*n!IzI63<_z-&$&~4Zoc+ovKmLo<$>&8u+Nm1LLsI%-#V^34Fc zarBW2b8+|fihA2Ea`k9x!)T+hhnYqG-K?$iWUj;mvL0lC;lx%5*BTg*iTm~I$tJ#N zVcL9hFUhjld5bt{jR@LvybBIgn8uUA-`+rYLDSwo|MZHZqHc@;Hf8B0)rdZ)lcvzu zO#ZYkpZ51r$Hod{RXaAOpLTiuN*wCOHr8{_ZYCxBo)6K~)VPInGs-y8T~Z*?j>dl% z-@>)~p?uK6n9END8YJg`a_wu%GF{7h+zB~rJs65n9F{muLbw)O z_9B>?JsNh06>+}#r7NA`=vh_b=6ST))sV(e$}0b&W^Ac(LLx_PEDi$E<^f?#Q94%% zv$i&FlA%W6u#Q`1MxPwMBHegZ`ff~vPr@`6$)d^d^vq=&0d{(Otxvp48Y9~#9Xr3y z#3|YpYT>>`=bx-#mL6Smi0H2E*d21?so9Ki zaNyDId{2q_ckL2lxJRa%dIpe|W*a&eTSvl}n)u8LEh*#o@4 zeYV{!jfbk_%YFx#_TqYv3Hlz_$0e0@%AS%FmvCR0Vl^Xu-;O zWZ!whWMtn@eOylH_3FdEil?`J`rAI(m5p!O6)=DlMzi}oYo&|me@jS;Wc&U+vn zYJG+$O}Y<1*HYQB|()UPbk zNK|xUwFzO@T8>BHc>l?+($9cEB&UB|JIz|q-L8O@6_c5;vPxpFcouoLA>9ECtQ3ylHK#eoCG-TnewkAyC*M0S z>y5*Cv-NyrI7!g{G}454-VeCZh@#3z&tU))Pa|#o%7Wb!%5? z_WQ|a&EfsuuezS6?LAb>Ub9&mj$&WS;JWNhU7c^pPSrF_X zNSoky7!i!@5{1)ZNN=o&IHUXPL7t(V_1`@^9|Rw2ed=7jWgcd+)G&Ei?q{@CE32!G zkTY%C?QtUr#sNY-g^a(HjPqUqQXO>QCIR9~!5yuqxJ3k(V=N;Ydi{x*XXtquP&(Mo zb*I<|{QP8N&pH0g2U_C-=C$kzKxW{SrFI}~phBQ^UZxsNIM7kbOL2{+U6sHVD9P9K zd>LL*z#6v}a>35OG1<>=EihtU+5(U&hxf5l`r=vljj-mj;8@)Dx>a4j=_W#NSgkNn zXg+HvJbb$-74>7kyzxjgF3pP3?WvfMvfgVG<{Nhgs}gXrew*dp4GSafhwe$cWt%Ao zjDyv;uj?Jmy-fa@)fL8-o0la4Mifs#zTh2l{GpFvJ{rD^HCf$YLYxKMQ~yTnG8RJ>i2|(mju(s-tQDv78>one@;OT?v3D2hsLy zMPyM%*o4l7|tE!XlY!2k|6P*ST2voTWylgKVY_=;a-EWt#E+r4^@J^3nS22F> zF9Jj4_Xw`o=fTcr{J2lQS##hWJ`~uK_iktGh2m+KmP-y(h4x`n|Jl$fwSQ5QMFJ1AZRa+$(W#G^&gkH8|UCQ@nA1;%h3A zN=iHgC!e)cbe7nF|E}Y>Ny;MF3OX4X`(XYEl}Y9B8J?_pp;!KW>#5`BI@P%l1U?;o`y(n?cV!s~Um3JF&sdG!+zJ{#gOoOPe=pM>*=;PP zY#!zUJ3EY+_}t83G{|f;;wJp}Ij#iH@f2H`0Xt%Ko{6)cE1mYw6HKysUNz=RCEZua z#A&?MG~4TG8R5czJe{SGID-p)U%a?SZwZ%TY9p_db~@&w4JDo52J*O)nzT(`dr8^~ z5+LJZG4BiEF5+HQbgnA6QCiWmN%gcC=GaS175R5{p(EZx20Vcub^?^vPfBt$zuA`0 z@LI0et~s9pxXayoiMT&Z;`hD~2BR*0DM6zL@~cG;^t1H1n$=1s>&anBL?i9ZUtDw$ zC-WCtMfMxcsMqUL-bU_-#WCspp{3KSpzGoBjOkMk1rOT2^FkRu~ zd!rXY(5R_LnY5zwwWE^gQD_tc~A ziDyrDd@YBJU`IduEodzZwR;y7&V6L~;-HTU<~@r2^*^_pbd~aH#@;$8p%`Q|!yhE^ zBVpdPS)rv0K-|yXjK+yP;g15Ud?NNd+F?%!MJm3i>IZoI>cTcbQ??P?Vz%?_Ik~HZ zN!ataTJ3RJ(#l%%eoD1A#&?_hpLsH$v7Vb2vDN)YA*wx;hF?8Z;d|1L)-tQ5?H%@B ziObv*Hp=altcOUO5%S!t;`bwVS_RDdn2L+?>oj#clX1-0t))o?xt)u2?$wXp<7Qoy zIr3r-7>J$eY&Abzu}z3=6^;v99vodUzF4N}Hm_wd$rAoXXyb7CB(kUNl=@Atu;8hK zn)MuAB>9DR>1p}qp}EC+%XZp@7@SabKg>K;=T^PEl5XA6AqIGVZ@ zeEIXTWbiQ?Uyg*w&BpUg9tya)a30I+KY!}MevW%XDfc_%C0tP#Z%C|>aY(ZC9X9i* zQhsxa%eTe0x^CH73?SZDzz(-G;8-okv_dR|D8q zz1A<(36_r6erDB9G)XOLy6BX=_WKjM^hf9S{l!@XJIdiT%I5hbg*Qe2-fDn~)T8r}xm{r5D zA<-K3y*(s>1fJ&opxY`|w_qvZc1N56n4;le=R-8U6J##Qtd!blG2rCMlS;BT zfFx1Wg5Oznme$8u1}`r!1EA|k_=62Tz~}+ce@=3K=k8o}j_2^c=P9$`L`WO4fX?q;z*NnO4VebZvvZ- zl=CJrx)1y@mSaS>>>9c+nv>fnO~k}o8~vGf5gCIW;0&Fwn!Zva+b`sMVsh%WN<&+< z+K&yvUnLU`1!e*trL=?uL@5wM$={lKV6Mct+`_Q{fW+AERdmH^t#f9d9*6p6+_ZV= zcM|I@2oMZiX1hGYVVt_sHYCMkif`*Hin&(Z{=((!YI8*4V`2zB@0|9M?3YsfhjGaZ z6(#T(wg=9gd}rQ`*9O3NrWmqTl39Y>Jwx?SH9aPIH1KFb(6hI>%n6U-O2QE~26%-g zrdkMOL7o4h*kPeUd#)f`{PuU*4dSUF`)5>da(IuwG|X34U1yl)_SOVORAA^w z;lV;i>04=MHec1Z$UiKe|PY0@6hH2mh`ii@9Z~-YbB}AR=At)hyd&n z5q)s$(a{8vPw!kwjRJ9ZKRUiNtl6yq;wkWEr^VdVM6|8DSxH{hRDkN)zU38M=GF6X z)^=xW3=nF)?IIXrn-EE^-BzF6Q3k+_?Kf`zC+@B(UWdK0OEx|~sR4p32Qb9jl1xiI z@SAtM_HK@B6JE#11r><^oaDsNSp{0*35?p3H|*2H4B@=K=N)o04`fdwWz~mHO(Zr* zcctg)s_Wx!Y;t7?eouY^$6#E3z4_l@QJHvB^cm~tF&R!)*i7EU1pJ8Le?OCMNxShE zZtACvv+XQYx?}3_^wO1EUf*JrK-t^)p`E$$LbXvvN&c9Q7Z(hV(uJdQ{f9JEIpN3%!3)l_GsTV(g>{gMHwE+U~~t8oRM7 zO}l=N6I-#q=N8*|Am0WoxqPX3l3cxF=nW%vomN#`lr%1^KO=zkS^m2xONZXEAG)H& zCeR5K)E^7YLmijf-r%^w)^%S});37|i|MvO10WK==@47CPKLeyR|uzGFnCe8H`u(t z$>HXdNCOt|yJJ5da7-K(aC)r_2hUKhh1@UV3d$U^&U0tB&Z>Fewwhzj* zpan?Fm8icfUCo2-DRk_4mf8l0^UonSJ=p-x$@wqN2@P-FczBw&SoLM~ty3BEB|Afa z;i>rR>c3cQTOrrFe-WE|)X?1Vb?{C~qbZi*KltgbmyRb5(tKVIF&sH|E6txAq+c1J zaKZnFzpEI3@H}@++kL{8z`6nBWe?UW}k`2Nupx!o}S(@>ga!M&E9Xri28aNApDdBW-&YZ`f5D-&oBB- zQQCQDm0_^5yVd|*z9uPY1omg=@F>#6N3Y&xr&jg^b!eo?csU}hzSLD(R8;hdx~?vY z8@mYrhzB}4(Yr@mWAqAbX1~Ck^W^U_*izjxq7727fFUmgwsV^Ki#h3Bo%juD5ndky zy#|TEy?-UKKAoJEqtV zV_EUy4)M8+(3T|XD|>BxhdCy4;AvRs1^14r%Bd~i)Du`cQrjVzdZq~0R5;;;b`LiD z@@i{;o@fPn)V2%QEb%Ab{^IegS%18Dm$bPsKhRf@ z`A`8BsWtuLh&vf#D8-1J^ zY&K_kRh6NX+l0$YV@f4qmLR`4;> z4izj5rz-Ku#GI5Oszlc3>pS#nI#@UqlI7g!*xa1`^$ATxx~tH%G>i(9Oqh4)#1` zZ5$Pe4N>>IGTk!y+!mj&aHVXONMDn)z(0P|-JmfucT&@C@nxE->z<$T8Mk#gzu6u0 z8HrRG8px}D$kcNvVOGIU7XcUa`by$A@~GyPjD<= zY}c=ACMQzSCeHMR+ds$8v*tmFZn+I~dHE}~X!Oip8*@v)zITJl{brNca1c%t;%DM8yIPjn|ATTxWCCWM_zvJ6v`6h+BR#vy`GZ;bfZE_lUp6C-+l{p@$2n|9pOV#<$?rK+O12ZZf$#crdBSlV=%qUMX(f= zYF=ymxso@5PXVR4k?g?Jm*ggRE~O&)t3NgL47m4K4UCQXm+>fRCBB|Z95)|_oR*4< z!>^Aq$U`UKV_Q4RWaMN-kQk&sOVW83AJ*21-~YJ?5477OJ=xtl&tcaf+HV((v-NoU za7JzOM|;n0a>Akggot~IaScwW?rHk~4Me3=Rwvro<6=6@#H}|V?);6LO7(Lrq927f zleCgX@Z17h)6Ie8;^J07OS77?5(77Xp}eP>6Av0WUtcFrx103AMe6ANp15)Hft>2;@m@<=UZk#_u z37}LC`j(a%j$PXRPjQGxlA2nwFR%!+!ys4QH;T=t4&_Py{Y159fx{+}{~(cBabs_0 z*GJ{cXt#52a=_sz8qoThk_1bnqu#NNyzOrIQZO?w#w}cvF0Pj(SDfOH>Kw>Jb@ucg z2XXb^z5#*2o)-6q8zsZEXhJIOHsbyCp|jy9iB;o`pODr^9~`enC2QE|XR&A?W)cb` zxS|VQK(vd?%cWAR*)JEDmVP)m2w;g8alRdmSaQ`!Y=Fhz5inzY_g|6(r@Q>&Xv+U5uTa$OSX*;D%s|OfQFn##^ z``#uM#DQ`33Yl=1etERjIkAztBcVfl`Bi}FHnuTxDwCtpe1YMtk;~==AIc#?#Ju7W zoue)@=s+QFF&;S(i-nC#V*$f~P0Hn?V}mHLhPV-Gi=0dja@#vxBo|H=1ffol;o*;A zi95HSk`N)+22IMJ-~QA6{M~_VcDv1_%>9W9+zSQ2prA(~y0Sw%Dibo(kMEY{v1@=I zIn!i9q%vckUpkg`$%2<ji6X{6VBsEqmi2*YkponZnH5S&yxnSpM8Tb>@HNopHnKkaO>7uXUO2pX0)Gc$t?gGm>(Hd;1#r!1?>j$n)HfnaN|s{e(H7gLwjZeM$7 zV9v`xUevGiP23H_e^)hfOP3dqT?r7OM_GH`$c5Ipuc{j_OojPAnkm4 z-R#m*9oi_g;oE<+S7}b(dQe-#@2v7|b%`}gP=x^XyN8wuAy(x4e|Kz|Tt2CQne`Ui z<297k)!=jw$$c+FNXi!YvwILPKlg|l9;sMh43YU#k$h2;3e!hz`8IeulnXHOn&4Yb zfL(Jd#VqwRjx)?}I)#eh_=@&35GAzhLqO*)txRAVg0POF_}%*S8>r>Qz50x``^|3B z4|V%!pxmTONPjF5pB&G>_%tTjDnTbg`2)#3vZ@;gl4TWy_U$&fJ`j zYI35(*bCO8WNHPs)}|0o*O1Gv-IT4r)?_iOQ^u~06%gGL$U0CEJlx^|CL#ehs;sUW zYY)N}tErIl1O9bPWf*SB*Gn~!`rE~mS4~w+n)v_;pe07h&EY~?zM196VyYJs|e z!JMAKB*~GWGYb?>t!PR`~K$s&Is(Ey+RkW_cp+C&~V(gurTbH=Y9u&9ALBrXxgO49(5AwWP^YJ|+ zcKi0=N<)*qt?kDbhicOL3%f=8%f_2H6HZcFDAQV#fb#v!ASNIs62r#0wiuM-C-;XM zFnTDo9!k1e6X< zj!Er}ZSRpzy$s^#B;DpWcr0$_=vdV8XK1OGu+5eFgGMZ5?HwqU)<8!z{v>9S8v2U% z2XGsCO05BOnKj!(5J#WzQez-n6rtpb`=!SpquC`N080-swp&kLoxacSp{TofP9-~-&F4tke>QGle}Q2uLLFNP}hg8j@G0t zVC-KydK+CeA`c;B?So*1st((;OHIJs#gJb+^%^DpZAIA@a$ROV3}7#%B)$Iau~+8q zEUaTenkNwV@>#tboNIA6cRmqeO{tng%4aj-v&!PLZ%^eu<~FD1adedRq@4J$EItEo z#9Fd_wqwf!ntOzl*Y;)FA8)o+;geRNe|~5xG;^JTa=|S~I}0xce&@Am$5>x|Ub#&= z7}yli-~P+xg~RCG!Xr>Db~LGz>= z^*vV}-O48HM=ns2LCQClS>VXfLolu;;-9xdt3lY zBpv+aA9~)9>4e08+`d`^6f_6lgwh6A3E;}JVJ(i&)nqYPt{kA(!<(ZPv$nzW)F-P= zKc;}o1lj~o-}3db8})}bE&3JAXCflt^3)LQWq87g*uq+cD{Q!$BI%=cWtX!dwn$U+ z65}#)9M9&)h8D6w*g;XPB*KZ~IbklOIom3{TC3mP`s5+N}8WDb{-%0(t*RLE==H5}=e? z3LgZiJZJ6*M{>ogR6V3-BsdUrGAuDhqppKok2Ezk`4ki;PsBNhiGBWW?zKq9jlkCG zaADO2R#5ByL^`|abmv)uGY9jg{O71Dhm>yT!HqaN*6u{-c@fX;6OenPP}7L=PLWd~ z4sZo`qPAiM09>wxD)Ky*xr*h|l|Ng23dIO zL$_@KiPzVv?wK-ZI4WJo9AI$Am>e{ z1qJUxy}iwS;7v{;x_#Erm^hy8acllj{p60qlD-+|8&mi@`)+G5|Hw7!*=IIn?3%+q z3z=Q_$U$RPY@XFc0P1ulGqc#cpFuO|)s3yqi$4-o|3PlPQn z(YZ>EcQ^BHx*0o++QUC<-R?@oX~()EUZGhD##tRA68p~EIqg8t4JFrL?LE$yrS&bc z?-Ww`n@7yUwLqQ9CnRPlJaaIZ+p{56znm=Z!vtj?I zSsHm;J5%2BFM*XKy+@*ADaF zQ@&p^?`>@48fe|aGSq^gjcUkIVO;8oN%WehC1_z?n^<0*Bz&Olo?{-Kve18|*VoIy z%2g;#$4ncpa)QD9{183!jUWG9zklL740|&M$%Pec{AZ~SMUV@l-KNmokip-E_A_O< zAu@0MCkOUF9(}$G&OZjQs8s#61N7KrNUsWDw-!C+@;wi<+k>2r;6tWtM(m#bAqZNy z1Ttbxo5p{QJZ{=*j&ZGljq(HC#Td~=t9Z~Nkh_$`${vG@Q*G*~jp77$2{ZRLw57&p z`1+OZM%a&H^rn>b#Ek#k3TP6g#@hU1ToROiJU>B{sGVo}Ou&02p9=B)R&U~XA3?i5 z)`~$Dp(jTF*J_fFw&1ITYJMcXAKJZiYaCcj?cOTGBzfaSge3Aukc6bjqe3=NL+;)h zjH^mbv}(w?|6V+pan&`Rzi_^gIX^@1=XLBwLGkCt+}%<7c}XCq+_{ItaeUdbGJHtB zT39fqn9)=pDo4fv;}p&gj87eGq0Y$F$bZdQ@fk2_0*VZ=ArezrI2K8N9hxhgs-wN5 zpxV~B%Ps3ILWSW1>D%GN);@kyu`ePzd;T*M+{&jd#7exAJ6#|?SuBX788sh@V1`@y_Au%!@0QM2YmVwKwJTmt)vMB*6YEHJd?+CzZEBwpE7rx8Dv%}b zkZ9jxIjVxHm=2YER`;j4VZS%t)UV67Y4mB?rhG7c7mGVhWK^<5cc!)V>l(gYaX+(} zpd0>z-qkjcH@e*EY?m%wItPQq>j!R`Yx{631j&JnG?sKQl+|F|jB|>mL^&ffiQ02q zJJ=sNUWn~S#W<^902`u_Cfr=XW+^WZ_EMD{Rrzh&d-OVbSwOje=i!UDbLHTqg-C>( zB(}z8T)5oXsm}B`475zNgwp(8x}zME=52^<)2TW6 zEqgvvzH#yHIb9yp%0Hgw;Gq&~y3r#)US*TWPw2wmV!%>y3+T)^hGt>o}~-?nv4!2*3k{mliD>sz`2Z)RL((o109bLuJENA zb-MA-P6R;N(n4vnw?wHbFhAx7H3t(klGS?l!NbG9b2C_AI-w{qAoU&K{+7;;qxNR2 z`m%jk9a@XPJyKkjazv`)17BHNGi?J|X>2spmoQ<)MpA&gEEc=jo%e5cx#YJv;8d%s z`dYm4;M73FoRK-9BF?`_G|?_i+n$G#74t|@KBT{wM#^=}L43YSG!>0)K^SM@wtQ9z zcACmKpK7OETEnVCIq9F-p{z3B6xFbs4@SENH?{htDe3Uw=FLW#I`r3?K=Z73BF*>Q zosOL~%>^VMJ)sWG8*P@rNc~Ni*#Vy6New%&qH#~n!UG3AbAl8>FkC7fwYu(j^E(b@ ztx##%<22BcMQcIW=TdZD*v*?C0WO@~eg!GHaIX3)m-+1;@h5%cYYp3$;LWC9ntZ4Y z`H_0R_rPRh1X#j?8!ZZ)i>D=!1*^Yb`*2~oKx>5m)3cZSvOa_XJ^yEfyR9E@JvCd& zVEOYB@eHJ@lJgnfKYjtv&YTY}lRUiJ7W^Y#+#>_qi{_i@B$VHS;ZiFbP0EEKplPRg zYA~9bI#jU|I*xt~WEF*Krj5=^KK?oEgi(|u1<*4aqO6;B9{7|d=+7o=7>|pavu8HG zmCZHjDez4Ht7fAQKs-+MQO?1ZH2VQRFvKuk^|^ra?6;*iEDJ8*zkfF{HWSI|6ia+p zKZ_pXP$nPf33Z}V-kDUX{>9`i{9!9L69V3!?O&t&Kdp$T;Fr8Oge`8GlxI|8M})A4 zA2;`>FCHQFLHrN5jPPKU2$YoY!+eBwC%#toCYWu1a`ixXB!<&le(k}4fjD(5kf1UF z(?VxLv$Jm7rLwHm6vqqdFlXikrz|Ts8iQ20R$+^>W9zE;r#WIpN$w*dVPTk{ne)c$ zny%FvX)chvu|m#BGq&^u7QJpsq5VDAo!q$R?2ewnNYtzi=yKbukT!IF>$`U42qkbY4Dg$4!6G;@UUvyl}VO72v*W zGHpAO$s-S%J?B6apm726icU^WF5B@nRrO`tK_#3lSV7uzZ)iW!-`7seCJ`TK>`A*6 z`BVjBKBQ__1RcW7sZlIVRgdV(neCV@3zWhms_3knF8dsMi3^K;SoIn2F4W4rZ>m=B zlGPpDUUs993gc(s`Nb=17q6R;%?B%`uC;A)k?D6m{?gGKbW|qqP_r^!CvI)u;kAvS zzt~qpr1va$oMZnex<9&@Khx(~{(3paqr8+>)!f`?*h$fC;v#esmGGOCnl5E(t7&t1 z7e%G~qH|-D?pp90+6|Z8i}Np2Dw^+T(m+C7BavKFx5~I`L0Ik^?SBYbXBDtfsq@%n z0QZQVJQv}ee#_y{B|)ub!|hxyCr!+4mp+_9eTj-UC5T|p^;CFNjl@EL>0vh!-ytq5$5V(`?tfxx zk9x?+lq!VIcXCy}U?JMa?IPw1&canN{vVi<973{YVm$@AK$T+N#Z*j`D@kKT_Yd!u6~|UqzhX zi11~xxGwbJ>a(uh>9j!xK3TeJa%qb~0v3AQdND;5gnzkMA8moxAuYo#e2SGDd^{`4f#-_YP`+hp%d3t~VZycP zYG3Vb1nb5IpIH6dPn%YZelHbY?%5(NoJX@=?jq-Jj+CR783%{$Xrz5>uxwY0e=coJ zTQGN3J!K*c0=V29oDsgZz#m|sIXDH&S3AS6*dFq=kjhmV3@ba>Tf4!)fSTeBl z8#%|b`;JT6)H^@WQtH{!SW=b9b;k<6&HE#%fl;KsNzFaR%TDtW*kx^$EanJX^^?=? zjOM_h8vJsXMuD?r^>{-?1{?C%5>@IgNjNxz(Yoc4DvL4z8Zsy)VKB zdtQ_m&}VqaWwX0pueH2BXzM=Y$4xgkt4W0iNA-9ru_cU&!F8nZyWFMs+zM6~Ma^sh zCrr6dm-F9YJ;I5GjZO38Klr#RnkXQr|4~au%s&^`R|hhCHYi+z^C{0Ffb=`jzskhx zSL8bWDgBi9IWdpxhI!?Yu6RYngXYk$TKP^7i;Ii3(1@e`S+98sSYD>S|FJfZLq_Lz2GxYIG_ zpU4RGG7hBGz(dGse|vQ<3lYLqPXkT%@PF0;lJk-HpLDlTi9L5c@~n?+*y&)wgJB2jQOlex)!vtAP*% zn)80!r3Ibvn9E|VR=l>(b1^WDFsR^uSMh|dhF4}~?&F;k9i5$M53_uwEvWzYN9VIp z)xsrLU&$YS;^wXjnXz2wqKwZgL<@Zauw)nodwD5yZ_E5zg-Tf}8Z;OlUf`2$*rJL| za(I3(Y!$E`oo{KLR+p--uCCbFBo3$z=@1~_OVF!xyxq>OM{3e=n5{|)3wI?!#xoH__=Num z6N2*ccrb_xzE1;)GgYD64~nQFf&B5$_#H=7M!jY6c(%a6W>bFt4~tV){mTzg4>9GG zZ-8t7OoFOV^VQ)l5h$bl8LYBy{(Q5230xJW3z`R8|E}NySV5x1vKkFDXwaki)`dZj z$mk4aj6c?ebbw!0M4dv-%*~}PC%XJq)Eoc%`Z1>(&`Sb_xP;hdT29c^R@vCx?Ae+! zI>idlsK-4|c;7c)`4kfSs8#`x-5!CREJI>ETqm2x2j9ssKAxGK)d0q0qEzr37$y=N zyZl8Mj#=_rwjFMdYryPb`Vll%Jxz6b;lFk~!}T4Z8qcuhHishV$HP*gM# zwCE^Z8_{(u8=I^Sx*?%v_75)0$|(QL!O~0PCK#X*xa>_S=P`Yjv=qROIN(2mR0``R ziXL+2lBK0T;fMD*Ado*lfT^6__qP|ej+xF6DH}ZJww1kISip>^yFh*J zLKgm63)Cb&CFDN`R5CWwL=omKUO}a5`UoTkjR3UJ&;TJ{27F%1EkL6Ye?FyXevZ5B zOZMmzU`dHmVK2kJI)W!u98}KW593M3-*Mdu(P!L%JTjFmIjEo&>Jy1;LNv%hzVQxY z9vmZ!l;nJ#6P=68$Wp*PijhJ?g9p_!*JWH78Wh0*Rp*b*25p*^E)?7BidT>-D{;#R zP@{a{C>%7Ajs?mHn-C;#9r;F0{!8;&X`c3$mX=s`a001|fCB1L)x0+9C7f_K z9G%k^V}o1;Q24SMidSsGs|@cWemWRwPeJxpKqRG5KNVRRxg56c!dM9O_0S4Z$oAlJ zTL}_Dxwe1x-_Woi1g?7B3dFt9U_whUaN=`09fBTM765Rrg|3zQkAEXbB+x2|%x05g zSN!vm_6mp{zwn@j?t2M`2PXXGTJnql%;$>(800j<;KC1`ot$$2yg+}{{1%L z<|j>1K9o?QZVW#2X6oq~qa=hq^(=Ut1i=H_f-qt8wlbclNa2Q*KYj@ZoJuVSG{b() z7l*L&^Q&8|J|7}Yf)gsA#bmsVv6?q*rMxuu-Pj$j6nKb{zWx;u-VlbEveHrsl&Pud z+y%u05`1GfU<+>|N0K4$AI{ZNB65f=fTlk+uJ9;Vk_zGUzPc!3OjDy`1mNKUnOqRu zTL!kPffwY8VSvPbL~%_BnN+_4enq_O>R;Vp%JLy=QpennVqKXMf~tv}Me(QLrS6eL>pW zDE`WTS;KVx?e&H9&RADCVAw{s_lznc@?T?>9WTg%Up#eputEB)J2GcsYnBqQ#~+@g z=;bJiZ)m=F6Ppu)(7Eje-&(ROAn2B?s+fEci^?)gWufd-41CA!A)cCU8435uDg^Y z5e(tEqB&N9q0BAiS|EPgfeP9Zt~`8R-{h)R!^({5ZEPPNT(&|5Q_6kH($>!c52 zfrjV{Bj=D2{ph;JrJ-yfh3W3@zQTf)lz6^9=FvA&l<(NGnxHEmS5jX8fa156=0UbT z-b>CHg(vW9iyJ-q8s^gAy&6v&N+J`l9ZRhg-Q>0sP1WMxta6iqF=? z>WJq|*XLxt&n@^#OiigKApqUAe8zsz@slg4yU-F+P$ItEQh0x@0R>!W`#v~6cLfzd zS!cG#?R5!d6u4#i3k#^^@L*7hU+waXS8P6AHX8jyLfBNdrLx-n46$e0g~dRq|WB!L$?tyQr?3kHR4xf8R?2SWxYQtrr5VQZfDxh zFK%Vc76%_6%}H8;2MBTEH)&7cFKe7^nZ)H^iul z54@8FCfmTDEODRMJZbw&5;$gB$E3iLlaoy*%Mtc-j@t;@eaZy&oO2Q)$ypJVr`0k% z&?`==k4m;WhI3>4{t94sd{LO)AU;r9gPU&coAVv1@$%F;AX`gx#3gv9aA+XqVw~eu z()Gi={Yu0~2FywNiz>D?b*-{x@Dk8tQBq)>T9m?b*E9b2zDk_o|j1!J}da`Z*6okq#k{12_I1h%A2v*qiVqnO>no1qt z09*ZxXUUC4nh{D8awk@ubQzabUfwI$;ylFzdg8q>h1D_-@Z|g=x3NwRqrbq(YpbRx z1b}6yMgiGLq>Vc`pr%6vCxmJ6Ax1ZpHGy~nG@cw-VqK!Xe7X1k)b-VIQ8v-PDo98v zsDu&65JG1kU6W-hPzU2L+LRO{)8dH_+OvA{i6kBM2F) zm%5_kg9bKL)h2TlL=K^|E#-fg4D=id%^=x=s@ACiqeQhqs%en9M0=@R=r>XkF2=EP z{{EjM0bqnkl*Tg=q9>-~Aa@h1ziQIF0E&H3T0;0Yk3anSGw@lEc%Otl&mEIuWqNgm z3U-d+?&+nAodL@iwoyVEEYC~se(bFI3iXCwN7arJV+K3o{GH6y zRw$aZf202gP5A3V`$X*M`|OSR-u8-%q&nkrYWE%$o-pGV)Q^w78ZoYL8~TwnO##Q-jrUHEC-Wym>hl^729Qc{De8Ht_n0q;7t?_p)2`*cv7!Tz*pGEQ zkRW6dMM+eRCRnRv;#?rjyOTXd-T4Pn_e=1T`(63MeIn2|W$3Q_n-;0w&trxp*BL5z z_?&C^R-IO)?&xxCUM3m!Q{t_qw(Wk8OH{ARsD_e!3*R8Dns-1i55D=TvU$dMn`~e} za2cBkyqk$Mi_56^6bpY7rbfQe5KMTr#v#u97GZm#m{%Xh8u_-W58n z?R{UGKn0OskIIrE`y;}2uYM$T^b-X8L(5)44x9xzCK7j;6CV_x>fvvCXjSy{IE&gy zkGC(MIfRL-dQ?gCC)dE)f|NCw!H!;kzP!t<0>b_OVKmKj9Z)H<%p&?8s~;p|VS@Q^ z!M8UQ0@Bny%Em8}a1OW)`-)E4z{?+SPj~p%+#M_8Z68-UJFR_kaUT7}CONt#+C4q- zmgLUNy3FzbvOkdbvc(*o*3Dq?&?TuKG|xn2PBeh~;hE?DO zU~mE|lQ0{YA@Fl1dGY=J2-iD^Gy_kWR*D^?Al@;z)WK)^&3yzW!daB=LeIGi$@93S ziY>yhE6_rvGA3DU<@9XZ8DDJr1?H3YX~RSZ6Ia`r|2!kw{5~z*_^rp4N|K-sp z>q)GCCi%W5HflDzyYBhZR-xsOkj=Bq3u9!hX^s1z1|M_$Vatd&YN;}M8Cr^RcV%oR z7E+CbG&x5*d6!R;=<0X8oi9GM7hVtZxKI@u{?^;@xc0{7@x$yx++RK^G)>3FjDZ{$ z;V)t&J}@EB{;jAX^1NHg#@$1fc8YX5cU+d`Nm(zXO>4nc#ThR$PCp<+SI!`1Oa-I< z!1{)WdCuyfu39m^-9w?ZP0@O4NC`=PlbQH$qV31gk0uluXs>+$oWAl`utKjd z;9X;=sD2}!vu56%%h)MKmdz9-*x)$1xccd0S&3XojLh zFD}sT%wg$v6!yKNFITuO8NAP%K;9c@y!`0`dDa*GxDoN}VYXxfnM`iB;ND3hUH!Af z+k{B(Vrnzr2_c&yK_H4#KkH;vChbukc)V>(OMe%*=slw7SRhU(E{+kQ;-QzqoV=0W z7uI2+WRcExX>+A7cTn3P0H?zW)4T6Ns=TpV>wSYKm&^7R#XS3f<|5 zb}u~|*jrZx=j7|(9hUSlg$Bmz3lkaHjI$quYMDRX|L$)US<`*Sn?3%d@e4;ngj^pB zTNZ6mf}MU2_TWii`jWsxxyG32U<=$S-et8{N{|c8YSK|2ZB;GPR4x1UdS+ph`T&*cd!-{I9e7Zq zwk+HG1G!c_^a|9>Z%E~+$EOROD?KSlhZ|gdtY0l-?!99s-nT(n-WN7+kXy&Fakgs0l~W;SyFo-cxo2i7_(h%e03|T4=p+iynITQb7-?Tw z&orRNIhL$34;qepeai7xC~NUrZz1H_Cz$1pB(?0Gu)v3nmw9$_y3Ba>OU%pV3(Y^s zc0!8imc@wR*0xX7e6u})WZ-8E%5NP%wcSApadA)I@yLECS#QJm_KD}zF(FSXYu!M_ zqoZR=(j(Jw?23!ASjq*DLz7QAtIiMp0tOgWLwmy#AKdC&_ix-z86CWRY^ZSU>A6>S zZf=Q+%sZ(&kBXfS)d!uEI@&E-kW?madK^EhmBZp54nmV!!xi{t^__m*Uir!#KHlc` z`#lt9>ND;C89uX9?HIjFQvaO6LSJa;V_pZTu6rW3i!kGlH7ha?Pm?e2f{Il1BroN> z)Zgv@{d6bPn^k)+iWewlx1H^z@NkKnboiD`GY|BmED4R~t3LF!=Mv!j+S-#%+!JYy zLoXEs8=vD*&E`yHpa1l`Air(J(+3m{%P=~fAKiCHc7t)=1+q_kTR)jHb*JD*qBD1N zesmpNW?URppjtg6D{T(_?Xn_cS{=CJlr>^l`;Sn~!Cd0j!#88u3a{Pm>{l7}!8(n< zmcsZz1#U8`TdyZNQC0r{tB=<#gKcaKs=qa2 zVX6(6c7GPIC)j|MvfB;mi!ocqUr`lswvVzrn++}Qy?s>#Q&~xEA!gMa_!IheP?3Wf znIjSFvN@}OYbnk_K`8SwWw>^&G^$M-TA|XH%oP58W0B~#EbYSqi*!g3Sx3T?@Umo? z-V4(1B+|8@?<1OF>sSgxuz=P~HRE&8hag>D<5utL$$=*7WYFR_&}p@m9GIgcF5TYI z5w^Q)k4d45JpeVWgsTmfN!@Op3Roh38b18_yV7)Yc4j|&-%XnRNu+-65&mZ+C+?Yv zSEAMneJ8;oCKAEDX22xa$G zz|^z8GyecETyILka?>wf{B=#;_^@^VO3RCcvzT$ z{J06YKq)*aiQUc3tsFaGaVeO#Ge=K{f&S4;`@xb74Y?fC<-6k+`Np%6>RVh_h4Mpk z_nZV=>YZa!_nfNiz$G=^2B#DG&X7AS&vW?31BQPych=uub}i+jTS@)?{h@>Y$_PA} z%`6#I+z#@#fV2=x*$Gg_8^_DO@0BvNTW7v}37iqYj$@7Ivx&ku3~&f3=)Cw+aQC^* z%*>Kh2#4b zQ>#)%sLLAZmF;O;{Rr%oPfv%1mL#b6B@jO~soB#W{+g;m z9+^STqxV|lkN=B)z(t-9IpFx41oYa$m;*vcUz4JwNGw z71X_ezYbr3HMrq|RsG`PQc`bAIs9bY%~{?#v^bAx=?(Y$L0NG1yiC+$OD>3+ayROq zP0S993E`RtB$|JElh__&@;qmF+_xZYB$3>QzZjDU2>hRbAHRVwRTzH_wKf!}P={Ss z^Fy~>W7E2rs4fae&qmrP`Q*0FuY7>-OEEO;#b2A`j@w9 z_7Ta%)UIv2^I$1lcdYOxwYK)2iNhnNjm4&S zQ&m=f%J$SM2B)sGjL6##Ej`pvqISUZvMa|PK2Mg>3(!lmSVjG~Bn}iKK32gTVR7XL zz<7KKaR4pc2WPKstUXE^7Z=aDRz?)-Vr&{yK!CH@T_i1^zeiYm`TJb3{Zk4zK=g%FRHAVdf-ja&`3)7H`~AiHXw4 zAAs?!2#KHb#w-Dsx51v_trDjItdzy#L2Yv9!`9H2!b{5GcDuVN21_6QhH)TQNTMcwsgPv_cGp7SWy<7)op9oM8RCw3 z)2Km>#Nl1dt`XvNj*ZhMgmBG1vp>b=6uG8*gtz0)ONsxJ!1}; zZ)RHAzrM~_^zkWbGN^`X(0Q8L-%BHeqBd0TVR#hs?1cAV&Acbq8aF@^4uOyV96Z?n z$KSz=A<@Xh#C=%H4~G&2kF7Gl;x$1~=4<9lo6QZ3kQ+b7tBO`V^c17vdAVx>u`t=^ z;1%HV9mL2fW|CJfmuLrc);6=83A7aHId}lXmno-W$v*eqCW$ok5yXGPW|Ma;IR#*Qg= zR09)7LMG+9s(Nth?1hcGX}P>d;mxguZUjma4)=e>X` zmX#1LsR?;rkr+|oq{wTipHCfeqr17yio8^`f}j^0or1M>!TMN9n_`P}NkEn_fvYr} z5iZ!?k4#|wD-J>R+|3sg;1J#@z#;tifopcK#KEM$9ug1~!%K^W4Gj$?sW!fR`7*M! zlpBNlK)4bLqh3X&kEqq|p9~{){}cCm0tKicRvc8c6slYqw*bH+$!PfZGZ0(^?L&Yg z^CqTaKvEgV-1_{tKS%MSE8}u-T8`YVHB|nH0A{uf5KL&VXLbirHn0(vk6W4y6@wZL zSE<=_{8e@9U;(*YWAif`L3T5Qf)E6(^D{<*!Bf}gFy-tZok$Nb{p|_}u9|?GfkwsX z=|5PCq)=nxiEV+zPrqSegqY{Viv`}=7U(|ZQVJ;*Ai;zn0j^W<`&#b5e0{^n0pz|o z-w?N0by`s4D1(V0K^T(nlEiqm3Av?0>PaDik?87zH#P!TI>r@!lX)% z-7g5MTX9Mo2_@f$4I`;A_Ukii7;Z2R%w4=R*#h9J7R(T+6!kbxShMQOv?U6Xt&7j6OfCI661|KZ8nb|-m4Wn7Q->Ce=1{LPrOd1%-+agxfb+^o+^a!&*MQhqiVJUz|HO* z-QCDut>+xu(d%Glf}h_}k9a?A7%E~sEV5O*hiR1Z$HNxF#xtl?0kNBZPNu1Q z8KK$${r95<>N-LuRG`^{nAz% zauE0Z*`xgLHRy}e_-Cu9W(SGJ%h{Mf)SD#rqM@9sxWr5?n07(?4GXb4QB0D^EX&N_ z$gPH|g*5Pnh!b#A%o^*vJu3O165Z<9@ID4CUU<77Qr**l&A6P! z&gsodkxIS({>1#z>v28S$sZvbPDPwI?m114I{n5OueWy+SzreIfDC$!p)K;aofpTeKWJ#0C(YBxCcbR>3Ruy~{_ z2uXnKom+-<2+*^cB<+cn%yRVYomKdVzp=JvcLTMnKUXd5#^tc;lsx9i<`CH`9b>2N z2V*7+gmoJFo~QyG3NzQX0iA<>c}cNrwT{W62fjj})>#;yxKQ6-EXj75?~}wJ8=n5+ zT^6I2q_?*>A@1<-W-RjLvu1IJL7~Py?&=h*O#+VE?S65R?soc7Lv@;x17$PULr_@Z zITEhuD||3#_#^goRfKBOe}B5Y>Er}fYg6U@NH&}y`!~g!>_q>-K=GY*qfhTY-=xnq zs;d?=UeRmt67g75Ngn?lu6eQUFS?(~UlUr$F6HV18dhv+zm@-rp?So=P`L5w(5{+&Qfs&rZe zxxNRN$3aQ*lpx+Ijo+h6dU|>nw&q8*hEX&Jm?0s^mAy8f7;s+k&rN&(9rr=7is3L=E?fXO6VC| zJQe!tM|&SLn{S^&&(}Hpi#D563y*6Q@vfV!$qF?NbOG_veE=HK1tjE0Q%7|UY#_ur z#In$=aPmXOl6_0fhZqirQG$nm5+*oOTB{#yPTfP-;eZsLTdjM2V#&hFb!nRuQkxTU!J~dBKoY zWKFbdvve|gGFgjf@+*dwu(Gl<=e`TWc=YM7MBH$i&sySLU0rrt<$3*2J_MiZ`Q;jm zCzAa!8l&?&4Lcpsf?=2v*C^t?W>kSKXa3Hyl!3n;PaC2^_ePXIxf}vg7C=*_R9$vPlZ@79diquNkJ!U{?8mHN zw8m9p$(R`pmAFgPHOSS=jfa4osg_tLt?vY;CMG76HVWq4*u#2yda=$m*AE{YreU7u z3Cw?GV@QAeVxZi~#ih;A*!ZzP1d%bah;I}$BM65mU%c*Hch?9us`&VEG7vKdL_aSt zFM3Pyvj!nq!_D6F-Ox`q9;irHiGf~Dq0g@#bp4>DJBo>k!2>B+9h6KBS%zx@Nb%6q zkXN3zwwwa=;G?p^t9tG?<_u2><`!@_?{8)^iu@=qFSiAO*Y0@Z(u#^U&$iaqR?MNZ zSMQ`8{ z1AF_Y0`x;-REDoA5H`aD))kX3U_dy6@Jq{EQ!A?}!5fm2k~*F?($dn+VYMruO|HETe_CR1Nl$>ksPYnwz(XlRb7%CA>3zSQ4%AE2^YRl4PYT zUuw2{!}0o%0jZaw*Ze6$g?)($fz|bHupmKep>A)>Yd(Nc0&u~;-0cjtc)T4ehL+$L zy5KV~v*Hn`slhuScG@6F_BuIj_J$pj`1eLx3f2ArslIZA`=gQ^$X1F7iB$6VSl^J@Mm8sjQljRSZXtnWddU|?x#I6^XhH=r2cENbxi0EkR;5VGK+W32; zGaE<+wa1&p1TQYO3$Pd^kbiK;t+ljRK5^8e{O4?buz%bVaTPo}b3i{8<~vf@wRAAJ zI|cg1N-8NS2^aH1AY}L61qB5$n5+f&H{ZMt%)3N6xP3XtW;1s_1R6lR7afuf^^er6enw$#|PO zl)CQSUWMwsqWuKQ!c4`)F9vQRPZ1Gd;ZY5 zHHQyL!xM^=a!b1}chBXZe`0W)@^J995xobQ+m}bbMY{e86uav+&ryH#;@!N?+-dy@ znTtr<4yvPKex~ongZcUpp)Q3HzTxKaKfi;rB-iXf;UnO?E!vj$w__R<>vCyw%I&M} zdyC0|mCF92>FYZC(}%zLiTt`wYaX33i@M>=Q?tx0$%C)&`h<)JjRQ{oPqV1xVHh3D z#G%$~;++8Qb}*MceKgXd73< zJ*2hEpM2i6;A;6>wXdTW78Z&K9Pd`zRC-s2V$OlA4FnNk{>IgnpOuw$^3HqV8@{C1 zM4}!H=y8J-i97ck3NtC?>B@wx;obh|<9sx=*Ww}C;;769tr(-FCzE6*j2JuKTQ;0h z>wTWVp4|s}i3mFvww*UMo?l9}>IsolsHgMoZ@VEcS@9oRPuEhKjEmejU^VPuJKa~~ zeOT#v>P+|wPkGcNbTcjV1hVfs%-3lEGZGErP;3#mshf;}FlKYC1)c9@34y!06<#MN zWI}13`lP*Z(9@WTx|j46n0buj17*r1F&l;BE~NKZK3d0kW#{npWBSly;*!~?0WV(RlgDhGoaZMiNg;MaS!pmlTTk@tGSC6d2n#BxSsgu{u!Zf-PqXJ zV&48y7ULQ-QK$-n)=5T0Ihtt=6~LJE8C4VUCR99>>d!Jp?1pc`j{?y<$qX~|_Pc7z zWrcIz($a0^z9QE|t`T3W>%95Nb_pmpE}7N!b%oygV8wJ{m!-}~>g%5egbZec*8-}m z_1lcrzAk5Vb#lgv$)78?A@-VI!OvgxG=WA5K?@YDCRG_~ckq(xq9Y%vfmWc2~x_~QCRBMs^- z#h7dL{OdF=hU|0~^)tTY)8IuN*|O(YSK_Wt+61ur;2BH?kTJf6_}=zu&ybIv56Z&* z#KLqSME^Ne>#Da+ru%Vr)oTs}k)CBX{o1NQmi$`88~D?oX(8DrzBQdcIa)k_p8xc` zq#MQ$ma-;YlhrRQER4cd)$jhtd+YKqt|(JZ7CCYyrdCA!x%x@s!3DL~G-xVhnetky zMS{z*%=zQ=fk3qf*5(! z7AkhnYj^8xaJwkXMDp;@g7A0I0Y3`vJlETZ)t^7b^K{nur1Vr~5CXWVw5UGM!o=A3 zmuwRPdr9v_`?Hj1UnPDvD*Wga56uWA=YSHPpP#$1wRqlMw`zSVCviKD=3Cd&BBfDk zO+Mu8gW(oFdd@wGgqFt3Wwhp)UnvWB&G#fladYIRA!C-trBYTw@HzFa3safCLPzp& zV3r=n55x;GXYOjONm9Tw+(bJSvy?W=>^34z<&BMvx%M!6uAc^OOU{DfIXka$5m-*o zJYO+trC4XrINH?TcIqW{Si5*_l6;9PAV5N_$t>+k#^K!jxQ{}8!oXik=a^?LyK)Ds zUGgrv2Tm4DrN6f1m&*FJE%AXOq@NS>-V&P-6x|aMyDjQQ14woq!RTj5`@%n*39(uv}PZ| zsdK3u!^Zs;40O2HacJP^$ekeK&g;f7MB+0Hvc1#4 ze-GUKLSlAOSPT7vikhDpnjVM~uAUhGQ+fU;LyQ2u5U)?9v~>?Z8C{c1OiS&izia84 zM%fBrlBMFa7-^Lt^S6ENjsb4;I509YvYjNOD0)L#yt)V+`pXH>an$WA^E<7no>7N1 zgVgnY%TyYu_c?0Rbk^?h_XPU^Uk#9ny5Jt#?YTuuPbQn;pIbraMn@IiXYzARlPJ#q zG4kq(8eRg1W2~C@{o6p15?=G3{pB#%vg{>%e{DQkSf?Q-F&esM-i=*HyQy;!@~*>* z{$KqEpiYJ{Au2{Hl06_#ZKdgmbf2>o+r8|h3Ya3J97*WrZ3pjj<)hy2vc>gHbB=OQ zb>yyew0d}9>Z*Ybcr|*K3B5H8;+@nJax^70zi{AlQ-4n#XWv!lwV6@&HmDVs~bvCl1os;>r9Z^V|ys>YCcm` zSy}*O^h*j&J6mnd7 zCHzeZTx|In7qVxrCy5~G;lkUes^jwe2bEOwAxcj3RhRK36chMw4G#~`d%Y`re#LQp zw1~e38O%5Rx#aL0tu9Bwy^&C)CIIKKN1rpjHXKH5jlVUA#U4%jE1 zv5wv~79#~Mb=NI|aR|U6;L?4U%qzigFg>_wa8^ZpIX*Kgm{S=+@6_QHz1jE#L;H|X z7UR-Q_q0TSyL!9F^TqR@P8x2N^38Za6ciOV%^AmyoA@EqK|%`+@dh*iwuDvZp{6}C zpGB}TdoS~XshfjFb45rZBJP+}wJy&C=35V`1?(C24-ebwCQZA4?sjQ&kXYYc-BoXm z-7%h|L{HtoXzNhHbee2=3=PUCV7v+p4-AmryLEG=aNQ0-bGIJ{qcy>gqPy?Xuec%e zD*)UlpLF4%529b_sC-%<1X<8SitI7Jwn0#%q^XrdF$ATL15<kGil`~eEq}oUp4Tp$$(NQ( znMoES58i?5J!qJz5_n~vn#C@%Pc+=Gb1W=FFbc)_>bH+%*18eo5Si4gjsc# zJVD>5wpU-y&s?Wqod23TiJh5N(Z2%J|eM(6-QA-9`IL|nW|!=?yi!t5_|#e~-$Z@zy2gGsOGn-CYu=+Zt`?ibiVYa}hOtBvn9Nbd3YA@hmL(i|m z(TG|QwEsKGVfvms25Bop0#m2OF1Dqc`|9{+s5u`Q`Fk$<`T4y)lH6|YW{C z=DjgDyk@zZC16)qX{{?(KtB?J>_lCn#3h1;1<-xH#n8^~cPuP?nsTmhuGzm?MM>mN z{EPmnh#xmRToDk#M>ZEqnwt8uz1-vlBqIUH7N><1J8GgmFii%g20=l=@;IU0p;t-+ zlE2R*>qhYWVeanD-Q5Zlw6r9^M}u);dFOZ)x=eahRb3M)z#930Y(mWm{-p;qcjfw` zYk_YUU<`w3aU?`2Oi;D{dC1n7$YV1kZv=yj_2`u^LTvLRTI-Re)u$kF*^t3mK zZ=v_dfx41Lm;CbE3oP}^V-4h@{NIGb)2YoZEC{f$u((tOVf&`S(xZ-`q@R;rQ<~0;^E-in)TnQb_8*NX`2x> z??Eh_Z`Zqevt}=y@7CJ^e+WNK5Wzw-|K+Rp6#;nKh>MDfs!Z4i;2h)L*Lgtuk`}K) z(-(HUyTl(Dylvs-CE9Dr65Z9&Fn4idDOfKtyA8SEvoWohPAeqf7Z(@(zklCS*U)HQTzp4y?_MBo z$&_n%H{Z2VP<%P6@ZBj*b0`+}(fRDY7mZkSavyRQl@1SX^Jw3#b~`sS?Kqu3-w}0d zxMZl6^hu2=>^3-4yy44DD=Vh_?gy5;GC8#O?%ms=i+nu#K!)=EeT5`7$ams_v|Y4H zqn(4D;JVo=-X5~c;H{D1H{Rjq{c(vER_Bj_fn)|%x;K^pZ*<>a;Xp$%4p^XXwo4O6zmnXmW@gD{BJNY<+v)qOA z_LmkRf1rTqh_nEshgkm6AC5Lz%j1_C5}Z$yzxWk^j0CPrR}olZ`_@;NLJCM7fM_q5 z>i_fLJ3~W&(Jpx*+j;N*@vPwC*-rg7!PdPmZ5*QRh<~5Wad98lmeiMaAB_fQx-(HC z^{WS?EUKmN5~mD-tKvz=CnhS0|NF*o^1Xgxsy1i3n9r%ogJF@K`f+@`yy066W-e-8 zG?bedQgg0LiYyT~a5@0b4uc8!&yL$SFJ8=ll-VfOm^23T;{sdW#(HM1D1-;zoXqt~6IG10`0jj7k{&VVbf8FL^;;x>x0erwuiY=%$HBpww_E9d@bB7Z zPZxzk!CDA|IM`ov2y9lEbVU8Wt5YwN+~`ska*+!zW`m|enU2@-K=8w*+^FV8H?ecY+0n;2zuwgL{A=!QI{6-625-cbCB6?t|V*&iS76 zyYJPldVk!ix2h=i%tuEAVk2xz}6$bf_~E# zIDZ5E2kWRJB?ePDM!FCEfM6~vFA4)w6NUEp1rhog<*T%oBMc1Y&p&^#1NOxxFfjF8 zpTtE~-Sm%Ek-m|tJt3W~pIoPweL9))q{4`Ptr{Aylpp%-1CD4PDl#fS4;KOb5f2Zc z;Kx-VGHm`QDQOzPmMdV=ie6<>$LfpM`dHh@@v;75%JDi@`_b6);^JaQO4Fs!L+D|* zIl;sCkbjTCUfZ?TIR75b#uNX1AL8-l|I)Ps+1-&*mV4GeZ~m=n*Q(R0nMqeCQ){Xs zny9*MBHbCY3rt~5_7aMrjnZgrav4tTchOQ=rahhgAR89LO^8nX4~cy>=a3ZOK{6H4 z%k)bX%%lia^wp^?#>Gm%U~Ol~&yi!207>zG4~GNKI9YIbjl@nYN9KwWtFL~?vMy?b z@qb@NRL^pM*+Wf+MF`vY!*+}FzBl`yiIhoGwBngBu?VT+(CS7=cfTyVVYI8${|`z~ z>YpLAop>tIIFM)zefTQ8(mtI3G5K$Ejvz@I!~D&Lv(%pV7XJ{1i}ZfaxmjzTJg#Eb||lagFx?E-zGhBL#l$wbvirORuRR?T%p|?zGj`g!ZXTRXs{{6MfTL zg$W5dx>^RDpxP8MwaRjtDM)Vr*k9HkSsE=%i$4U5xF4GobbA;#rx0aRT9P!7XrH7( z{B+ot_XabSWnQf?P1f5op-Z?)@>y}UDmqw!IjN-Cn$*JAz;OE^s%b4$YH%w!X_#lF zswyU6ZKbld4G(un(Htu&PnJD@;qi!1Ku<+agVk57-L^YxH7Y8ZW*7HIxo(|!XUoV4 z`Uj8jN6!~372;|Ncn_9;F;qGIZeIut>P04|GQ3(F;uxjz?7Zup;z1_` z54AW-foOhmyj@+U(K$%lo^DmJlm$iagCXq9tnyQBOQtHc0wiZh$0Ng>7x? zto6>TX_l(?4y{a;TV_$}^Pyo*MsOO4Z&oFH-;oxy-k*|JhV&{zGAh1kH-51ALza`1 zlZ;xN6ki{W1Y}VqW&gFSMw-CWkX4Hy7dEHpLEXd4Eu6^t5mSajTW{W!+y_^@;NBmK@Sq2BGW2H{nN$6$0ERw})Q znwhlc=Z?qcpYj)SB4x=nTm8cnW&|=VPQ$)JI#gW?CPLU!P7@ZljibOvDQ7b~-;r!( z^P~@^`JZ-9GJyB|{H8^aJ^dEotnwT1jBb`we#_fBscKq{N&%pMx_(IZ=L9cDt-2H} zBH(tq1Ms-nX2_~Cu1tTG$v!RW`=1?9kf_^ol>FnOfXo_fEvr4!4nvc0G4e(0V$f0T zqP^u=OI~L?`__6C?>$B>N_GKSh2+qwOHVlDaAC7(3``D=reSd_6{GOe}Lj+!r zYy>DH@p<3s9o+0s$Ts&JLgx`4hZocN-Yw7;=4F|CZPEv)gORKq0Zua7ndX26i)wu4@ zX7YtrLF!rZ4uQu9T+WR4t7TFO6j;NZ}NXwjJ2XZjeTQzQ6K^cJ&fB2~n^FPv0F zQ&7@dyh@gOsyUI>tSh2g*b)!rE{bA~-t4!Xy|Hr7i5drP)@F$MQ}h9s9qP98w)zKK z;`#@L6_Ob!(yf@X1U~8u7}clim{rbwOeE#EZWqX#7j!OccRqUCVyV)t_C|Y}FiQ9X z{GnnzfnuseS{9!$=IT*;sduno>=?s>ZDqq|Ra$-@RFT`x~1Cd+!L^*voz2ML0 zST(Fr9Cm)k`yfWZNvGn)5c?FAPt-(KYvdVX2fI455u6XozxWm+FX^XSub6{=`2l2o z=rZK*Q5BDmHxz}LLch++HLrZJJrJxIe@M3t`f8BYUiT$Qpr%FZfTPxQE!^Q^B799b z;UZ=WeD9E=(p9IBsj0!5)d&)iY_G8p>{xm)D+oHwX zP`F6*)^tLY8o~2@VaywbPBKYv4hnNKf(3CKb#XDKA){yZ||Zt4V^wW=_|NGm}=B!pfcTy#s3*#ol>(aFUrOPno%b5Z{OjPnA1 zfS>QJ^~E+|LtY-^V&c_dxLIpooDMLc-sINdJqH9FAnj;2i6@;Qj!!v+v;|J@b(D%2n*l!$3a3NzBB$lUHYDom9|K z+3PZ-{aBlr$nZu@XAs=Ic@>rMCzpl07H~nPWSdsa)T-@bzEj{31QL(g*d)_ zH;i+B%=WXol+MVP)hgZvn@az1%WoXj<mXc1C9Ar)u6|!x9 zb2PoM@Ws~DBydh(N3Y1nK0bj^LuEWS@iXlMmcvlw>h+UCf_+0>=ci1a773r{8yVi6 z28;{04u`WA_HB^yeU{8uJz0tXTWj9^-+Me8;^HV4Ly50^2^wN$coHtpn85 zZ~(3o)a$nmk|QFYU@lJ*<1MCNZ9b0WY&&?;(pC@h>S=G&2+A*MI!s7(S!tBTuxm>N zuXWqO&0U%K%6kAx6xK?vLrivua`GgC?dYBy9ilR>$%f5eF+KV_or-3(-m+w&J_m%Gyoa-hUM*;3`F3<3=>gHo%T-M0MTMY zo2ZcGc;XgZKN~Syt_ZDkTKNgDSMRrChv4^;DpmXEQp1ps52xydnG_}--cXZlQeEg^ z=)45FjMC7Xh=y66c<(cb5oi#{0mM{y_hTvV(7+r^rg!9^OX2mdI{{?}NFv`Lkj(I< z6>)zURL_#74JV~F56tXPxPdA)<|y7d7x&<$pI&<{yQx^!E@q5Fh^JE{=pE;v zOWKjxOIDIkwVsyjn$PobcZ*`yrB)^)V#i2 zCI&};LA-cwoWRM;hW|ohDP+!&t^XeT2H(A9m2_ZGZWqfkF8F7Pp!~V3gQpf#fHlc} zb2+VX8vm>J%PGDq0F>8m002P7Nw(a({^8r47&56|=dLnm95LnB@0C_P*&yP%G=q9q z+JDKVcO3a0CHg)uo3!qM`8>~ z3hvgE+7S2K?wLPvQ{MKOP1cOf^1JotV)e(kYSTf{hlsegeiPZ%zoGmyY*gWR^4nX5 zVP%dAz4IO}h_vDtgZFYL{6@(m3Ww2%6qWIZ*!e_)lFtz^v0%s&3X*)%;k#*5a4OBk zsHxzk3zC*_J*xZO3@~M>QOeH4J+D3gV&ryamiDl=j>dlZ6)(isOqSX)OXkq}rZHrg z=$)^SNApqIPJ%N@(;%x*SrW#x)FW!>b}GEo@TWi)W8ZnAhh@cE6wr-&s zq0fT?F&+2mXG-vzS<7vM^M%F}5wVZgFDBk1X%8=!f9JX9^xUh=F9JsCSE<&F2qUqL z*|t9(>o*V}E+?C2C;EG_H^;3HCVw)(nzO6uifFi;pW{F5)jx&tR!&0oZbKC<=Z<6^ zxpv#yfRIs`IDv+Djb%|~{cYBqvy6^*kR0oHlBK(4?8$L_Q(}2imWEbZR0?b&QpSr(l1^KyYu>{;Wnrenrk;hIe#*A-RR9ecj7z(~9Pw$S4*2`Cz0m8vAoG+VhG)X8E=%Cf)MMZzE^3W^}3 zz8xh8{9FaBRkD;=S|+6`#Dtg25oN@&@Y-d~oR%h*J-5~EmOD@xdSZmJwBtheu-Km(O z#(VICryJ6km=k)FQsS67WPlHHZyty`_xCfHe`i(aYpqSf<_R6GR4Xg^7{>|JR;XFE zvoJ89b&bW7Hq|#Y4*HLC4?^J4q2-m^<6GB3|0M-W1yD4DRhMHv`zRyl_4UQT?fexg z;lHacG^x8>Cv2w}=&w4SE%rvOU~CY=*X#|LJ-wLOSv?a4)Gpp|&P^J^Nn$r+K^;}D z;Ygz7R9yk)zaO6z@v;Y&q_Z^+>V{U*anFFV;LX~Q%L@MeGY$?fnrPZA#$*;5X%!LS zd$?B-|0zoSvQJWzH8x-0dc%)W6*!L`BuWi63V%i}PfcgZN1v9jo~m$C&>9fFJ_li> z<$uaK=KZ_o!{cV?2}6NeW|4IhG#Jd`Vu<12nE(53bbsFUhp%D8*-MrSw2iA8-N*}5 z|4d9DnKL_CVjMJho)GvOn358(efi_jQzsx*y{pSGrvD$cz)^-9ly?OYfG$ncKQ+p| z8;lK2M<3$8fP?>pjLqrZUfa>|dG13aD0Yd-K2>kDIiwPP({Q3FM$kiwOKV0j| zCNlj`sshSNOGE#|bHG85+UL(d|Fe3W>kTVQ%l}!C{OauD!v3G4nz&%I*Q-mRf8-p~ z%WwMlU;hylp$EGQ~M>X0VhLwpUQ0s!IuOu^X(#*WdmyG*19Tm|CT z{RnwAJX9M1#>NQ@`@;)j5N0UntB676xnA_!$s=t#=;~_|$^5`FzR>AnK9_@${zd2_ z+X66T5Cxq$W=rXt)@O6`A_y|hWOvRHGLzz~cW3TQ!6EdzOkpAPb4T$X#a-Ae#Bl=Q zBD#uklzHHMmC*>G1l9iKe(Gm1wlWXlhujd+o}lld6i|vld_R?%|4gU%_XX+n!6ChU z**FpiXdeJT6&_R6KdT}ByP8A>T-$VD7HQy56qL!YZ?C#+t*l}nAHDu8-*3`XN>VZ? z)ohM$*2ds>1?hdYSwXu-hc5oHKI?F1kQU#L7q2X~wW^FM;_6~FpiiaGyBvN*X{vBnpW@fMi2CSD8;2$pQZw*;YwAVaRp(e@W3fuJp5a!GKF+|^ zBrRSOI|yYwP4HLfqUfMi=E&cOuW@*3a2TzaGhJHj7L8+f$KpxMGmLj`oPIG1C)v0L zmD{ec5Ge3TR%SXELI(94pw;tZ#TUxDpzs*$!YqcA6Q@fYKxm`*AMUk$*TKZ$^@HjL zE^BniAj1!JWu59{4#?cgvtp<6xdgBANK(PjG%dM7QZw^InEi_5I;0|<_xZh6vvr0^ zf%)LLxtCtMP*pzBDOPK^_t@xQET&@7Fkj#DsBhq;rUMP+o@`wlNYWmMrkO^!%0hcj*INw=O?)g(>r zAFAlhhjW8{b$`~LlLwjNmTr7@Ow?EtJmO|AGHGvt!;z2$BtYem{{NIiY4K{y5mAff z;ud?GO?x`$1qis5%v$O_Q4!wirSJT*)Ate`Y_%Tuk%M+{XGc#3TpA}l+N>E_vZ7cK zw{y|NB_@Iba%J|_*I&7)acUO0*kyeb*@;*%fbO};IO#v4Df%m#ufxy)9FQhdti{k@ zkY;09rshY1Zm1Wd1Na%PlRvzoLR}?}7#ca0!NPcJ6BE;7%08(_>wGM~dboF8bAr5e zjuF9MkW(j-TJSk>My+ICOoQvMfwlN+=IP}uBwz+27YqcnSa_@5az|K*SoJzt_Agu@ zI>Z~oTaAq!%87I8L*Z1$`E%1u*BSD)2Ip>&-JPhJEw@PRb!lXDXBYSF?McT1rZS}| z0U_RMn@|t*9)|yIj#jNZiMXf)14Uk)=j!#_?Tw3){-iRUsTIou2L#{t-*Jkr6*1}t z!H*75@tRL}(clX7D+3o60 zj#=v(20k21WSN0b%@YCnun^EBme2N_~8O>y# zGPR+oDiANlv@BsWDKsDKEO@hR{@^7)TpN_!B96mCYnKxe!axy z@b;~aQ{~=aN10CgS7Z&(>KSeyJe_|EE7I!=x|pg9_x0Ry^=KOb z_i(Jm)Nm#pSuImHF?-82=5r697U|Kir#i{q8jEkY|Hnv%ZX>>i8q0XT-!-{hnr_}l zr!i=ii68RVouBjbwqpZthnKcqn4-!(Kl;=?GPpkt8Vqe#=`nMU@aeE*_=I{Wcxbn_ zAOb=g+?c|AE+(_U_Rgon$6I5G_KK-RuNKD7T5x7@6k8P|DO$5|O}2;M=sYv->%VAP z=-FkZt2$(dkGU2eK1D@%yH!8ywb<+lv^cC^&U7yvNy_39{gv}ZNUAu>@AK))*(8bL z50^^?JE^!!DlIN%a@5q4GaS?NZz$}#$DB~q`4Y!dA=C-&OMjfV(v$dCCNoys@L zR?tX}J|?_PPRPParHc!puz*{t(hLe46@O86S!EJzR&KwHw(t#1Py(ZHnILt z6lIf~HjzYry{WXew`KBg+g54z<;jO+#}m`*<(w%X(wcOv)HRde7ftKVvg#HCU-d9j0Rm-HEWY?g8*7=CH-Zh!XbBgm%U<7;o2oeWPv# zOowst1b|bE=2^CD)Bs|_zab|;~nQqLVLAyPhQm5NB6HuHaG}=juw(Fx3ck&g+==E*xZFEZD(b0F za1Lix+3xT82Td^v)0Jk2(%7I-bZs)kUSmHzqI#yORfex&_%Owfo4S(~3I5JK)h@g} zT-PpV$qfxE8<%ij_~!;HmivGk&j;5(Z9hrG534eOuRl32lWR?O%Fl71C_!AjpP}Bd zW6iD;Vm1qh^i?PijFX)ZdHpEaf>X)_qes3s;7o*lC!m+9wO@g=&^kJI0458=!V}kPaTlVl% zZu}O1L7t6cCXJ8C5nNe3z?v=VHgKy>FSsef1UC(+@3u@vevnh^hY8fM235F%$@tX$Oa0+Gep}l)OT98skWto{eMUenNqAWl2x*} zZ-)u1t@4D{p>8)&+b5E!3$lc{RwZ%t`)I8d>*r|JggcnxnraY}P`U7@sw_zLKcqE3EzoaxX{bRe|G zD1`NNpS=#BsJxRSFcY;8T-5IT?Twfiw$NocU0598;29wy+`XcVG^P8+wk5rScAXS4yl}4#*7=SNQ`R3!s_|8o%fT>R*>!O1fhmCpauD4bk8BS!Xe+g z&k3x|Cy zz!?5-k>BZ1NgD-TY@}LOvL!v939O<@+v~Uz1$47G(Hm5gk&XB(RoMq#$el)AKfQWo z=(K2g%x;6>a|oOvKI;+Ey5+?lzUjWeJ4HKHOUg>sM_L|mEFYhCXH+j&0*S{9Ktnv4 zw^T^IC#y@Q!$Pn`__A4E`lZ{Nyn>VmoAxyIVmIomg(1!y&%4(n9GNs`BLmw8Yb3?) zx{qvm9If#36UHw$DYX3s!E(z3x+h>qEoD<1>aT$*=7S7#hRHeed;#YW)UBuMwWt*H zbffJ~Kw{GV5Utd-WQPwtx`)&+njRlZifKI_L|gS};eW8l6}(ePw~ zlDXXJ=+sm=$t0{wNLgp!V@s# zEn2-MmnW|dZxO+HKI0x~d=_S__VMF$>Tz=$O)TSQ>D}KH>F0K>@4dAGAbzK0rWYc^ zrT`f)sFD|yYit89bBp=)OuAH%&Gn-2Ma&2q^!28D2{J_F4VA(z&e&IorhZ$-^?7nh z2h>^KHE2UT+RdPEiF?xI!|3D1)o-f(| z8_F=9H8zcs;aB5q?1f42o5maiR#FdsKCQN4OoTNGeS@T^xx`Ehi4u52zaIN|b2su0 z&g;Y5Yj;t@FqZshe_d!;_(wUk63@du-pVF5d@aEjd1kTdX)O%2PUx!AW^L3axXx|+ zg?La#gFC5@j4xQXsR!x8W0lFMA5OZUEn~6tHMk8DXxQxPn2IA=i=iuBiwOV~N-MPK z;Llbj`Pz8ttSxjdtQcMG84P(Qp*fGBQ7X<%Ky|otaXx z%ri?ix*OM6iFv-=49&GmFQ`Ac@*i7QA9ebZ<@o<r#8M$uShb_>!&yU%AO*H3sZ0xj^%Zd5W_ghd{AVAh|21AY5pw; z;V@AOSJzvr&g=GR9aNPhK-D?M0vSIk0G@lgf}(^wH9D`?yjspW+O~ULBXp>}(VF@% z3ZOn>{pBob>Rm?dz%*d}VLR%gtnWo>Ic!JH$|CU1L6WneQdt8#9^qh(x{ZI&mY*N- z;R0z+Hk!EaGC8u|;#AjCoi(xY-9)eS)GE98papNowg~Tvjl3m+_TzWd6AM2*(1#2P z!Yh=yh7lR0960zz`pbGly^%A%xpt+hsFUWLwKsDTpv+Ef;42sSkG!E5e3~i5(oO29 zD|8rHvensMzR((6Nq{JIu4?|P4$R~XoBamh&Eq@Xo79c~-P_z(iCmekH*4KfTSW$M z4`Qu`g#ugB@+Gm zM#lr;4W>mfKrFEl<^pe}G)@@nLI;M5k5m@T7u+Blcl-tW>E@=(L{Y(6geaFQK2bAt-8?xY9Zv{?@8mX+)o?!rj7tR8v^9f)t zz;Su0_LV8(7^GXYvcjo)K)Zi$ejc2<*67?dCiGzAT-G*jM0S*QeeKLkaBzLDe@5y= zhsHEs9qv8PT>fFz>h%!up?1J)<%>TG8}mI~a8%m6 z9vgM1yojx$!&En~<sr=%!$!RIB6vXA0nH2Zo%+4TS!Zk3X2YafFW*T}raGUh1U;@^ z&%x=ud#?Dm*SV{QIEp^4_2yyqrjVJZ!Z9w(^Lp1^YRIL|tekm9zvEYuvJb}+Z2$^_G(efMFC_ZqS=U|gioY;>Q{I$J=VF_ zsSV+?6uQA!J~_NTCoK)7-LWO8o%Krp8vcCpxrfTlW4aBvRqH!==a1UrJ%?CZ>CpE0 zb<^95DUaj^5(G^Na^^f8AB=bpk9ACEEq!BhN4&1}bBP3GT#)qxNxtUJ;6iH%ibD5w zj}<3+Eyj<$7ssD2xBafGK4~W@s}sJsbF2WJAWTDw<1xF9zVwv}FE){#ACgE7&JjyS znAJczly?CR3)QE{{=pZ<#uVh`z8Wkfe2R7}w*s^5wN=+~c+Z(nh=BmusxTOw0Wukk z{}yV7(B4k;q^IqmCqe5~pKmj6?40X#n=FF2i3v1b_<-#xvcp?NWkhq44UN(&?q%Dx zSa5|;8W6xOKU+0dLB3mg@wJpjs?XIVh3QA*hR4fdl3&wcoTQr5WM&KnpF>)R;wrnb zX_mlUAO&dOxU2kvtwEW= zCE96p3cD^#mP2a13I5SBx0&u@lT!l;x6qI*=p-R3Q8Z_RcflN^Cr@NSSI;wUrSr&g zF?NmKH~HDN#qjs!K?|}r4WrY#m^G<&x7P0FDX~1?9og}G!2(JXDHJP!A`r#W`jkBt zpi=OxZN!d>3E5{YN{vxME0&m`p4#29rbr$3O7m`u{RVc&6R#3fs6OO>WsL^tVO}Gx z^6s?XeS|#6zuESbJUDZaQP(c?I9lUm1yz8u5~+j7Vlm`rr{d?Rx>k47fP2+M2j^of zQ6~8XAcmKgPeEYZp^o6kWKrtiy_t0WiKJ9dR%}9iI%~8~O zEAnf|C9{){G;9=-XVk(g!ez;cgIV_`X}`r>_C>0_@Jc=PyEI9(VHQFC*E+I=iT^Di z%^*${WZ5LpINR>1IE}K5B|?zy+9I%(uYMUzRIs|qA1&|fSf$)loq~k1I@BYJlPk!} ztQJ_-4cw~A?H1m3t)KD(A4A~t^U6L~>fF1wm_tjZxeihw+d9_hkt=jOr~AGltl`Tv zVb#@TH&JO^opWvcW1EV&L;M%LIc}TJe(4r^6C|g)Q&UqW>O(w`MInQ@+H$HL>i?}! zSb~b5pUVlSl*vChjb&;04#7~$g=Z(gH2CdhnY)ONYzNxX#AI?Wq2#@UC> zKq_UMx7tvcYlv>;-?&bmo1eJ2!l(LBV*NJ{E_Wbdjk=;IaF}q6qj2lz)Q)4|Q+?fb zyz(;(;@pXug<8B>`SRi{gKO1O(xc@n2ng)+146drOkXru;y|<+Z#72w^7ttw#3u>H zO=Hec*+`_2O^Nyi4o)2@0T2JG>N6@qtJO#wy|#){b>(Yy5_3%_@zm?hl9mV z^G8T5#b3OXYm>m$&3<=xD*8Akbmg`C{@EdTzHVH@*muuhJ75xGA^*LkI_u$r6L^@8 z_Lg7pvrwfDb=%TSvGatH-ya~7KJ_mYS_^utd73>&E9h0De-YeiVs{>-pM64q#r{Cl znnm^gJIr*6F=h;6@EWE36cxp(b^-Z3bHZOBGdJ)rJeoE!I=SeLezL=7@4Wg*E} zQ%DddbR&n8L*|laB&YtV3%R%ox^1QSi_ZRm5TTEgPE0Jm%Oq{`vRz4qyYpR| z3VHH>u=9K5Q~e$f7aDyftjO*%A5paK_=nS(z2O#r{41|GQ?%>SxE7aB>b+fB8AWNw2kWx;!^m z_}qVde0+dkl9%@pYCL9)p$u3Zu=Vh0{o{-Wwv@+RTXY5OoR#zKYlIquuiIK%Tcb+K z%bA?hQc_A9gLN{_2=&8ae?r+oBP?w2lyUAV(R-d7m7)Z--J5Q|xEy4~&-?DZw{W4V z@j+7|N#eima3Ae$C3S;5@mQ1%kMRmt!C_0@|iIzQmk0ztRX zZ=p`{8b#=-rP#EzGP`Rb6{8;mp~R6KWOr|K?*dP$<6jj1!0Ma8QYL74X!HY$P^!LV zji%?r4zaYeQ-=kRy%oboh9&e8rQ-R{%Cy}_tt(FL_T3n30I-I3V*FBpV(=Sx{dYxf zkR$vqe7nXpK+(Q9uFv_|6cCz@f|vrlPX?rDXc_LHqYq#w-}< z+Tgb&i4s5T^^AibzkpdBe}vF?Lg$PJObow*jcu@=AHKgnRGa6xsv+mLOGl>=(hWi< zid8Xw+35H!xJ6*#_3Do4ETvqhNx@t4uH5O;7jRsH;k+XlD%DoW)4M-Pk% z$(W6%(j@5uf25GCz+(wy&wccOfRjdbV~&@bKtJd*Eug22Bru_d`V+d|sFjtK$@Rf( zt@EKe=Ny*yLO3a}%4+U424u6Y+nn8MhTb`B+-kYew$^Nvc(K8n0Ev{w6lDP8hfZ;_ zDFv`Re)T+ErZTsd+>d%ZC_#{1>Rro0)J~pYj{p4qsZ_yU>O=k+QdeSO*UOlfc>eLI zInB!mMT#u0Ex)!FK0GM**TmJY(@{^)V+cq-R-I2bK@_X&sb&}rk4MaK@1*TC5fKf4 z@FSMsTj3YT_^6cl^Zk6+usrvw$$!>R+}WvLT3V_aMpXY^EQzVUn1O|5N6rZT+c*|0X>&0AIGq$`K%*{5f> z!>3irsW4EVo@(KVF~77PG;ZH%wtN?5WQ+q(8xJ#~&E|Mv<7inmriMndTU?bU3!uwz ziah{Oj6{<0&)7;x1X|%?BEO~DKw>E zJZzqFrM>3Cu}_ADNs! zm^z0U+;Fbko-En1{AkT`d~8|Oo1TE9RNX*^1(fUff?V@AGYyJatXetW=n&y3`%W-d zoV9VM$_iq%-0^*4BrEiv-=%QL_S!@G!IqLkRJ?h|3ymcfZ_DTYoG($Zh<( zDRxcx=kfYK#r^Dw--xhi=nLskBc_e_#n4=#gUJ0Z91RU^c0uXx=$w0@-ZJh!4O`j9 z@R2I~W4_L7zqWF*RX8|_z1qSYMiRJBe1y2jhb~CT{qmog;U|WX1B?#mt1+YH#SxG- z&B@&IQH78KYT=D5b8LE&B`ri}=D*FYKi@Q&4#m>Eqf&BO_r5Jrmn6jHUw~PJhGWq4 zo%B71+IMPY>Ln}|$hw9rWC$$uTxW}0_TMV4@6tBxKkMjN3(1I2`lF^|UQkl)1!PB` zSmC4l%XmB6PM24NPxN6hN5LTt(gMVi)SYDOUKLk3X&HHwR)n8XXWNO+5Lh!(qlZQ$ zO8LQ?yuso9D zW(|}+O;?0pWiKrho)i{dhJ-8;bmk4?trFwt>B2TC)XU9Ogum~{p>)e{E7^yhGAV+Z zDBRtHg9B9uE!BwyL-x$wacO;wkfm-wGmAXTA*5b<83wr1k-`@8L zQFaHYOUyCcu^n^1%Af7YQi86`$@R_l{-ctf5Yqg4qv8pXk%}40xXOZr zcP~yvn;H0mOp8Fmm}JH**R1^f%Qv@lAOLC-OH#QhvZjs@!0>9^_jy_u6~mZ-S+hEu zpXa+nxKmU8vR#{eB>xGIbJp+A`udsftOEaa+Rz+A)gNDiP?IQ&tO%q?j!c}>hx3kg zueZO;`UQ%g-{{MS>9$4d!GsurA3hInU$K20DR`!_o(M5j{>@)lD^i)tHlv$sGLVf! z!~UnSC#F?I2G2rkW@3VphK2@(*HlP*)=hvs;VZ=ej9tuE-3@#LJ06yD39OStR>2iO zw*T&LF$&O~k$4m93IOXi=13jED5s%84Z|!llw-B{9gck|9jkrkhO ztw6JECxM}fA2UshYcl)@FDdQACx!Y6588kSfbN1n?mFKjSY$GhSx1%o1Hzl%WY3j; zC8um_wKzax@YjUnaM-4*C5Ag~>6TVqO z>?^%Ga~`&Iuh1r(wt@H-{=;`x3vZIBirBB4QABfJVZX7V{ysmYkUdvdh9|3rMoK{! zy(OnK&`1oIrDzxc?Iuk8+uCvg$FOMl)kH3e&uMR>`7pE)o~;!{pfelj=-BqT-4*R4 zHs-_H)KuVSGh0CP@F+YA^U-S7^CP< zP}~D-{ckvy&l(*IE0h&M!d)sxUJ>>i_E{adlG4o&P05RDGYsXa3g@lND>q*re70}& zk+StdV%R9BSO)FqhVzwr2lyN%N{{L@A@mJ@gKenDjDC1ih2$b{Eif5$>f+tkU=nV^ z;S*Hdy#YBg#{!i}i?o$ZiFsFzWt$*U5bN%y7Zu_-zA$TwRJ2@ki z0fZaJ#ahz?O{zcLO@L4us58q&fY+nOe(!R$knAG$Q8q0#l^BnyrwaG3&oRsUCMJej zs&MAph+CE0_;it?M$g+vm4FSR)*qW3p?C-p*bBWi{^yl{v$PN+XquLb{PT{lOua(o zDK#uCY;X1a(WeRQxVSO3=f1lICHiwmcyc($<9`@fr@bWbt|2#C7PAXc&c8A|X7AUf+9i)q14Txd9bu zUqdU+zgYeO%5u{0-+G~L!DKL+N*QA=`x>o(qi3cwj#gd;mRgQMaZId?0wI!CA-y;e zCp*jxL>(?12s1aFz^G;z{g*NHu~5DMk)bD+9svk8wRLr)D}%k(2U-*FUnxTMl=y2J z04>V_8oV5|zPckZ9DcUw=8?VQH~w~{Id<`{cIcrfXiAM_*iM9=7=9l1eoHO?%c~D^ z18b9$IochDD*%K@`qr})ENHCzgmKoi&}f@>zbsB1$wq3;v-92#|6d$^Tb=mHWV%wX zZRU2>2 z?7y$z4Th3I^8O>Sc4B-&LJDt8z(zOJF6GzPt2!%&suS=K^E}F94>Gc_sO!z;vdIfY zb|fa^&mKV8paR0v(4D02xLLB^q4pWnsWti00!^zwxIuH#Kon8fdfJ})qnMeswMzfb zd#Ie=_f8B@6kYv%dp(m&leErz~V zTBKtzv*j>SnKeTDcP#vHKk>}#vLq32y(7w2`R<5bH%A2%{QWj~CP|e6+U5)G!D15o zW^r@02)vq8;LVhd@uAC$?4@ zNC~gE>dD(v99lI;QOLs>zi7>4m5dn z|9Jy|ELrAldo(oU==<2>KTPdU!B=e@D1abR4vR%LA)3lz9w~4!NWDWnMP^bXWQVZ^ z1IK#rU`>e%Lax+=SNZ0JV0h(t_0$Ok8}Aof3`LMc$)B1#u@)Lne1gXjqc+u9w;a0f zpN>CoJiG+}grOUx_nS!f_E4M}0cX({C0o_!fp8*fFc@scLc{mSd>w8cLwJZ>ky5CT zQR?HD+i_L(6`c}Cxf$B1JSiIE_lgS}cM4l|s=^iq_O1R zzW_1$z)b~nHk6`%_1cp>XYY0mo6{A?aW(gE4}(9q+`ONBsw+7yzIzD>O?cOKDgppY z&=&Dr*r~ci%Vaj|-tQ<@U#*HZI7OP|a<=`u_Tw3#y&O+XE^z)yJZA6lx`H;gf=bnP zUgy;Ap1Y0EK>MjhTVhZ}&FhauQEf~S<~HInM>G+IFg-l^s4Vg*(E3|3CVH|AtB{~f z!)E$Q)TB9AwY)Bbcmj`u1A#_)A7GHtzv%iy6VaPuw_LS>g&Jd9ERphoj{im0SB6!! zMbRqVaOiFXK>_LR5KsY02?6N_>2B$il5xXcL%O>g-a6iw-#fqV{XX^Vz0Y2A zjXB1cW3>;O;Njtcr@}Po8Q6LO3Zg3=?h+DyK3`!sq;-?9<@oCL(4Ex-`Z1*t73`hZ%|p{|s4}t}T|RxU$&LJO z|4ark*q*hQ*dpXoNYSj*S!3!M9b$gI1i&!b*z?+-{wNd7Feg9@NO#OU60=HU# zN@Y)oo8mqC6bjkl+P!4QE62U*^2looM~T?$l8pdnahQR4bsU9LOXWO zWLfe+UX@Y2xJF|Tp>r|B4It1m@&x@D2VWinTd(r z&Q&adqnfpUXo$=1zEnV=0CW9e)fb_?pdAi{!G%gBIAd5Ol1xh^n>u^NdGVJ^^gAIetAQ|qWNq*h}{S>i3 zk*Ig#r5f&uOnCiq`^>4y;6{Dnqs8FEpFNM4483&Jmuk7-&GdaaHQi;dkzb1_fVrfi zp`$dj=7MCu^U!vc?4YT;__?B|vgC?L+wVNgR@LJ`so+Wmm*s!cPvNLJCL+aDXvzyq z`-Ub|zEk9&-!h6Qz(?ccQX;;#?oZ5RoOoWm%if}dBH>g!BSD9b%uMjDEZ z?-+epQpIm!!l57DeS`6_>FX+tj91rARiB2`jypB=`vjJ$Q($V@p7*JU=kCMiK$)6E zE443KoJ4?@t;c`#85Cp_F-NJEsAn?nUmMzHC`8eS_`kv8uqO~3^P#D>63Nw(|Mg)a zA&yC{=u8UHX~e@4z>|;4$~p0RJrM_ET4$MI=J(%{#)Tt`m8bCU{Ji}7`l@EhCw!`e_vdx$t7!ipvl&-(`Yobe9(Ur&4Krs zc8&hG^+4TQZORk#$RLD{;Hnpj~{a*;Y+}_ekAXH^R*!tK5w~) z1xDh-oJzAy$UHe`Ldu)68|qK3Rsm2D1U}USep0bxwN{Ho4KbO8`AT#1^D1g}Cu=>) zyb?o+Rl{1dG2A+H3q$AyX^S;tvhx&iHRh^6c>nv$p5OuYo0*s>)J>V59*C)N4`y|L z8e{mFH^d{swJu1RTK18>r{%QP?e}8KN3zEM{t_|x!WdT84S?TV2frE-ApBYYuo0m0 z$w8`+upM^Hm>PJNQ2DVz(z|Z16gtky`9J?*VrX+FX}M58TIo~HY44ECVHyKW|DSMP ze2C(_8BUfPDzIplQ7u)6(u9gl4Qmnn>mf{K+i@^GN@(%i>NDLjbaH@4zV^5K!rJq4 zDD-hCQlaNDJ5s1925+kKw!HcBN77oL;XcW~{`cbtZSB+_dDwm`{O~`^!vRA&Ha`A` zG^-a>!tYfp;|Ah?AB-YA#QAh$N+Rg0Eaz3(1EDcH02r1tX7lOYT+Uk`DfUSU=J*`Supo-7i2&^lCUeVw_P9`C z&o`E%sdjFw`Y_dBYUSYLkK#?Q+h1$;9TKR)xp=08~DLT z%o6kJ;&JyXD*;wB*nbW|7npE!dI62?mxN3-OtL@NcRM43N|Ry^#++VJc$|+`nJ5n9 z&-eD+N(6_A(#XEt7UQztR9P0~S)s7DN{FEJWl1SV$?#;P@cnV;X11q^ zWhR9*6S{H>WVz!M3VCa-1Kg{JMutbP?tG^w_VnlZ5});oM~MiR9l_RGq7$#YdAM0o zt;25r4=d*Mb{bngXkHe&ad)-dmK>{`tJuvFp}%sH{#{x zHHr`4w!E89To_$Yc1dr7Os6e+-F=jDMFANuLD;eYtSTo`GmY zSqN>8Vh#>9#Mg6$2as9tJn6dVIt}CwQ$g^M3!OOE)I^!Ka!XdQT2MA7Fp09aN(N9gO z-ToZaj_6pV2h6V9s&#>1it9U;1&yc6_HFMdhY}W2oLipuQaqTSuG$`9|42ZFofYb= zoSffURfut0o12@Tbuc7OzT8iU=P>VC?ATlgPN_D!#g?!*Yy6P)e4*d-KKA~8SyUl~ z--looL_m2dxO91s^^n?yCH*8I7nl4b{wo57gR!$gtb}Q(VE@av0-q`br3&rERQd@g z=?av32TBWpGFZhQf1g@fB1;xmE1qT$YIB*crmhS;NGmWdMG=FK2{m%JQM~H!Bu-#O z-`_vjcRbto*7P7fhu^}Bfb1=i^uQT0P4t9+TSMrP*A-IC0`)A0Sc4Eq%Di0H4xqUQ z65N~?b%?xp4=H2v*&&cD=9BCKEEw+tan*+6s`S6d>bcEq#HYur z3{U7{@{ln-$u7E+fs~eC7Z_6MFtXhOH(!QPa*p?Iv^+aKu*|Z++q`$Y?(J>!&I5XP z14V;mu+bmX%lgN`^Gn-eKeHd7b*%L%?<6V%mNPhcOaq0cN*5=h0@rAd36bLDV+*u| z{9Mv|xxhT#=W;bV?b$Gu%wItM;=Fb+GRjNBX=!NSGsM?iB|a(bZD&|;KM`DlY{-1{nTWbmS2+PH)Ff6M6N^G@r>2LIA|xm* zEe(H8BTJ}He?Z^(v*jgO8OlkpTV6L~l!2y16 zRos9ef)fEdcps31G59|M@CgW#-^qxBFxHuxni_q$t|$d8;}1EB zVC0@-z$Ojm95xlrO~_5YT(>^mL`n^$$UwLf@Q zI%{nr{v)MMq0aMoKyKhmcQZzq(`ef$*-~7I;75JAw zwr5O!s7C7$i9-@HG&TnKC>Xtm%~z18_CU;>2Cxy(57Y}2%b`Hj-8Q-XO`o8fZFPA% z*23l;|L7djR$|Xu^ya1!6emFcD2T$0 zHXmST2@t^Mf8nEa;42tjg0&&*=GM5rxe0DYTrLbKop!+tzhV;-_{YFE5F><2hyKrN z{s6sZ5hfb?ZZZGN(_#44%a`E3e(LvN5b=J^F)%bt;=Mdr_@xAIHyVfG zXGOCD6HFi@5zBDy5Z%Jn;Q%pm?~45V)?_El>JoI9vX-}e^z7_P5A}h2?6eaq=ND0* z1E~l$Z4qvIs$;n-@0jmn*weD~>%Q+7Grq~#pv@)$}eDWd+yCSvtGA^tJO#tfqdrPGS^T_a|s25t>yTOO}P}mqD2>MyYD&(g9O+y|?0Jd{;=N?4}9`v~Zc9@!sLj!nI z!brG>v`{b^b7oko!zg7Pc;rX5#}9vLsHo7f8x681>_Y=RVu)DUgA0V4HZz^EJcZ%1 z2t_xhhP<1-z47z*joX5HG24l%aXWtLAr8GqW0jYX@E8}hdGX>!oO0)T}yiftV^`gP^UtLjL1$T{&!I%TCmUp-?0|E_)10TA;Y7(?f59%KE z9=1cO z)52Emta#6{9*R< z7Z>z;740i>A#}LazHKF@j=Br@$`+j$_h-Y!^Lyp3pXo+iJVbN7z(Uv@TmF+Ks0oi; z!nzI8Z2;vBSgm6|BmsqWwDY6jR>-5{t+Qyu`Iy`vX+&mXFE2QKtc-SN9mi!-L-T)y zHS0G(w{*C0;UlMQ7|KDNmN)*YGzA$Hp4Tfz5xp<-Q?gEv)mcq0e5Po;Q66=5lbxAi za1&o!0fkBMGh+MmCQsdCn7uF}_HHd|dh|>1o$u?tLe=jdK%A0eMpw+DmDRsK8P^Te zZ!u9xT)dC``v~D}b0OkRG*zmbu$c~h@B~`Az>riMSSAhip|~=CMifPG%%+hk5h4M(b=rFCl37ELarPhp$Hb*%cIL8BowEs%` zCA0{B;WqJq6U{pGv0mwNdU6upQrlNR0Dll7O{({ogvUY-5v?T73l7P?7#j|G#1I*a zqm75xJ439#^j(3M--8>qx7~JTprh0K&ZUahHpN#@k38JxF4)ZfGw((g1Kd-GiM6%b zYr=)YV@yr7e#<&HO^ysh6kGy=V$DBuS-*)%MRY~CXbRdg6z@ecdWlWMoY>%E&X<@H z)t?^N+1Ms-@R?Jx3AqP{xTs|?Cx(Cj{ym`{^j9H+MK5G zIfec6n)yF}WPAv8rwpMhsSQ{C0}q`|^1uIkVUF^O=AVcOzUI(3DRo~&l4qgJ)ca6W zm#p>e(lnkEea2Yi)Jr5R-DN|jYU)v!sc8u{x@w_zyK~Qv7F6EZh<|E#S$YZgT3c~M zX@U}L!+o`#KjAd2z5yS2CW;@%Fao0BKW`zz?%Qc9Ei60RG0bvqDCDb zTo9FH6)81}+8jEF4~o{eU-{O75jSjO%=B{(*fNT|1Nrk5fr%2>6JGw?(bye!l{vSM zQCXTOty>>zQSeh8raoDC+kFqGQrHNiDDO5NVBeN#GPCpaQnj zHkUTO_DNHC(DsnKSwxE99j^UC(uH@k{Q9|~)p&X73D}(C)jqpOKA>Nmj7<-kpB!KH z@J@}4WFS;y)HzUG;co6a)v;IC9TE{=XOv%*qN~~p7$R0t^>j6%Ra2eYpSRK ze@k=&x7*_P2{H{@V^=3~0PHaunK8;(7E zIKd~V|1_Qgnqx!yil1Q$S^~U7@)utuA`#D`3qW#F;M{F|z90_Gs>6ks5UA8g>g?Ks z%SAK4fvDG>2ZHn#-gXT~v|8vyL(hMkq!^AYZGJb$T$N$y?(P?3kpr-86kuS5`;vJ? z+q!`Y)aQ28JWWNx+)eM< z602Vu_%|H{EtKGG%iXQ@_ZV$YmlwK`a4_yzUMO$5HM`2_yhc08tuUKp`~~KERicpR ziQatipA%}O#Rl$qBdlH}`mw6F?xn>Nd}jA9NgJ#ENz^g?(zMMp>Bb%((4YKP6 z5WvJoNS8)&dghkzE5#7Viqw)S+8oT_kV!fKRenP4d4E?01!d9w8ycuA4#z@=VXnrK zKql~96sICVXXrgKZ)5R1LHz}#JEt_Q<8+>Gr$1_Gm7q8_Vcz!#&3irU>6|tndC1pz zi#nw?*vi15xLx0*xWM%w@4m-^`-x4?;GlA~ zs6|=pqVD>;ErpBoyHfp<#=4-0_ZAdA1*yztJn{O1dC@f;AR_@ImZISmG68s#t2%>0 zikB92?-I$Nh4=!;-T7v+beIDI(s(wQpP zDHjTC3t?}_(@V#HrJms{f`sL9y%NaCgIf|4ArR)~U&iOO=#a2m^ez6K^ZQ!95G4kU zle(P}{G}h|p#yiB&Lj(~IMul91JE$rfh!)|y=w zYvy-ls6LC97`<1wlBZjWaF$!(wiX&gyR zPyl$QL0j^_PJp5T`nKTdm>f%=&f_UT5|pCuAWcU_ajkyEcQK)~QJ9|=^YnPtTBL%F zKu#{?UjNHTetYxmbO6lCBxt{ITJoqP1)hSJJc)T~IX^$-6j$=!yt>LQmZ5Ey(AqF2 z&egnrJ)WY6TdMtMRnxQDcBfgPDSRfqy>?E9z{#g)W{fo2w4BJgufU|h{AOE=651=x z&CN}yYpsZx|0-0<{~_o1y;qbb7T&t)K6r`SgJcPqTRME=`J!JbSL)0NG0vLcsi>*ZGpUvBpK+$U zpUQAfLAnLS2C~b+S~~>~u&~F*mIz5STQU^CP?Y1%nnp=aNHBVK@7F7KLf1Q~lY{%@ z6X{BB?WWg7c+n?yI#2qxzdF$geWV4aH$KyTyui^X>QOphz31`tt8phmOKB{G+oFT% zHj#AROfoL=hAflz1dv;u5OyR75m-q`H3KXa%|vgAfqHke|QCVEsM= zHAF2oI51NFgc4x2kwZ&0<_Wu(K2|zG-8A*Sg_ds^YSKzyeN^}*wT$$dt-rrtdBk|V zH(JzBt3X1(b;NKZ=lh3`<1yKI6KbuuDw8n`xXLfye8>BXsiB<-^CM>LYdB_hdOVBe zURT{i+6-I-7ONP+&)aE9RxZWX=hlYdh7L-HznV-9qL^pZpl&3 z5cvV6gs~4C039p-xHGu(R#zPME4MrHUPXu5)S)|9qY^mCc;#)H_Tru9UqU=nfde0J zw2a(&35i^f)m#}jQt)!-Pyjt0nQ^qd^|66C*#!74%vu#>Bt(#Y7E)bsej8*T7NF6& zo0}fY#ht3`q##AvPczaUBBm;Xs1ed>bHG|W0+;O8+3(4l2xm>(b~3(W-%eP4!W%zP zA=`YCJ>ul;(`$;e@^yTt2wN*d_fJ}PO=bqyIy7T8ht#?xMFZ<1%4%wXg5;m59ppP* zB1I461x_SXE;A;nV478Dyxt|u+;CA|@LFU!S)dRO`Cl z9n>jR$T2GJ2h6{w#Wz&M+>9hl(qu^p8w&j0*mHfTuoQTm)_#Q)gVz#zoQAc3gzJzFth30>cyNPL2&Y}|w0#)a9K<9Bxi?|7x<>fJiu5~r_f1=_46-KdV zT3p=$uS^`&Cv=+2;vqxsy!?F9br(_i+NX$i$z?0`G_Ks8Zu{(Hc6C5>DX*zEeAQgr z6d4yDE2Qw)Lzvn-YOJlJ$5{?lU1 zSD-G&(L@S-WNy9xir0j(33kr(6q^oYGcE}MMA0u{wzxcH(cdb=%bHGQRUEbL{%{t_ zkqmoeZOis%L}L(IHhy5^JAy2w+3V|!Oma?KDN;5Gukq*i(vX=hB@98hovgnHW&M-D z;oW-i1iPwW_p^$=NuM0YR?#4G7+z~5nukk|fU zK~lOTq`4Rm$u&&6=F+mf*K@5~w@I@VMMpmhW+Hh`vvzb)^EpHF(aNZne_Ea8wY>Yy z5*E>2b^WFGt3yaah-Me*pnk8q+MkB_HAs4;rB~XG1_XDXki0~hFcNW##+QGaI@IcC z{bZbkwyA_6qz=hj2jnoFMta?g118DvZxc9omg<}Y9_}xga&8GQeVzGx;^T)*f3W%BRfbzihT@+0p6@0DX zgWnWUQ&r03ydC2ECX96xxgjZ|>sj7L58GHMOG`mFmzW$)$yF!CBdf{ANN|Wdo9)Is zuE#cy;h_!l&#zi8gx?%W7$4i{44^>pC^#Z`_iO_TFCUyHYU=n0m|A?TgUzRUR(~9G zM`x8250g;0gk{F>eO+khs(8JI@VYn)uBU1-L%w>bQN6F?N$s^S0)Nz5*u~xT$#JKzg+mSG&xN-+FVTHEs$rfQc8Muo z%MHRWIEPs>Jx+(vk~X%(B=|+6JjZD2fhPDc=M%c2>z936sxcD_A-Ylyy zFJ(%kX&E8gEyqI5>=`zqt8mMl-16_|-S9@Aul#^M3G=TNDI;@SR&!hAe5J~2)E5My zS@**fiduR&bSg6JIP!XOoAiAKtE1y~P-AvekR9<+KLw49Pg#Y_R9EZfE)>_jT?=Js z`a1YpHPu+_s5Y55dWDU-?do%uCgaT}d;`zesHr& z&{~i;E7f&UJ^n$-Z?y%KQ8uSTtsCgrj|REkAdTRV42R}hP7aP<-mMfG6&01D)6m1O ze4i2!&j=YCU88gaKoG3&3E`>j-n!fQfs0bk;bb#382$*&{L!aM+vR5$=f%r4l$n*J z0#+k`ruR)GVk19F$`-fm@rgWNIe4DaakcQyEw$#%v3&0gvh5~X$Q)<%ir}&H)}{>+AYGrc;KQ}j%=$EJGje3IPa0e) zE;d?h_t^i6=czSlb&c`f2bIn(!s3UB>WH!rrtH|zY%&A6aq5%Fhj}!tgZEPz(M(Ph zw(mV>ZoDBGJe-eZEtl`9Lyd?AEH}NU7>vSM9Mpc)p9)ZmZ-tuA&~+nL$hezth8R%L zmjz=I7T;)+-xM+9sGX&|Rx5v;hC8&d{qyOIyLxt`4pWTHLfPl$IaRrApkZ<{%k#ug zndA&FMG8NvdtUxbFMPI8TKBi8A*KDhyilPA#fqKlZehMMYh2K(Tzy>4c7o*N5wTj2 zRNrS#wS|_;G#qE&ftf2bnsIIB((H7I&X7IL_;pr?6H3|e*QN~^mgb6109)`{cHegt@`NVs?hB=4KKh!D$k+F ztRMIW88HRVqqH1n4U1|<4;LFJT?uJPB7Ja<1j%ch=W<)JKg~7pDbMLISf5lUMSjcN zush+rpVmSeQnB%_&lA8-@`eAF{kmdAFVZtsL&!TISez>@wcE}Kba z8T-%%9o_w5&b++v?1{zeCe+L$(6EGLpeiH?JCWIHF{xX7F`#722|oV#VwCIN%B!t6 zl!auCU$#bza~l0s?Vlr(^x>Jjfu+T!X+BW>tD)6jw{A6<@_h+YxygZl23A9Q)74+M zufu{@y=#%eWsE^Ow_^zt8TCuo-P{umAf2-bg;lhqM=!+xTLgZgX=#G0yj!I5c!M;2 zBtoe}_%*RG7lE(c#u0cst`3`*!&#?$(}V5v3U!Pd%gZ2eyBt=;;jNZEMz#*kR54z) zC(=V#I`#S~L30UVzAgdI{3jA#1avjw7;8LjT-iulVT(EyUp)SH5fACOe@mo8UmPqy0#WV+(e_`PT{cB_kDadbwH>YKfLa<9E)R-%IiO^Ba9K zu;>i1G{g;NX|CpHA@+?EFYMRITfKb~CyWYe8jw!uGCtxLha_5YR_M0F%z`a>@_Y^n z3yQoKwGT(ok)CJMMDU0)w_Ja@=~rvX>L%!z5sP|k`^D~56S%N2RMT^rfq5PG0ArH!@YYslGmq`c7h~sz*If5w z^`EbT%?V)wKqAk|UyF6?`^=N`W`Z@s65RP?blx6e@s!;o{M1l~xAN1is)0RLBQJ@T z1+1*C6CN(-Zs1lDA|k)fd>o6#Vlic|*tDj4OSxIFX9SH^)iP{C+>Ake`YAFZuSzs z$Uk=oDn3D8BW@O>wbnVYe9G>fWAAF*XIecaJSv3UyN*;-YZ#vC#;SSUx(%;q^KMS? zGNv+jL1c$*>!5DwFfD>&i@PCTFJ406ak}+UCGI(N9BVCAcf>1bgEul`q_T&6o@v|9 zJysvdH&-JdoIx*fiA+17=cbj}-Dintn{AImZe+1Sq-w4lGF&kb-oZDl)hee|$`p2_ z_T&LS&-!?U!TVP$;{F2HmzIGWc7O^lM^PwKP8iIWgIN7oEY`so;Mq22sNP4e^T*GbJ--qQ|@>+Q$V>QwA>U4uRMZ<+wka0ma~uSL$jYxUQKaa z+**T*Tnj02BaXk|+mDyRrP?g{@ppARr?^!4eO&96TW9@}sTNL6`A0dTdC!+=-L_+# z>k?~JTE;|6ysy(`8XEi6e_tg=8{Yd_I?oT}6mN4}2s{u+`?9SAb7M4gYdpSz+fiaJ-ay*+U50Fc1x;FLcf1c8&RN>PsIuCsLNZ8964b?DZ6Gut`8 zqp!{rBe$Y(KNbaE3&q#r73hPpQN3K8Dg*ruf5vuq2$eNdS|z!-4ms++tjyAkRsPXJ zqB4%Dm(nu5hkQAoO=>-C_@?u!Ws20c5ymr#Q&vZuX z;1^y;2xm{TbVmo+A(ZOw2RWd&s)Jehj9DwJj6op9cETW;Iu};u>9bl&7kLm~) zY+b*CFk7h~_^-{@%2w~NutGw)P>B`^pgXp$j+j`K_7zmy+HrWcK@E}gJz0!arA<4f z2sI#CdU@N}X}s=4sKZ&xL|gjkB6C&tz3j`AdI+Ha30tw3r54AETQ8~3A7gh!1Gn7q znRv`KTB>7Qz3XW7V;W6XpBto%!Bl=V2koEnZ`%7`Y6Y&?E}DHrR8=U`BY}n*UvZ6O z6_!`A0?oO~3bs7dQuD0BA|L~6+d-t2>k@rfbT+KV_W%dxgELtZOG$Pk4k7}O&e4K` z#Qy7Wbruy}LXAe=9gOywJ!;xQIY|l^leg*R@5LNqu z7&XDMs4iv54k@*sVU{!Jep_)8iVzb&X&LJPyf7}Bk^&B!Vo0-VT`DKAYd@wx*GXwk z$X9pUMsZ5HyV!Nb`3d~&<&418X|!F@wu$nhm<-^B$u>#)h8MQ=rR}3Yk8_zp_(gek zEs-FP@qUC%Y>_C9Ho`rqc}i>edb)1!N+a!v(ve}>>$~(Ba6GR&Df&SZYIt{fu&Y%^ zQi3`SW)%nB{QUYg7z#!*znsqKM<(%7%L_kLyF>=11fF)%r&U?YsokmD8oP$m#cnW9 zt_^OOM-N)65$W*ntdf28RaB^Zt1%!Q(|kfIbL2d){`)MvYM$XqJ4w5nt2;D*T?~)} z%4EQVk)iO7y1;jj9(@D7pUhlMqXYy)BQ@y~>A&#td~ePq8>=*hFA8h;8WS@394?z9 zm7w0z{1>6clX$x$mVJl$wl}s_B?S`pr;7_But2bri$7+GR%xC{$fcP$UX9>cDxd+DfHTJGSI(eJ-6TsxeWG+R^@Gdm zn^+tZ$>1_`sQ!;aOaRn#)nI3VG$H=QI4b1?1anCyEJpoy4Hl}%AnM-?;=faA(n5;E zLm}=*%lzHF>@7x3LUr}*W;smeA!gCB&AW2oq4UU?yMgnHtB7Pi!>9ralF?T6Ax>AvX@#R)^eL}<*LB&6Y zskVVPUoI>k_69yF07$52Ea`F@gC%enJ+3>Rrs`U9jbu7`BojH2p{9;%IhJN+&+}{w zhy3*Wb$T;ZPPqCJG2}%F*0;>QGBXG;(!qHx*}NuunKm&$LMYZ43}QnJBJujz88ZR` zgNmKg>ia*a`B1BEME>SHC)ID}K_c|A%KdJx)$6d*qPR|c)SjOu+8uBMh}61MjwiOu zPvJP3-z&>{-a0eSSe)QsgmoF#b)h!s(CNX(}#;e$_H@}s|5GY8;@&d4FW7^2i`e81V(4NmMM2-FZX ze%`i9hmCbzGdl!xABpIbMV^HJR75vjx9oI^Y<-VC_0`Zdr`RrK?i-4jWZ~S1jyGMR zO&Si;21(ic2%j`!FXGr3{bx9Y&ETLE><6ZgS*#f<7{074VwBy7x^sR350b86CKPJf zWBa=P_F@k&Z>sgU19hXHsa$z{*L=JC>-|a30Eu-ImDl50`lRkGsIeIq3-yX%hTga= zPMxn=TeOD}g@S-P?m znMccQOGyZfkfR3i%C+>~FXd{xtq*vwZa^2X4wdXc3N4VzhPHH(x2}O+#He$>O0WU+ zTqT!Arz5U9e8B?e6}3B?1_OU=MywjMI4{=E)FHU#CzH`se`ChG;tbsbx0%N_BeDLb zeCmGS$gb#NgPD!rdu1QZl{RiDpr%E5#Nhgx!r>W%avQ>ilCB87a%f6>ZAp8rgE#4O z+A#Tb0~a1bhEPPB2~R$V0!`KgI3$V=u9~BYosf&MrPAcYdkO~1$ln^0SMv#W(Cl5`L z?pNK*3JVptt^imo>z;3WmNTvGl!`*R=kHVb=90*2lp6B-`v~^QMuLv&d=(BW#Vh+! z5y>4uLU7paqv8b-TLb_dV*m{UkCbZ6XkjOfv2{TGl6XlJk)+sw7}+rFDX`2+y>8LH z47>S=vO2N72D_UPMJhqQL5`@uqzylu!CIkv203w?ZGbom#honQcgdB~;(crF;g>0b z2Xa_gW0Oh@3lK~PJy8hxIqX|{4RHRY9BTxGnZf47?-_PCPlsp5S5xkp(E*Fz(lMfPW268m=zK9fBhQ`6x>*ZZ4j}B{(M43uANoHj2 z4Tg_NmtVTTQ)&MUR_Jdw1t19@2j__dt`&B_ng z)iw^{u}FqC=50x)fEYRXxWeCzg;Af}$Baqrer|IcO>22;SmD)Ae*Df0eam$Df1j0A0>H3;?3|5sdk2F8cCEG?Je2s28XV@qt= z&@SJ8A*8hrfD8WviV#C!GeVK_SD#*bUOmJ*N%wXl4{twix>xIJz~ zqAq@?D6hYDWC2&gmJ34yNjGhTa?f3G7%yip?I_i6(jDp_2gY(uZ~QxzkCyMTGk zmxD_!CpOlGhboiW-#;GZ`m3}P6aPpTtl#MuksTY$`P%p4O#UG@Ihh?nAvE2uJGOj8 zZ7R`BRJ>VEl|JxFqI;I=6tVLEkVEAyBc{;y1p$Zcxg9=ev8{NE~HvnqA1 zXhN`Ze)B3pJ&Qfes z2-4&Blm<|gmAANrl)#W9)9>Q42H9|JU`kfUybGlSmKdE9gs$50vCyCtU^|vz4881d z3Xx-T6sFt>zY3mC`K6>-8hX6_H@;~`XDz(2@KO+dNpbvg&r-o4BLLEZiGZ`7HSvGW zj>=??0y@KpC2V1J^~4~N09;x~NQiO>KAKsm?(nEIMX4v1SQl1%w$8(@8k^(3dQHUR zpu#om4xrxW@N4fm2*7H($3eoyZXBS~uhJKgX7txp{6yg+fggvNV|U zJHNSUTzdF`ZUpd^Q?bB)B*wGM@M;<)d-bczqM3x}pZPI5nU$md+s+Tx|_E_kZs&{7h)fKUR53ac;} zDer&wEs!kuk~%>6bhFR^RuFI_bi~}^(G||wI@cZ zo*m_u>mVH0mpOlFuo}enJrvwldjgVY;X{vc3R3HQ z$_TT>Ei^&Jskqm@U$$Ig7!^Koq(u z^Ss#%+I`_q+h8$!foT4nb&FrIO7>g46tjHzdRZ#gN%W6Zaj?%=z!j9x*D%*|UI{o&XuX$&4^tqT2yka` zCaqv@$jexD0lu2IU~tW>`C0w-JGjIb!ZYVLQxh9!1-Scf%y-8;8nD9^^%VGuC9C%C zpJ(oSVo&uI;ZUT(x{@dwZKa5AAeEYb-1#Dvo&YdK+OBM<9r@N7&@36YQtTq$r3ri@ zciVr1$J%idelaX?_Kwq!7~yvhA5sL(c9`fp2!}X<@MkRS=+{V}a}#9TZpg+q1jPhe zI&;U*4A*|_j*ug}r2g%k;Ir&*XbG!I^mqco>AkKTNO&yJncT1CfT`B#9T@mId8!8t ziEO50IYF4rp{nn}+gl3P9Z9jYcxzH|ZXCRoHHmGc*;zu=JKmn_e0e=rw|->)mo>pv z&MCL=LWf5UobYxtX&f1xAIWU8_3kz#h4aM5?xpDD<2VnG5KcOi4m=D@s1AV!Ez#32 z_xGyvjsepLD<{kbx}BvAH=jW?oE!0Pi<)tj}0MjxDMaDYZLf zS?>9K+qG0dul`eX0un2RV#P^t)bR|}@l4m0AO;I0)jK{~yYVYpw~JSO8ZO=j zr8C0LY~-DMNy*HL!-~UT$;Uz=D_S+{m}ruXp2BD4AR5W1te}&(BQ=A_d^Q}ms}d^6 zP+4HYeCFX**7L=24Z(xvkNMGFXV15ab)y|cZVZ0uJ)MaOA>JUP6a3WbOM z^56aJILH$YziA14C29q<&G$_xx&A)h!HeFQ8uAisCjKE5j84!_D+S0aMr9r*0nfH; zPt%|7rJJRwIi$PxO)a&_1hG#&-Hs){wvD{p)Nmscuqe8}%|KOgs?i;mGub^dYzmCu z==L#LkbH-Lds294;>(_0bP^%&b|nX1!l;=q;%luucXQCw+#SD}CkY ztq0m&*MZHInvRNt3>xiQWPKu+-yIzt7JOcnf~9k6F9}sII20tE+m3JRmZJQ=$a79@ zAAEa1wh`v{@>%$iGrEVBNc(5!w-L1_O-xOzdmS3bPu9*Yj?n6QQ*87$bY326{r;MX z*Rc=1vLC6KgG$6DPH+l=Vkui7IriSTTnpDodF1V}B>D?}Sq40`)iF%r0hLT1~sB=lRCKB4;(GNW~%TiQPp{kj;3;zD);VV^` z;)32P$y=lcVSos({|=37x-qf3XAk>Ms)R?++4GI6IaUUronY*uzT!Iqa3tAvg} zUxzlerks`^#jne%>~*e!Tl`|hiU<1roe&EZ06GM78{kOezF(oH=LtW z4TKGx{$vwcGkCMRPXx$GnG6BYQIe`9OeXgtvVf%~4?R7-s?75bL<<4?{U30d;?ar* z#wc_;5AFhVnDqz++dVS91JwE>-J5T**XneuLYqv~skS({xMoR%>2UodK7Bfc*aCxc z4Mn~VpSBD1?!eFmCmz7bcHcI*VMo9gw?^izZd~mX&sAm?>_Z|~c&%>g6R|&-GtYiHowNw)QvCX7MDx954%Kgc)Fg8}x zB*DmAX7p&jA3^I$rqvHZ`@+;zAx9Y+%JPm7L+21zA-?sk(TE$ko$mWI2P1+SsoHS< zwX$>8iyI9SQ6O$%gY*Rm!c+}9eTo$F{zso8ALS2(lbt=`O}DFlO@%5MI;}x^*EyBVGpQ#p?vud}3Y$J^L3PT_Ll2UK}Sz_JNcLTmB1q z=eAY`M8w{IMU!+#NBPNE@_>j?Syh!FvmzV}l!BBm8JOA|P4%3<|I|l3vj#$&>V}w= zd9gV9f3i?u#G}+E{22#Mcn-{0C2KH-t!mhk5|Q`+hXN`Ln<;eI@i|sSE+!3q(i1ad zBWD;>_)wl?NL&QWHbMS~HTsQ0^aC(7?WnG+n|TK`cszLf@=@XTk?fv-{}v52TPS#0 zg9&{lpi)QNSLHdNQ~lt~AiCUh*l+ny*Yg@W+w`{$h0ySFAXMV9aNyM{IXjju9|E2^^pN33Q{pU#QbLbpIyY6Qt+WHezTy4WPJH& z#YEo4=ID!+MOIc8pe5|)78R*2^n&dK(1-4WMrFX!$%B=Yx8*a#0lncsA`9BYgR1@- z1(cd2&@iIb1_!fLa+Sj&ups@)Rt2`!8H0~r*`Tk!b70f|#|$ZhG*=uP6TeEFoX8Er zZ8ZTx8n8IVKN;$hJviyV0+h`ElF2@SB37DAC>WSPR#0ArnMEvH5pKA|EL&w==jEkW+zFD3mk_H@GwjT;bi#T?h+`suY;;fG*G*-=Wz3fox!eD zLGFq6Jqp$@ZO9fqPUPx9#?nuNf~eCwj6vGfM8d?xWI+T;mC^qV2k25(ct^z105o!f z+@6C4c~yV2t`!;e8nqKt&fO8B0K*uf`<1&K8L)R@+;D=8mVKaa>%N~8oWv(}2`5t) zKsuuY3k@L2(C|A13qPT##3_gKdqj1)4wzxYD2PQa!}Ul{O99owy3~ibQmW08LWt99 zo@b`QR3Thl8X5hEUGIyZz8adQpn+%;3t^P~a+(B2dkB#ue(lp=;TINkYKNnEv!#RcQ)1cS7UaD!Q?+oxD9%Xx$Un5T80*!mze%4&X{gLK1~wZqm-+m6Zs@n zBhq&A^xZ6U(9CxB#c)25^()YXX99`YyT4N~-3SF7Z38C4}uo9e1l zOmuYj`2nEUpx|YSC zpP+YDVzd8PA#SgW1(@@H{6i%+2&I7XR&hI)&MB2`9x&>$K4SoT?}RlcZR{c*@W44a{L`p>(GqCt>hO@ z?FuY5Hj5~8zp%0oL_`@|L#jqc!Go1ShTVB4otm?l0*{qLxbSx7N;E1>GaV=_cEi9cU)*L_3_$Q=Zp zn8tDjq29`U1IH=@Nq^pdbE(|6PfyhLb~>D&SvVL6kY9+Cuq(q4jLoC@|A*lK6T}1H zL}2b^sjAgM<_A@tfp_UXE_+2ah~sA=>85*=MPcwtw(@C-+8nU|0G5Acl*;+Jxr!C9 zWf~YLB}JU{D7apI==?B<|VaExfhb^}hoB z_Uhr;?m%Wiv5|7GJ2%JvY(O{$3DeF%bn0rT= zZp1lkhhXe~KREf=C#}^0h^D`QnP&kK^8C;K*iZf2RDb6RVRx`QylF4n9iLVP;1G)> z78LYmj<+vKZ65^%OQKzziNEmrAyzIT{)#1>E;&uv-|!x6gMf&)RaG@iq}8@~2o63G z5v~L@R`NgQUlRJ=mo6R*nYn6f!#-8ABlu9~`+Lo%4?b2jx)&_OnO{uu&CGoWQj$I$ z@&a}R;2f-ChmAm*ZkhAtd10AXm2cCRZ7_pSPK1`*SpWE9;eOOrRHhW(cv0FXQL48H z_xYqiQ1^+BpiS5h_rtnf$3L@tEWurAB_5R0x_&P-6eZGR2qz`m?2&voo@ViZ0I`#n zx(K#}bw(JST=aBw%O5OoYFM-h!<2{%Oz68ZY5egFwi9ud1Fc6V zh&VjZlZGfkZf55G1jEBRU5}@q)#tu<;!1b%H6%84;pIGkS{*PmnuH=m6WqcYYC{ot z^zKyLz}x0go}WZxDm4n)qqoSY`35s@UyS*DT;F51bK(EvDi%Ha=f_7hjrZh=i+RR8k&h)n9;Ork{ca{8X-Io3q36MFsjLDs?G%T*?F>)+nx)VEQe30`CaTWR+(V}PQ%R&)EAtOZ;vxT1u<+z*%iH)7QQcOXKJkXKQm^8New z)%@YYf&vvk%{Yj@d%p7$5k7+MIwV8>U8i?mc3GJc0_YQJ<|j3uHYKwcrd}IYq1l?$ zR)fKTGsnZlO$h&m?lsWP`&nJ#pO?&~3{l?8Kt=SGpYOS1efnobqQBaNtZCe~4qD-a zN7gY?N}DhSd`$w`I>6^Ke*=vXJkr)ykKOcDV94}#V3_WYhf``s%!QINSX zmKLjKi?#EcxzYa!A#jR*@I|z1%uUby%Gdys3esSa<- zC{foL13-+7V*{oH28-*sA*0%FAgAl%@%pIn1?Dl}Rs;qD!+UlJ0YNj0Fbevam<_wa zfQNef%_87x%Swv#_W&DsyG3uSC|ak+myrq2BeR=J8TH=Ly&X4biAzusN3UMvRIZTp za&8f~n*m((X5oAfS7yDYc_*7Lpq(2%_kN%~?3M0mI^>%%OR9j-4`iV~jS6#D1kyfZ zb_(O0MY&K0F#*QRUy1HtH~4fEWt4zw~q;<)5wun@z+_`5uV zdHJu8dW88K%LxJfqP%+aD?YyQS{48aX0wkRx>&5!J}mK)X?n||ot)zAr^;!CS90Xc z-BkZ;>-bIVD^ZEfn7+YDKJ(*BLWIzx&)N6RJ+-h~*+y1}A7b(pbg8u)&yJ_(8P57*ee?7Ukx$*ssF<9~U(rx^{rq+fHsSKL#`w@aPWq#^U(%o7md2|U zcm=nQ!@Xsm^PkIURk{~_KPw!Y%d={i-t0GbCE$%riDfG0sHr5p88P0+Bb8AHL|Tom z*r-QT#5c%<9oj8_)9kMXK-eSg3K~Z=M%-yRxw-oQIb%%mUb0x7X&Nw8b6k^?JTgH-RK`qtpg@VI;TAwHZMiTKszr1*C7mo?l+WUq#4 z(1hvWdH!M1eL?S2cV@$VB}8NL^JBIGd&Yc`62Q>y1s%<5q=+ofxALZA`e=9$W#3qz zyz!C<>s3H3&?r%VBtMnlGoh%0i~VyGs89W*!cVA8a@QyNM8P9mt6DdVe|&Tfzqq_jVJqjIwN>gP>=0 zFk6+6i#3yjG~U_FM?+7Hjo*&krB~XT2Q<T2%UioIjQ z{4N*f&f0sRAvKY=$z^%3vzL0nqkGiY^tOA27um=_2W1JQaqCc~7k3La8N^tBmM*hW z^0^VqLv1C7cPrc)gc4I6Lr>}(DoX-};ahV|4oP7q&9sZ8;(mf+>}*e3=ZQ;~&$7OLKny{5hnb z5p$pQGb^j3AJdUB{WDhBfDq3Y*2P%IZ%!x}Z>8a@!-TTN3Gv%JE3I0J&IFb6MTU{K z!p?4#;tcOv?EwhG3D=14V%~n@q?BpFQLcu0m*Mp!)>#)SpyB-kWKr(Iy*1Hiq}=0S zOXT_(8Hi1r?;(fe&0;d5uYc{}AllY%;YZ@}Dt25WhS%lkAqJPvp}I*N(Oi#g6vLK> zj_&JIlRAqf_5sONvd-!@RP*iXujCv0%bKF4k1JBm@j2XqQhFDE!e)O}v!hJnJgo}u zExW&kxIYT;4ols+sb`akws3%oV={KYN7h!-dXY*_Q=UssaI{bL* zDe=&^{|zgnBqR^vHv9ogqU7E0FtOCq;pd}Hi`Oc8SlSL{V`I%aLN4bub>g6_Dp$%~?gtL5t!TMu4vp_d znZ~(uEz#!dhBpa^4r`Om^OgcN57l)QYTx-kuI?R6JH5>&L?(Kno2E9KuU0UQCvZP0 z-^yBRwNOvmtV@ND`vLJa3fWtZ)Com$9aiz-?ziSNl+F5E2!nX=nl~A6B9mpmN(BVG zl$N=MY2-?X_EICNlml*ZPbpSzNTx83ZUq#?%1oIU8~|jb_^G&GUv4Vjn52LAr}j5h z{6}tTB6~>v$2faWxO38uOL8Q_;XSP44^ZInFdS^w&D>Js>pb}fVs}BqYVg({#trgIzKH=vNQ z#GD4YV2G*qxNHP>^Ui-*EZ|vb!PoPT8e{cR$tL+cM)@`zieChJ&B1odVaAv20R;tR zJU&_~K1ni8*zJc;w6GY2KqJn2=|-uf6-aGS|NR$ay5gd0}##hH8BUt-={dtr~b4P@EGgK!SwVKR79KZ9OuO2+XX-0oPPa*55 zr>bhSDS=2&wjCME$P}{5Y6hQMxLeHnDX0|vsd+BBwzj$^Ljy=X^2?L)JP|@#2svO$ zs7JTHqf={5J&GoBtLC%>Dl|Wd>t~OnbjhXsTQGZRWHWu(q)Q~l{L2wGcz3q4` z=!{$0Hy8ya`mOERV4r~(qVO5zk%>gG!*B`kh+B9*hA(kM2=I2}2H>RUx)d;%@LIHLFs}bFu2J~)-+6=+N?JWu#1XDbUT%3 zJEwB%aNOew(8gA%G*vY<%{C>_!=5<23GONmaRR3&Vn;2Yr|op3nmMI#BYKdQ5>}^L zvyad+A;UTla)hyqKBJ^lqqhX71L}`RXpy+k5mH|0DmV*6czv=!w0?tZ1I{vf@sKI5 zCXqFhKbDpBA}~l@p1{8Fx;&htKi8)hRre(XVFcePFcouK#>(?a+99@*MR_bk>mf$< zJjGvUVLE^@%~RJB?Zbk%Pd^jHOcILY1Qq*a6lS9rPBuvy41{lgq%~01V@bf#vmrmD zvFu}qHW#EkxE#JMeTOZ^l$1=RA&+zV|HsE^YKfAYf|G^(8~ke$NjSt&u|LQ@D3$y{ zlY5b5otZ*1WFfS`*#tkAg^&divA!9&8Zlcu%GZSWe5*cD!c+{pzSv6M_;l$mEzrhp zg257*vQVo+0>LI-D?##t2R{51=We@6BEoMCfF0k;rG@KbW{ZVBgQvyJ{m$=No?lD- z?<&RU!TpOxBS$uL3gZ}??Lf#W++V^poNu32wp8BMe$#1iD*CY4sHGO&qqXhzMe77= zNWGuD?9U)zX|w(8Z;0w``7dvlGAQz+gB|{e3@j1HKUR`|Vg>GpXO?vJFM(<{j~s5s zZ|G?X4dj*w>I)$jrN}mn6pC5lO#k`UKnO`EGK`C;6UX&&HuG(+7&a_n`~X_bc5fvh z192AM)$b0sELXHGZ(i;{Ep0UoEcV_`e+tZkY*1G;gE~aYl*+kY~c0yynC>*QO+ zW8e`#0v|$|t_m6lc!Of9hKH*jr=Y=dm5l?FDp*P>vpUEol=+JaL70*Uvzd~ADj}9& zoTkV!3NBWEXIwsnfP~%)5TXCIAS*09Lt~O0oZmaotiUH-2tLVM@w z`SZhEbx}jGCO_ca@5$q1yZ{i)11ev}+RzF7*KnUbCz*1eqFMsN9NrCR2U&QLtpB>8 zpg>Kd5<=3({Xg##4PB55KI{)KfJ}C7=yy#^!s`sfK7Q;&P2$k{QC(gAf{8gZO!#7XoM6i>?VMV@1)sB>7lS^_thA1 z3A11!-eY}wXX?nZ{yP%X=uq3w-ykD3zBDxq-#4}2B$g%gRH^iqB>Sl6C2hnsZX-%w zHbvxwlkBnje*_dce}0n2(UQKjg-D;yZVc|B=T2kLXvH*sw!#S#iP9a4<&Jt zAsz|QdK6dYyra_-^cm&SH0?3WGgfP>bG6HZ-#B_?FZ8~`F}K;i4_lK$LizSozm3UP zly7H?8p|P0bdScsYEZ;n6eWXNz5!S2|T#! zN1>)p8i!VC>P6;~{rzb}<}9vT=}@f^&||390=qDR&l1FFDUN>Os38S^2!#f#z^~T^ zOfL%;@;DJgzz#+Yuq1g=0;296QA~NCk-T1p*?ec04 zkyH$oTPkqiPW?+$?dbV@tVAm0=(59XfxJku^4m975~7^yG%a@#g>Y&pE2m>MWJ8w> zA_s96gFY%DF9xoTYP#)F5R6!3*nn>=Cj(buL>-x~;pQpRjIRXGCTl5g_DGj)IP(`A z(tkE8z*MW=3JVxNu?jM3{e(r#OwnSG_|vm%(i5tX4sNE*1m6MPylYtix}GjS;W`slysx5Gw=a7Oo-O7$AM1iRZUAtA)#x$?@D2wHuw3A!{2k}`b ze}@VRD2a~vq#|V7NIDb2hkvsvBVyS8?sze}wO*Z%*PqlxV=#=|#nt(%)qP-KAO`p| z*FK)5!KU?&CTKC-2ljQ+h)@^;r{k<9ehHHf1$gl6-_p?* zwu-fHbNOq}n7|VV#NiNOpoic2qeT_3q^|y~#!!X^4iMR%UaEDK2vY9K=<~KiR^3_j z&{YC}D?UdMlY=%0Eh9DU@~RVj`N|X*6I_>0T#e=KqsCceB9@hbv0j}@pJKAkRD|4=kog!br=mTCGg(kkCu3W z4B5XA;RlD5bO`VNY(_?6K`=aU;4JzDgRJ=NXzJ&I&QPK(u&4t?!S||NY_p zdB^{$L#+u-uhXIX|415+DJUwEtD0F|I?!0t4XQV?)oO;~DVtp+gK+M!FwF zCZ|yJ^Ka&~a^{|p(TLNqniH6D@uIgs>U!`Q6=#9f2NK=z{%$fF zJ;Lg6l>O~BB;iN!-lYQY0jEd)s8~1Sl2w_m!>-mVy6|{W|@tP2Iaq0ZPgn&FsJae&PTRjl-I|z5{&uG#N$z zzI6j|Dj&q7ddW{34w{xx{u7L@b{n6 zx&2%E9Bad+$&>*uy-=@NcL59Yv%!DuDWRvNN>Q>T30@ z&Q6J)?d@&dfUn9#u)X2kvrjBqYAnE*u^@{^!94k%Ew5d;M)xIIXyt z8JAjQITxTq-)d{Ckp~8lVwxi(QV2yyMNwT{UD50NlSftsf7cEFd!6avB?cjTYOXwc z>_bJu-v(1-=JQK5{3{0_Xr$8#2;4o))x_ZSWfs>K36?MkEaNKhUIW|=P?Gl%eGc4&Va{Wiz*>9rGH z2>4S{@TZBiCUz|myiXrXz`)p$jeM0!Ao>%m2oD1@L{9h!1_tJ7$Li@EH)av3CV65@yvSKa5#NC#MFOOT`ov7OFlj%R46Ie!_$>f^DiLP5-8(I44Ik%o|2X zKGTIzO|>@OBMA|CU*T0pV;`D;MC z`oj!>an?KT*##wTq{iIwbm}C`|Jcn>{#5Ha7x-pqqy5*wy>`?N+U%0rq?UVH^FW6n?3yXSd7|d-cCsra<0E-RRyvg}` zxC!>uu4|{zlxQ3(a;`YygnS=Q0_}OmQ>^qisNXnYU^<0jKt7*CkkLc<+grM9(6wXV{5;j zph`KU7oM;!|KX0CiEy!RE%8i}i8jeJPu%}#-gJp6?*~l}iQYt)(f;{DQ6L(bTv42{ zsZojao{6;m8*`4Omml?lxUB{=#i?LE9VyY^!oZ|1e@%RkXE!5|T+90uId{H86|_La z-g&l6mqKzI0BmsP`_YL{W$U$*6-SKTMFX%R%RWfDV4Sjb;#)J?%^1gl77s7bAF;z4 zixu7l5nL1RSwcTQ!4te%k8<~p?*wnoh-E6k+^p=!emCnpOM!rFh8HYpcG()kn+K-$ z*RoW{9~R-Xs5sAW`Kv_xqA$8XBh??@Ma{`XP2lBvMY&h0>YoFYAc2%V&hs?VWbo!nUnpmIS_|+0PMG))F?_b=g9U9Q*jG4n% zO;R`P`D4rToKG9NySp0?Ppj6<2>=&Jz=u}623Ar^0qtd=`^_+)wS9M@z<3>?K1PEz zCHYAo2CTgduQd<^|FpmXFwf;;GrbKs#;^w6=f!%sbk;r}oUGFo_||AbV$aol?jK*~ zxBSpkK(r#cFMy>Q!d>0eqv8o``aZAv-NkzPRm54bhq{Hkd7Eo>(^n_=rt|1Z(7fL-mRmYwZ zVGRFq`^j!Pc-y1kMR^z7o49&(2`SEqtgYtxK|2TVoEHF)RrkO|2$4Uf=dIJZj>Fw2 z&sE)6P$J2f;jJ2Zm$&%M$`Hgh`%h) zOu2K&yvtriPM;3t4X|VH3FaPf^I9Qc=oHZ^c`8}BzcDx{t&tfTCY-&lTNiu%MpXU{ zZ<4AlYYdLiL}$}eZaCg9$0&HLVjsh1Em4BoE%7=l021nx z&CUTZnx~F8`T+m=2m~DHh{Uabdu{XZuyvoUsX>L0vs9tye*Ey+!Q>`v*FcPYWN3{g z!wkEXm3vvLmc8^VsT+{DSmNAC$|Lr0I>&JdpOPnuAengO?ECGCFTQfgtS_cak(_F6WfmpmDg2=;hJ zQU6}=>&sRQD46QAn4#+0cHn~9xdggePKnCOL-2n5_yIIZtEPe{37U*1{jR{z07M~B z@E|3;&a3#0|J&J%MDQ{b`uUxfLU!9D; zGu|Rdt?U`0YORvCX0ny=w($1e55(DzFSD@f=D&YL;oZOH!_p2{_?W=DiZ<9;gMj!u zb_V-ZbL_10_jh{fG;6b~aCx@MH&dxb?V(h(Q|ytmMFX^%*UoP;Iz_O>gC5(mLt|Or z0D7^JYr%;`goLVx2Kl?Y7HehQ&v3%KFo5ZrwZ|Hfgb4>A$_2C^0qTHE^ZpypXQ8Z| zPmw*gMajMkxI5|TrG&Twe+Q#Y2=Votp!F0!sKY=*&-2W-i^SAw{`R8z!Eqfi&Kirx>^@?Q7C z8x6F%_}aUuS;&BqD$9h&yhP=|oRar>WUe`Bej#c4PsI4-=&#M8%JSXw$*yNC_Jt=BZ)eFVLD5dBqm)ntcNViU|^!~(gY;cx|D~i8@>6Wp*d+;0=#x&_1_RkIXajHt3qFD!F7xNpLAKlG5ZKHV^O4 z{YhDNWcXq#MC`|T{4qtI?M-4&k))Y|tCvE3n&mfPrc!3P{YwSUh`@05toZJ-%%I@B zOqhJG^Ut*e(7k>L19AENK<&(dlc`|(6lKWeWvsT9Ef4W4=flGM{O^LE*Ap6gKawkH z4R675j!^`JVYwax=)k*+rRJR%g$7O=P0U z^FC7xZmQp|eii5NTLgEbDox%NDsSj-{hQu-m*+4OxIR}pQAwbvY7%QMsWa+cy`D9y z>KUj|E^{6mkYjxDTv-{!E8PE#=;;4M=e^xsXg~w3+g09dX*|ICKwwA|L_bMl+h4$( zgVNnPvvB4cfpUp}yFg)9ULJvST?~kA*7p$^6fDQx@@Ed9X zbveK}bql0|jVoV>yiO9AZUF`R0gy*+#^ku~uE4_qNIN^wiFyx!3N?>@VJqNY9fJ(*0)&Ak;5P!O^1C1^+yhv8698dB!*ImV zzptDdbU*2@_W?ip+`>W*N|W#|s;-!+y|bIU@)XJ5@8Y7jo*-`g!OAnTrXPvf{VjD? zV;OF}xRA7qmgNSUM{1qlU`6g+EOjuOJYPjKQ^rd0Qlaxc;)D0AnR^MgYm4%OETi>0 zqrI`7N!rMj2>@>pS?}n%2!+3DGWui16lm2bKyaCo`7oLJ+k{Eo%Uh}3T)2GaQ33^K zBQ0hlJ7!WMYRRxwRkF+5`Gj0PJ$H$dZf+i#IDop+yhs~AoK)3wPhX85-}F=2lTn@v zy$X$XHB-IQedB#4tMzfzQMf+saUAoDZ1yH(2af+4g~3uQKpGB~S~Wer*Zb|*&)<Hub1{$rAuhJ&KvBJdnV7h4^nog zqpi%^-mvzJExng|r229H$-i+nO>E1)Z;)hbpPc-4_snf>c2<>WQeUhS#JPAj<6hux z;nhtM0CmfB{f^ojMj-Bt!`J~XOT<=W zVwLrIpM0nqH&s;`2Z1gV`|~+n>7t?Ud>^<~)Z;D2l5wfB#4X!kGvnc5AAx8KG9*=^ z8@A0_EfX&M7_n}UBO0dmno*{q?A1OTDHMhEqQV%aTWr_%u_=l{vsZa6YdDVortzyr8wfhE`ubW-kvWEbL`x7XS8{U8GvToUvy7j@Y7>dCj@0y|T|wqk4q>^Nq^ z^<(85yz8jToYeCR-kQi(|6~iQ)e6qRP7ISibHp#GLC*W|H*WmPb3&M`CQItok|>^> zqkTJrfw4ZvkQKugK(xD`?*pRl z4-hpUF1#O{`;3htTp&=|;>78rBjenPzfDKj-qMpCFss z6`Sh=`_eXcj*w!H>}vXpp;3k!V#GIcy#Wcm| z0FBeuNgmhpFpuS)Az=6H1=jv+5F7;!J5d9yKz|J|YB`k@JdOZ;7`G`BTObye_TT0y zc%PI()V?Z?_$k30C z9sn&8MA$_Q4Xr<$Dpv?4)?C@ArOq1F%*D3Q^suXsMU*s}^mJ>>x@-uGAG zp8JL=m%nJ`L5{vNmh&2HwAgfWW4~owe5cP+f$L6iB8Zz;i+Ja3Q_j!8!z>p&ByIIl zZt`8y>;m`^2{QFtc5XJlk2HJe(ucDNx^?1GtPzpvLBXz>uzzDr@VF1S!#aRltcWI` zpoVFomZ+pKp8U$CdbGem&knObgx|}i81i=%_J;0lmUTeci)*j#yp^Q@H3ebl;CIfw zB>TZYG0G{>`#+wiG6b@&5!2Q3C}5V*3CtXly{>oA(9oFU`c`b_f%%pNTj%=0usk$} zsArb)NSF9;@el`PKU_)~v`bbc3sCM*=zuW&<{>*n`EJt9rsA6c+sXqyS9R@-6bCq? zRgHpyiIOspfn_k}nyO6zkNt|}OF1eI)vC$#=jh|3Ujwe!3};ECLEo(<_)oz~%SRjr zV8uiDR{$G944yaW{)3*k2W4-3fAbw{H@@L!oSxTJ%EMI(28c#iCtE~@>zt$@)_0LO zC$Z+RV%53*v`qvt-7seQs%m^vnMslKJNZR(&Yf)0RQ>64_X4h zoX{w;KR-R~jy;K+h0*7GQKYP&)h}Jf$-I6Z2wgGze_gS-od*c)O@1;wKjddvCi?x^ z1P{E9g7st**r3w&d3*#_pXmz3r2%>I=a(MufvWDh(ZYmDWD+|I?E9`Z;`;a@dH>Sy zDbo>O^Q)^2vOBZcO7qTzh|d1*2?n15Fabh*?mv44?mjuQe!?&0?R(M>9x-E4)Y0rv zTGST@w)RKmkwydJoo;6-vE;^LL4ouP*}XeNav;4jM$DsDrmJsgII-2^f)SGPiD64@ z(h-VT##BsrM80H4-zOA7Y4t@%iiAvIo-r6GQS*n8R3^s9y8}WkOwxNy7n^VUnmw+^ zQesJrLs$)r%j~xXOFT9h&;!=86Rma+4q{k$qHlX>Qy~2T2JiYez@s5D49N;f0c4Ws ztXO89@1%b3elE|E{~&C^-?p{SH+IcW=cvjO+pCTZ%VLjR(E5&2Lj10XQ;HzMAiXyz zFY^?rV#AoM4)^yzn42pdJc0pPxO(r0Vq5kY0+?g!957g3K!auc2n?18sb70%f3JCB zqGQc=qW>4G;1&=C?bz@X1J9iug`6Hr5^Tr?04!kwz`SPBWettsgO$~N$;0(aEio_Y z=8%Xp`~tS?4O8-`@gT1SJ%K6Uk_Fb7;^fivi6iAOp63rr^9)Jz4au%g?*u2s6kf|8 z-_ck&s#E^wVMmVAA}DfJysYaSU`xcbSYI<2hk>C$nC#}v?T67v;Egd(m4dcUAq88Y z234+_oKBoF#C=Ui>m4F=1yJ1fJj5V}G;ouB>o$Dglw#XSfJAK5k`<>5IY(Rr2XGGn zaKisd_@CZCt5up_T zhh)$qL{bk(DZk=px&M6jNia(38+!e4HN>_BfY({xLsEn{mG$Q&P#B=fJDgEC=g4+z z%p*-%d^4oW#~EJz_3bnkhV~GpF_SmOZ3JxY_Zd79G?-GtCmNgRXAkYuebg{?R{#BA z%8QdHrj=xw3j091zw+zvL1nUPIUf1Eub>A5=fg| z3liW9u$3gc{}V899mOFw>D#Fc)Lh_UdSCVFjT(N7+DPUGQ5HYN$U~)k_jEm*3XACe zMVapJOw*S-Zpm_z)CEIQPMW8$h_XV~U*LgUnfT5;acsg~{olhgZta~P!Q^)1GxRy! z+;VHIxv2GK(kZ!cB8ZOY96$X4!;-MB8tX$TK9AbaUGJuDXuo1tXHXV@{gp)lI2Qpg zcnd>ce63Lcd)>9+IRr2I6koKyIXin3K-d%c9CtaV-hmayB;;L{#f0SR>k(0OUTV+8 ziha`zS~=ZKC^H0`q!V*KE}4ZAu=8a>?^(?L)k&oila+TIIetAl&!?4H&QZ1#mA6=i z^UWiQjk4ocF8%Y!blNP1Dpr(dsGR2_vud7jgl%Ox`Ii=qFi}Bg^cCvv4V15(8oXnt zPr;5D>mb;}dH2WZ2d15+Q_KWv_Doa6Pd)!V%fu%qDhLywgm+;Fij7P9y8<&)Q0M?l z;VWCjFhDt|?9LnfzYD3o7Hte)5VeyhoqjQ2kdx`OmGE zOwEl(#x*P^$#0cn2r^U8e1ZlXn1syuIzXA57p&vZ&;#4985jQoS!r+PY*Tko z$O#7}o5;vW@N+x$l}s6l32 zBmc}c_IWjN)NbPC@AH1jt&+YCWrPk@<$lh3{9Y=~?-MdZlQSx1iI*b~ z>&?!$f9@zpG|cROSbDfy0>!NdXpif-r*4=ZkqI0idoIC(moqwKYEiuTDkTcM*ZvT= zk_t90dtJLd)5ydJ&NB1U1zMH4us8M1ak~!a#l38!#=ji?>wZO``+dc?O48tUUw%=p z;a8l!T{>eY*;TCX{|QcLq(a)5t9#Lr@L3u;2kj2KJ91vev#P!_sOFo|J*g1=7 z|H{w&Bt@X~Ki(dFT4xi!cftXxILV2{@43voR|fXlw}*1SQ%)S(y^BxZoi;j)i&unr zH1XY%DR7!szzU6&iy2S0uO1F#=Y6CJbj;}a9%}WapE1}0flQ$U9wtaf9;D#PP&KNs zz?72Sr>@HCwjS!bnm@b_Yf z%=8?qm^19FI<*q=+0pBgV6lpHNM(NCvIIo{nd`RQEjK1l!6KdUP2W{>2ep5$VjXVaDVg4^S! z=i02$Fly-br9S*8k#Z5^*s6md$!l)1#94krGA;Wn)H~im_Q;LSc!F}NrR<^JRSO+3 zqW?QBG13tzD68Av*~tU^Lo&>4lz?`SzUC(cJ`L^=gzDsAcNBLI3=j7}#fJXH3kaSb znpeIQlB^`Xw7Kup&Y!Yid3eFa3c&$X7wv*OO7QsC7uJ$2L+421=Td{G&)Wj z=!q{T8Xx!t%mfkL>)7pee$at|K>Vc!XZJ zzPGZ@XarPaQI*Lljms0Xt}oqt+!rjHEcH(GF6#B%?#qj_Sh1Q{&>o_1_Ocp|4n8&A z+lcaHb0fouXa9+D#V09il1q8>F*aQPiiMNXL_khgd-h>5Wc)gEd@}O>hu(peM=5)@ zCvL0NR7W03)-`R_ZPE*}CeK8{pA1&_uM1&?RyO{3?y-JxGthWExP0WYo@0?+iWT@P ziPs*S1Y!U*sSvq-2*;73f>Xmtg+WIUpePuCJq9RV`l259@VRkY>plnRxyvcnb1o0n z8#Jenfs0x0=ZkqWMy>0e;W=)!`)~HzrSYDQy{ThQ-V$`<)e$g_F`lqET(k}zs-L=Q zy_#5hx5Mk`c(5OGs9nIXr+pZkGU}awud=6Abmn20wYA4^dQ@O(ESzyOglpC1@jwth z)uc;gJHMA~=Y5y;(xSBs)3_MpWj>miRMI1yl*f7fT6}YRH&Z0v-jWk(uxZ$l_68QWE=c%AhR&G2yP~qw6c^%#OZF8OJay&=SaW#-uI-D?A>GJMN2ec&( zuoZ~gmO0~~Zs5`dnTn-P_P5P5lxp*0N7Rk!;F z8=0|a@ieehgq^kC^?Fns7Sq)g7)%Pp+z2RM+?nng9EGuymMr}Cnbo@JzWg!TgL*Sy ztyx}_$2&_=5i_r&l|P_I(WiTt|NDhTu%XhJJ6eQw(J{|6j^c}rjYrZjiYy4l{WGT` z3j}AUB{!+0+!%tjyjp_nRdtmNmziAJ9?Qa4B(qBqr*3SkAmT=DWZi89I7lp9qk_EO2si?y9t@j+j+~%{-?HU8m_B0PbrkE3Y28j2a&Q(!2sbs==W2KIB5q z_)RtJDiSJ)G$H~bAPs_)ASn#pNas+}Fti9r$IvO=-7O+DlmY|NA|)-| z@U8LebAH=%_UCoUA9}sK>s|50{oK#Js7f;T)*Nr4GmczaLHlv`a7!p+i2B;gn7c|+ z^@xnzM3NMY0MgF>-niC<>8;c!E0;^s8{79M)Xt{Ml{}%YALbh6EWBI=+^S8Hn6M`9 zm4OtcrBfms3s_)hb)>VZOugYU=do+)sZy}^y>zE85643*?CD?rm+l_hE)oF_^GF_5 zou9;`+g{UIOAv2&hBwBPI{ZVe+ubAL8!!5vSU&~K5ygqx{n_gL6N>)Ei7ECZ+g&@G zeiah@;dq8y2R3|mY5_7_UGJ~C4C{su=5w#@>eeaZ-itV>XTP(7!TRFQ8FB{T<@_rO zaiF|mw5!bF_mG*=aYO;Y*NRstt@Js|!A+3$pS=KQX2`Hi&TD!n9=IDiDREu76VC(( z-Q=QU{fwm_`p*-x`oEuNA5i$lKFTeN##4DIL+b}croc=_u*qS>s3ZGu<+P(WF)ZY1 zcBX!YI>qy;q+QL{UDGOIVzQ<}`HLO8x(f5^#3P;EE-RaMyp--&uL2XYikx>&g(s0 zOspdj-u$n?#lCUc%F6Y4auwiS>P$I_Jwo%opxx5ynp=FuR|7-CdCQP@fb)I`IPT~qC4Mw>1{j3 zBYB!3(3dQ?7*pyp2A>Sm-4rr6-wzCzRNFl^-t^L%YrVkT$R8E*%Ab3;ID?2CJ)5M_ zbRMEX(HGs|Ux8VNb{f|Z#5D*VN`}_F?;i@jGaJG#9HqT*$XOS$nKx6^K242aR<x}mOB&x9|Vu0lES5}QX?M=2{+n5Sqr{W z`8;9Hdt@o$JSnW=JAyOI42@|$GItdWQ zR^QB&NHWxAH{~8Jv+OzzyLRCe z%_}}-R`$E1;zJSl_>HbT@U`H-DD|S4BC;y!>qmZ*F$9z(_D87hZ#N7Z17xo0G5Zzu zxed>JYn{u?58at76auMZd}mE*cXW$v*Phiy2)CSeV|HfcvFrv1ytt2{vi}Rw_^_ zp6OO0_0Ik;M+g*sR(Fp_uKVSK>oUMSkt!U=rtf+I$!qDwbqYeffb3QXm7{nj8av66 zNvLqz&?JfG>4<;VTvxiIf!>-d2{M+Kb}$L^3isHH6futdw05VHEW3@Cf`x%g`dbBD z>i&ed3={?LunX_)tr(0C)k3g7Dd5*g0zx!P7hbH6_8xip+| z`}NLSwWa#?Y=60@`xMP}Cx~F}A&PT76n)%r+=eA^nC?ze`;oy_+ptJbMA6+l(V7o) zt*&j!T|BZXKN7#JyUY-~b(MWSh)Ep&{0!%$%14R@f1S3l+ftjB04ASgp%+AF2sP}a z4#q7Kw4a81b#g+8l)I7qfb~Tpuzy+Tb>#ynr`4BmmTzKChlb~H$L@_>bi~W;ZSd1p zGtZAMrDO~2dyVCqF|zeWe9Gx9@xdfGBq!E}sRM!hSJs@Rt4#xroA-)8nAFF{h1^66XxtMp(+~i@}M9PrLXBFPyh5!<@*gyZ1w5Hp8}ss>k&{ zW4IA_-(*MlrTas_sv__xUUrV$sl+a3u_y6R8hJmLlRsCKbMRGh=>|kqsTKg;`9GPL#ZeG zYqyfi?&mi;2{NRRdTX2*^!yC&iS3G-i|P3lWYkNU5)v)NF1pvDtgFpzOsn`b%Pen4 z{|SSw;?GWbB5V>EvGLJ@gay>4u7H}HvAJudZg{26Fn4|cdnIS4et28B-5kWdsg

    x8(VYW#9+N$aatIV#6JGdJAvp31rq7urYi+PRN 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 literal 0 HcmV?d00001 diff --git a/doc/modules/ROOT/assets/images/openems-ui-login.png b/doc/modules/ROOT/assets/images/openems-ui-login.png index eb206885374e70cb7e6ddd4030c25bc71f0cb684..74a6549872c4294699fc69fb5fad89801ffabdb6 100644 GIT binary patch literal 10972 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 W$bp&XX=4?sIEu1rfGTOTkpBT}9+2(; literal 11993 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

    V?J0}O9|CfeU;;om< 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 btn:[OpenEMS Modbus Devices] and press btn:[Next >] +. Select btn:[OpenEMS Templates] -> btn:[OpenEMS Modbus Device] and press btn:[Next >] + TIP: If the templates are missing, restart the IDE + @@ -40,14 +40,9 @@ image::eclipse-new-openems-modbus-device.png[Creating an OpenEMS Modbus Devices + NOTE: The project name is used as the folder name in OpenEMS source directory. The naming is up to you, but it is good practice to keep the name lower case and use something like *io.openems.[edge/backend].[purpose/nature].[implementation]*. For the simulated meter `io.openems.edge.meter.simulated` is a good choice. + -.Naming an OpenEMS Modbus Devices Bundle in Eclipse IDE +.Naming an OpenEMS Modbus Device Bundle in Eclipse IDE image::eclipse-new-osgi-provider-simulatedmeter.png[Naming an OpenEMS Modbus Devices Bundle in Eclipse IDE] -. Don't Create a `module-info.java` and press btn:[Don't Create] -+ -.Java settings for an OpenEMS Modbus Devices Bundle in Eclipse IDE -image::eclipse-new-osgi-provider-simulatedmeter-final.png[Java settings for an OpenEMS Modbus Devices Bundle in Eclipse IDE] - . The assistant closes and you can see your new bundle. === Define Bundle dependencies @@ -57,7 +52,7 @@ OSGi Bundles can be dependent on certain other Bundles. This information needs t . Select the component directory btn:[src] -> btn:[io.openems.edge.meter.simulated] + .New simulated meter OpenEMS Modbus Devices Bundle in Eclipse IDE -image::eclipse-new-simulatedmeter-bundle.png[New simulated meter OpenEMS Modbus Devices Bundle in Eclipse IDE] +image::eclipse-new-simulatedmeter-bundle.png[New simulated meter OpenEMS Modbus Device Bundle in Eclipse IDE] . Open the btn:[bnd.bnd] file by double clicking on it. @@ -101,6 +96,7 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ @@ -128,6 +124,8 @@ OpenEMS Components can have several configuration parameters. They are defined a @AttributeDefinition(name = "Meter-Type", description = "Grid, Production (=default), Consumption") MeterType type() default MeterType.PRODUCTION; ---- ++ +NOTE: Eclipse will complain, that `MeterType` is unknown. Press btn:[Ctrl] + btn:[Shift] + btn:[o] to update the Java imports. .. Set the `String webconsole_configurationFactory_nameHint()` default value to `"Meter Simulated [\{id\}]"` . The content should now match the following code: @@ -202,16 +200,17 @@ Afterwards adjust the following content in the template `MeterSimulatedImpl.Java configurationPolicy = ConfigurationPolicy.REQUIRE // ) ---- -. Make the class implement the `ElectricityMeter` nature: ++ +NOTE: This _name_ is the Factory-ID of your Component. It is used in various places as a unique identification. ++ +. Make the class implement the `ElectricityMeter` nature (and fix the import error again with btn:[Ctrl] + btn:[Shift] + btn:[o]) + [source,java] ---- public class MeterSimulatedImpl extends AbstractOpenemsModbusComponent implements MeterSimulated, ElectricityMeter, OpenemsComponent, ModbusComponent { ---- -. Eclipse will underline `ElectricityMeter` and show the error *ElectricityMeter cannot be resolved to a type*. Resolve it by adding the line `import io.openems.edge.meter.api.ElectricityMeter;` to the import section of the file. + -NOTE: The easiest way to fix these kind of import errors is to select btn:[Source] → btn:[Organize Imports] in the menu or simply press btn:[Ctrl] + btn:[Shift] + btn:[o]. Alternatively click the 'light bulb' next to the line with the error and select btn:[Import 'ElectricityMeter' (io.openems.edge.meter.api)]. -. Eclipse still complains and now underlines the class name `MeterSimulated` with the error *The type MeterSimulated must implement the inherited abstract method ElectricityMeter.getMeterType()*. Resolve it by adding an implementation of the `getMeterType()` method: +. Eclipse still complains and now underlines the class name `MeterSimulatedImpl` with the error *The type MeterSimulatedImpl must implement the inherited abstract method ElectricityMeter.getMeterType()*. Resolve it by adding an implementation of the `getMeterType()` method: + [source,java] ---- @@ -257,7 +256,17 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException { ---- + and solve the import errors again as described above. -. Additionally it is advisable to implement a `debugLog()` method. This method provides information for the continuous log output of OpenEMS, provided by the *DebugLogController*. Adjust the method to return the ActivePower value of the meter: +. Additionally it is advisable to implement a `debugLog()` method. This method provides information for the continuous log output of OpenEMS, provided by the *DebugLogController*. We use it to print the current ActivePower value of the meter. Replace the existing method ++ +[source,java] +---- +@Override +public String debugLog() { + return "Hello World"; +} +---- ++ +with + [source,java] ---- @@ -285,30 +294,39 @@ import org.osgi.service.component.annotations.ReferencePolicy; import org.osgi.service.component.annotations.ReferencePolicyOption; import org.osgi.service.metatype.annotations.Designate; +import io.openems.common.exceptions.OpenemsException; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; +import io.openems.edge.bridge.modbus.api.ModbusComponent; import io.openems.edge.bridge.modbus.api.ModbusProtocol; import io.openems.edge.bridge.modbus.api.element.SignedWordElement; import io.openems.edge.bridge.modbus.api.task.FC3ReadRegistersTask; -import io.openems.edge.common.channel.Doc; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.ElectricityMeter; import io.openems.edge.meter.api.MeterType; -@Designate(ocd = Config.class, factory = true) // <1> +@Designate(ocd = Config.class, factory = true) <1> @Component(// <2> name = "Meter.Simulated", // <3> immediate = true, // <4> configurationPolicy = ConfigurationPolicy.REQUIRE // <5> ) public class MeterSimulatedImpl extends AbstractOpenemsModbusComponent // <6> - implements MeterSimulated, ElectricityMeter, OpenemsComponent, ModbusComponent { // <7> + implements MeterSimulated, ElectricityMeter, OpenemsComponent, ModbusComponent { // <7> + + @Reference + private ConfigurationAdmin cm; <8> + + @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) + protected void setModbus(BridgeModbus modbus) { + super.setModbus(modbus); <9> + } private Config config = null; public MeterSimulatedImpl() { - super(// <8> + super(// <10> OpenemsComponent.ChannelId.values(), // ElectricityMeter.ChannelId.values(), // ModbusComponent.ChannelId.values(), // @@ -316,22 +334,16 @@ public class MeterSimulatedImpl extends AbstractOpenemsModbusComponent // <6> ); } - @Reference - protected ConfigurationAdmin cm; // <9> - - @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) - protected void setModbus(BridgeModbus modbus) { - super.setModbus(modbus); // <10> - } - @Activate - void activate(ComponentContext context, Config config) throws OpenemsException { // <11> - if(super.activate(context, config.id(), config.alias(), config.enabled(), config.modbusUnitId(), this.cm, "Modbus", config.modbus_id())) { + private void activate(ComponentContext context, Config config) throws OpenemsException { // <11> + if (super.activate(context, config.id(), config.alias(), config.enabled(), config.modbusUnitId(), this.cm, + "Modbus", config.modbus_id())) { return; } this.config = config; } + @Override @Deactivate protected void deactivate() { // <12> super.deactivate(); @@ -374,9 +386,9 @@ NOTE: In plain Java it is not required to add `implements OpenemsComponent` if w - This enum is empty, as we do not have custom Channels here. - ChannelId enums require a Doc object that provides meta information about the Channel - e.g. the above ACTIVE_POWER Channel is defined as `ACTIVE_POWER(new Doc().type(OpenemsType.INTEGER).unit(Unit.WATT)` ==== -<8> We call the constructor of the super class (`AbstractOpenemsModbusComponent`/`AbstractOpenemsComponent`) to initialize the Channels of the Component. It is important to list all ChannelId-Enums of all implemented Natures. The call takes the *ChannelId* declarations and creates a Channel instance for each of them; e.g. for the `ElectricityMeter.ACTIVE_POWER` ChannelId, an object instance of `IntegerReadChannel` is created that represents the Channel. -<9> The `super.activate()` method requires an instance of *ConfigurationAdmin* as a parameter. Using the *@Reference* annotation the OSGi framework is going to provide the ConfigurationAdmin service via dependency injection. -<10> The Component utilizes an external Modbus Component (the _Modbus Bridge_) for the actual Modbus communication. We receive an instance of this service via dependency injection (like we did already for the _ConfigurationAdmin_ service). Most of the magic is handled by the _AbstractOpenemsModbusComponent_ implementation, but the way the OSGi framework works, we need to define the _@Reference_ explicitly here in the actual implementation of the component and call the parent `setModbus()` method. +<8> The `super.activate()` method requires an instance of *ConfigurationAdmin* as a parameter. Using the *@Reference* annotation the OSGi framework is going to provide the ConfigurationAdmin service via dependency injection. +<9> The Component utilizes an external Modbus Component (the _Modbus Bridge_) for the actual Modbus communication. We receive an instance of this service via dependency injection (like we did already for the _ConfigurationAdmin_ service). Most of the magic is handled by the _AbstractOpenemsModbusComponent_ implementation, but the way the OSGi framework works, we need to define the _@Reference_ explicitly here in the actual implementation of the component and call the parent `setModbus()` method. +<10> We call the constructor of the super class (`AbstractOpenemsModbusComponent`/`AbstractOpenemsComponent`) to initialize the Channels of the Component. It is important to list all ChannelId-Enums of all implemented Natures. The call takes the *ChannelId* declarations and creates a Channel instance for each of them; e.g. for the `ElectricityMeter.ACTIVE_POWER` ChannelId, an object instance of `IntegerReadChannel` is created that represents the Channel. <11> The *activate()* method (marked by the *@Activate* annotation) is called on activation of an object instance of this Component. It comes with a ComponentContext and an instance of a configuration in the form of a Config object. All logic for activating and deactivating the OpenEMS Component is hidden in the super classes and just needs to be called from here. <12> The *deactivate()* method (marked by the *@Deactivate* annotation) is called on deactivation of the Component instance. <13> _AbstractOpenemsModbusComponent_ requires to implement a *defineModbusProtocol()* method that returns an instance of *ModbusProtocol*. The _ModbusProtocol_ class maps Modbus addresses to OpenEMS Channels and provides some conversion utilities. Instantiation of a _ModbusProtocol_ object uses the https://en.wikipedia.org/wiki/Builder_pattern#Java[Builder pattern icon:external-link[]] @@ -401,7 +413,68 @@ Using this principle a complete Modbus table consisting of multiple register blo OpenEMS comes with an advanced test framework based on JUnit. The test scenarios are defined inside the `test` folder. The template we used before provides example implementations for `MyConfig.java` and `MyModbusDeviceTest.java`. We highly recommend implementing JUnit tests, because down the line it simplifies 'dry' coding (i.e. without using physical hardware) and assures high quality of the code you write. There are plenty of simple and advanced examples for JUnit tests throughout the OpenEMS project. -For now, to keep this tutorial simple, you can just delete the `io.openems.edge.meter.simulated` folder inside the `test` folder, to get rid of the compile errors in Eclipse IDE and be able to continue. +Update the `MyConfig.java` file with the following lines to simulate the `type` configuration: + +[source,java] +---- +... +@SuppressWarnings("all") +public class MyConfig extends AbstractComponentConfig implements Config { + + protected static class Builder { + ... + private MeterType type; + ... + public Builder setType(MeterType type) { + this.type = type; + return this; + } + ... + } + ... + @Override + public MeterType type() { + return this.builder.type; + } + ... +} +---- + +Add the `type` configuration also to the `MyModbusDeviceTest.java` file to get a fully functional first JUnit test: + +[source,java] +---- +package io.openems.edge.meter.simulated; + +import org.junit.Test; + +import io.openems.edge.bridge.modbus.test.DummyModbusBridge; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; +import io.openems.edge.common.test.ComponentTest; +import io.openems.edge.meter.api.MeterType; + +public class MyModbusDeviceTest { + + private static final String COMPONENT_ID = "component0"; + private static final String MODBUS_ID = "modbus0"; + + @Test + public void test() throws Exception { + new ComponentTest(new MeterSimulatedImpl()) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .activate(MyConfig.create() // + .setId(COMPONENT_ID) // + .setModbusId(MODBUS_ID) // + .setType(MeterType.GRID) // + .build()) + .next(new TestCase()); + } + +} +---- + +Right click on the `MyModbusDeviceTest.java` file in Eclipse and select btn:[Run As] -> btn:[JUnit Test] to execute the test. Or select btn:[Coverage As] -> btn:[JUnit Test] to see which code is already covered by your current JUnit test. === Start the device simulator @@ -462,7 +535,7 @@ Now switch back to btn:[Run] view. Press btn:[Run OSGi] to run OpenEMS Edge. -From then you can configure your component as shown in xref:gettingstarted.adoc[Getting Started] guide. Add the following configurations inside Apache Felix Web Console: +From then you can configure your component as shown in xref:gettingstarted.adoc[Getting Started] guide. To avoid misconfiguration, remove all the Components you configured during Getting Started. Then add the following configurations inside Apache Felix Web Console: Controller Debug Log:: - Component-ID: `ctrlDebugLog0` @@ -489,7 +562,7 @@ In the Eclipse IDE console log you should see an output like this: ---- [re.Cycle] INFO [ntroller.debuglog.DebugLogImpl] [ctrlDebugLog0] _sum[State:Ok Production:500 W Consumption:500 W] meter0[L:500 W] ---- -It shows a Production of `500 W` which is what is provided by the simulated meter device. Congrats! +It shows a Production of `500 W` which is the value provided by the simulated meter device. Congrats! === Debug the implementation diff --git a/doc/modules/ROOT/pages/gettingstarted.adoc b/doc/modules/ROOT/pages/gettingstarted.adoc index edac3eb0565..568a233702a 100644 --- a/doc/modules/ROOT/pages/gettingstarted.adoc +++ b/doc/modules/ROOT/pages/gettingstarted.adoc @@ -43,7 +43,7 @@ NOTE: Eclipse IDE is the recommended development environment for newcomers to Op . Prepare Eclipse IDE .. Download Java Development Kit (JDK) 17 and install it. We recommend the https://adoptium.net/de/temurin/releases/?version=17[OpenJDK Temurin builds by the Adoptium project] -.. Download https://www.eclipse.org[Eclipse for Java icon:external-link[]], install and start it +.. Download https://www.eclipse.org/downloads/[Eclipse for Java icon:external-link[]], install and start it .. On first start you will get asked to create a workspace. Select your source code directory (`C:\Users\your.user\git\openems` in our example) and press btn:[Launch]. + @@ -60,7 +60,7 @@ Menu: btn:[Help] → btn:[Eclipse Marketplace...] → btn:[Find:] → enter btn: - Select btn:[Java] - btn:[Installed JREs] in the navigation tree - Press the btn:[Add...] button - Keep btn:[Standard VM] selected and press btn:[Next >] -- Press the btn:[Directory...] button and select the folder of the installed JDK (e.g. `C:\Program Files\Eclipse Adoptium\jdk-17.0.6.10-hotspot`) +- Press the btn:[Directory...] button and select the folder of the installed JDK (e.g. `C:\Program Files\Eclipse Adoptium\jdk-17.0.7.7-hotspot`) - Press the btn:[Finish] button - Back in the Preferences window, tick the newly added JDK 17 and press btn:[Apply and Close] + @@ -82,6 +82,14 @@ Menu: btn:[File] → btn:[Import...] → btn:[Bndtools] → btn:[Existing Bnd W + .io.openems.edge.application project in Eclipse IDE image::eclipse-io.openems.edge.application.png[io.openems.edge.application project in Eclipse IDE] ++ +NOTE: Instead of navigating through the projects tree, you can simply use the keyboard shortcut btn:[Ctrl] + btn:[Shift] + btn:[R] to start the "Open Resource" dialog. Enter "EdgeApp.bndrun" there and press btn:[Enter] to open the file. ++ +The `EdgeApp.bndrun` file declares all the bundles and runtime properties. For now it should not be necessary to edit it, but it hides some useful settings unter the btn:[Source] tab: ++ +- `org.osgi.service.http.port=8080`: start the Apache Felix Web Console on port `8080` +- `felix.cm.dir=c:/openems/config`: persist configurations in the folder `c:/openems/config`. Adjust this if you are working on Linux to keep your configurations after restart +- `openems.data.dir=c:/openems/data`: this is where bundles are allowed to persist data. It is used e.g. by the RRD4j timedata storage .. Click on btn:[Run OSGi] to run OpenEMS Edge. You should see log outputs in the **Console** tab inside Eclipse IDE. + @@ -98,7 +106,7 @@ image::apache-felix-console-configuration.png[Apache Felix Web Console Configura .. Configure a Scheduler + -NOTE: The Scheduler is responsible for executing the control algorithms (Controllers) and defines the OpenEMS Edge application cycle +NOTE: The Scheduler is responsible for executing the control algorithms (Controllers) in order and defines the OpenEMS Edge application cycle ... Click on _**Scheduler All Alphabetically**_ + @@ -113,7 +121,9 @@ image::config-scheduler-all-alphabetically.png[Configuration of All Alphabetical INFO [onent.AbstractOpenemsComponent] [scheduler0] Activate Scheduler.AllAlphabetically ``` + -Add any other OpenEMS Components in the same way: +Add any other OpenEMS Components in the same way. ++ +NOTE: Once everything is setup you can configure Components more easily via OpenEMS UI using the "Install components" feature in the Settings. .. Configure debug outputs on the console: _**Controller Debug Log**_. The default values can be accepted without changes. + @@ -152,7 +162,7 @@ NOTE: The data source was configured with the OpenEMS Component ID `datasource0` .Configuration of Simulator GridMeter Acting image::config-simulator-grid-meter-acting.png[Configuration of Simulator GridMeter Acting] + -This time some more logs will show up. Most importantly they show, that the Grid meter now shows a power value and the Consumption is derived directly from this value, because no PV system or energy storage system is configured yet. +This time some more logs will appear. Most importantly they show, that the Grid meter now measures (simulates) a power value and the Consumption is derived directly from this value, because no PV system or energy storage system is configured yet. + ``` INFO [onent.AbstractOpenemsComponent] [meter0] Activate Simulator.GridMeter.Acting @@ -165,7 +175,7 @@ NOTE: This setup causes the simulated grid-meter to take the standardized load-p + NOTE: 'Acting' in the name 'Simulator GridMeter Acting' refers to the fact, that this meter actively provides data - in opposite to a 'Reacting' simulated device that is reacting on other components: for example the 'Simulator.EssSymmetric.Reacting' configured below. -.. Configure a simulated reacting energy storage system: _**Simulator EssSymmetric Reacting**_. The default values can be accepted without changes. (If you choose an other setup as the one described here you may have to create a new Datasource-Component and provide its ID here. The actual data is ignored, but the Datasource's Time-Delta value is required to calculate values with time-dependant units.) +.. Configure a simulated reacting energy storage system: _**Simulator EssSymmetric Reacting**_. The default values can be accepted without changes. + .Configuration of Simulator EssSymmetric Reacting image::config-simulator-esssymmetric-reacting.png[Configuration of Simulator EssSymmetric Reacting] @@ -180,10 +190,10 @@ INFO [ntroller.debuglog.DebugLogImpl] [ctrlDebugLog0] _sum[State:Ok Ess SoC:50 + NOTE: The debug log now shows data for the battery, but the charge/discharge power stays at "0 W" and the state of charge stays at "50 %" as configured. Next step is to configure a control algorithm that tells the battery to charge or discharge depending on the power measured by the simulated grid meter. -.. Configure the self-consumption optimization algorithm: _**Controller Balancing Symmetric**_. Configure the `Ess-ID` `'ess0'` and `Grid-Meter-ID` `'meter0'` to refer to the components configured above. +.. Configure the self-consumption optimization algorithm: _**Controller Ess Balancing**_. Configure the `Ess-ID` `'ess0'` and `Grid-Meter-ID` `'meter0'` to refer to the components configured above. + -.Configuration of Symmetric Balancing Controller -image::config-controller-balancing-symmetric.png[Configuration of Symmetric Balancing Controller] +.Configuration of Controller Ess Balancing +image::config-controller-ess-balancing.png[Configuration of Controller Ess Balancing] + The log shows: + @@ -227,6 +237,11 @@ image::openems-ui-login.png[OpenEMS UI Login screen] .OpenEMS UI Energymonitor screen image::openems-ui-edge-overview.png[OpenEMS UI Energymonitor screen] +_Unfortunately the hosted version of OpenEMS UI is currently slightly outdated and incompatble with latest OpenEMS Edge. Follow the xref:ui/setup-ide.adoc[OpenEMS UI guide] to produce the following visualization. The language can be changed in the "burger menu" on top left -> btn:[admin] -> btn:[Allgemeine Einstellungen]._ + +.OpenEMS UI Energymonitor screen +image::openems-ui-edge-overview2.png[OpenEMS UI Energymonitor screen] + == Integrate OpenEMS Backend Instead of having Edge and UI talk to each other directly, the communication can also be proxied via Backend. @@ -251,15 +266,12 @@ NOTE: Disable the two icon buttons "Show Console When Standard Out changes" and NOTE: Apache Felix Web Console for OpenEMS Backend is started on port 8079 by default. This is configured using the `org.osgi.service.http.port` setting in BackendApp.bndrun. + Login with username *admin* and password *admin*. -+ -.Apache Felix Web Console Configuration for OpenEMS Backend -image::apache-felix-console-backend-configuration.png[Apache Felix Web Console Configuration for OpenEMS Backend] .. Configure Edge.Websocket + NOTE: The _**Edge.Websocket**_ service is responsible for the communication between OpenEMS Backend and OpenEMS Edge. + -In the example we are configuring the `Port '8081'`. This port needs to match with what we configure later in OpenEMS Edge. +In the example we are configuring the `Port '8081'`. This port needs to match with what we configure later in OpenEMS Edge. The `Debug Mode 'DETAILED'` setting helps us to get some more details on the internal behaviour. + .Configuration of Backend Edge.Websocket image::config-backend-edge.websocket.png[Configuration of Backend Edge.Websocket] @@ -268,7 +280,7 @@ image::config-backend-edge.websocket.png[Configuration of Backend Edge.Websocket + NOTE: The _**Ui.Websocket**_ service is responsible for the communication between OpenEMS Backend and OpenEMS UI. + -In the example we are configuring the `Port '8082'`. This port needs to match with what we configure later in the OpenEMS UI environment file. +In the example we are configuring the `Port '8082'`. This port needs to match with what we configure later in the OpenEMS UI environment file. We are again setting `Debug Mode 'DETAILED'` + .Configuration of Backend Ui.Websocket image::config-backend-ui.websocket.png[Configuration of Backend Ui.Websocket] @@ -277,7 +289,7 @@ image::config-backend-ui.websocket.png[Configuration of Backend Ui.Websocket] + NOTE: The *Timedata* service provider is responsible for holding the current and historic data of each connected Edge device. + -In the example we are configuring the _**Timedata.Dummy**_ service. It takes no configuration parameters, so just press btn:[Save]. In a production system you would want to use a real implementation like *Timedata.InfluxDB*. +In the example we are configuring the _**Timedata.Dummy**_ service. The default value for _Component-ID` can be accepted without changes, so just press btn:[Save]. In a production system you would want to use a real implementation like *Timedata.InfluxDB*. + .Configuration of Backend Timedata.Dummy image::config-backend-timedata.dummy.png[Configuration of Backend Timedata.Dummy] @@ -291,6 +303,16 @@ image::config-backend-metadata.dummy.png[Configuration of Backend Metadata.Dummy + NOTE: In the example we are configuring the _**Metadata.Dummy**_ service. It takes no configuration parameters, so just press btn:[Save]. In a production system you would want to use a real implementation like _**Metadata.File**_, which uses a static JSON file as input, or _**Metadata.Odoo**_, which uses the *Odoo* business software for authentication and IoT device management. This will require the https://github.com/OpenEMS/odoo-openems[Odoo-OpenEMS-Addon] to be installed on your Odoo instance. See the https://gitpod.io/#https://github.com/OpenEMS/openems/tree/main[OpenEMS Live-Demo Gitpod workspace] for a full, production ready example configuration. For more information see → xref:simulation/gitpod.adoc[Gitpod Workspace] +.. Backend is ready ++ +You should have seen some important log messages by now, that indicate that the OpenEMS Backend is ready to accept connections: +``` +INFO [d.timedata.dummy.TimedataDummy] [Timedata.Dummy] Activate +INFO [d.metadata.dummy.MetadataDummy] [Metadata.Dummy] Activate +INFO [socket.AbstractWebsocketServer] [Ui.Websocket] Starting websocket server [port=8082] +INFO [socket.AbstractWebsocketServer] [Edge.Websocket] Starting websocket server [port=8081] +``` + === Configure OpenEMS Edge Next we will configure OpenEMS Edge to connect to the OpenEMS Backend _**Edge.Websocket**_ service. @@ -311,18 +333,24 @@ Once you press btn:[save] you should see logs in OpenEMS Edge + ``` INFO [onent.AbstractOpenemsComponent] [ctrlBackend0] Activate Controller.Api.Backend -INFO [socket.AbstractWebsocketClient] Opening connection [Controller.Api.Backend:ctrlBackend0] to websocket server [ws://localhost:8081] +INFO [socket.AbstractWebsocketClient] [ctrlBackend0] Opening connection to websocket server [ws://localhost:8081] +INFO [socket.ClientReconnectorWorker] [ctrlBackend0] Connecting WebSocket... [NOT_YET_CONNECTED] +INFO [socket.ClientReconnectorWorker] [ctrlBackend0] Connected WebSocket successfully [0s] INFO [.controller.api.backend.OnOpen] [ctrlBackend0] Connected to OpenEMS Backend ``` + and OpenEMS Backend + ``` -INFO [s.backend.edgewebsocket.OnOpen] [Edge.Websocket] Edge [edge0] connected +INFO [s.backend.common.metadata.Edge] Edge [edge0]: Update version from [0.0.0] to [...] +INFO [mon.metadata.SimpleEdgeHandler] Edge [edge0]. Update config: ... +INFO [dgewebsocket.EdgeWebsocketImpl] [monitor] Edge-Connections: 1 ``` === Connect OpenEMS UI with Backend +_(You need to have completed the xref:ui/setup-ide.adoc[OpenEMS UI guide] for the following steps)_ + . In the Visual Studio Code terminal stop the running `ng serve...` by pressing btn:[ctrl] + btn:[c] . Restart OpenEMS UI in 'local backend mode': @@ -337,10 +365,18 @@ NOTE: OpenEMS UI can work both for local connections to OpenEMS Edge as well as + NOTE: _**Metadata.Dummy**_ accepts any user/password combination. For production use, switch to a different *Metadata* implementation as described above. + -.UI via Backend -image::ui-via-backend.png[UI via Backend] +.OpenEMS UI Login screen +image::openems-ui-backend-login.png[OpenEMS UI Login screen] + +. You will be presented an overview list of all connected OpenEMS Edge devices you have permissions for: ++ +.OpenEMS UI Overview screen +image::openems-ui-backend-overview.png[OpenEMS UI Overview screen] + +. Click on *OpenEMS Edge #0* to see the same live-view as before on the local connection. + -Click on *OpenEMS Edge #0* to see the same live-view as before on the local connection. +.OpenEMS UI Live screen +image::openems-ui-backend-live.png[OpenEMS UI Live screen] ## Next steps diff --git a/io.openems.common/resources/templates/device-modbus/$testSrcDir$/$basePackageDir$/MyConfig.java b/io.openems.common/resources/templates/device-modbus/$testSrcDir$/$basePackageDir$/MyConfig.java index 9e5f5a5c406..5b9432ab361 100644 --- a/io.openems.common/resources/templates/device-modbus/$testSrcDir$/$basePackageDir$/MyConfig.java +++ b/io.openems.common/resources/templates/device-modbus/$testSrcDir$/$basePackageDir$/MyConfig.java @@ -9,7 +9,7 @@ public class MyConfig extends AbstractComponentConfig implements Config { protected static class Builder { private String id; private String modbusId = null; - public int modbusUnitId; + private int modbusUnitId; private Builder() { } diff --git a/io.openems.common/resources/templates/device-modbus/$testSrcDir$/$basePackageDir$/MyModbusDeviceTest.java b/io.openems.common/resources/templates/device-modbus/$testSrcDir$/$basePackageDir$/MyModbusDeviceTest.java index b429bb3bf95..c9961ad3a77 100644 --- a/io.openems.common/resources/templates/device-modbus/$testSrcDir$/$basePackageDir$/MyModbusDeviceTest.java +++ b/io.openems.common/resources/templates/device-modbus/$testSrcDir$/$basePackageDir$/MyModbusDeviceTest.java @@ -5,6 +5,7 @@ import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; +import io.openems.edge.common.test.DummyConfigurationAdmin; public class MyModbusDeviceTest { @@ -14,6 +15,7 @@ public class MyModbusDeviceTest { @Test public void test() throws Exception { new ComponentTest(new MyModbusDeviceImpl()) // + .addReference("cm", new DummyConfigurationAdmin()) // .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // .activate(MyConfig.create() // .setId(COMPONENT_ID) // diff --git a/io.openems.common/resources/templates/device-modbus/bnd.bnd b/io.openems.common/resources/templates/device-modbus/bnd.bnd index 05cd776f2dc..df081ceca20 100644 --- a/io.openems.common/resources/templates/device-modbus/bnd.bnd +++ b/io.openems.common/resources/templates/device-modbus/bnd.bnd @@ -5,6 +5,7 @@ Bundle-Version: 1.0.0.\${tstamp} -buildpath: \ \${buildpath},\ + com.ghgande.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common From 99a6175b4731bb70f2bc3676fdff287a4fd5a072 Mon Sep 17 00:00:00 2001 From: Kai J <99220919+da-Kai@users.noreply.github.com> Date: Mon, 28 Aug 2023 17:27:07 +0200 Subject: [PATCH 25/32] CI: build artifacts on tag/release event (#2332) * create seprate release action * change to push event --- .github/workflows/build.yml | 58 +-------------------- .github/workflows/release.yml | 96 +++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 57 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fe48934cb56..92c9f8af747 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -66,22 +66,6 @@ jobs: name: jacoco_report path: jacoco/ - # - # Is this a Tag? Prepare release assets - # - - name: Prepare Edge+Backend assets - if: startsWith(github.ref, 'refs/tags/') - run: ./gradlew buildEdge buildBackend - - - name: Save build-artifacts - if: startsWith(github.ref, 'refs/tags/') - uses: actions/upload-artifact@v3 - with: - name: build-artifacts - path: | - build/openems-edge.jar - build/openems-backend.jar - build-ui: runs-on: ubuntu-latest steps: @@ -110,44 +94,4 @@ jobs: node_modules/.bin/ng build -c "openems,openems-edge-prod,prod" node_modules/.bin/ng lint export CHROME_BIN=/usr/bin/google-chrome-stable - npm run test -- --no-watch --no-progress --browsers=ChromeHeadlessCI - - # - # Is this a Tag? Prepare release assets - # - - name: Prepare UI asset - if: startsWith(github.ref, 'refs/tags/') - run: | - mkdir build - cd ui/target - zip -r ../../build/openems-ui.zip ./* - - - name: Save build-artifacts - if: startsWith(github.ref, 'refs/tags/') - uses: actions/upload-artifact@v3 - with: - name: build-artifacts - path: build/openems-ui.zip - - # - # Is this a Tag? Create a draft release - # - release: - runs-on: ubuntu-latest - needs: [build-java, build-ui] - if: startsWith(github.ref, 'refs/tags/') - steps: - - name: Load build-artifacts - uses: actions/download-artifact@v3 - with: - name: build-artifacts - path: build - - - name: Create draft Release - uses: softprops/action-gh-release@v1 - with: - draft: true - files: | - build/openems-edge.jar - build/openems-backend.jar - build/openems-ui.zip \ No newline at end of file + npm run test -- --no-watch --no-progress --browsers=ChromeHeadlessCI \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000000..4fa33b65e41 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,96 @@ +name: Prepare OpenEMS Release + +on: + push: + tags: + - "*.*.*" + +jobs: + build-java: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup Java 17 + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + cache: gradle + + - uses: kiancross/checkstyle-annotations-action@v1 + + - name: Validate BackendApp.bndrun and EdgeApp.bndrun + run: git diff --exit-code io.openems.backend.application/BackendApp.bndrun io.openems.edge.application/EdgeApp.bndrun + + - name: Clean Edge+Backend assets + run: ./gradlew cleanEdge cleanBackend + + - name: Prepare Edge+Backend assets + run: ./gradlew buildEdge buildBackend + + - name: Save build-artifacts + uses: actions/upload-artifact@v3 + with: + name: build-artifacts + path: | + build/openems-edge.jar + build/openems-backend.jar + + build-ui: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '16' + + - name: Setup Cache for Node.js + uses: actions/cache@v3 + with: + path: | + ~/.npm + ~/.ng + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: ${{ runner.os }}-node- + + - name: Build OpenEMS UI + run: | + cd ui + npm install + npm ci --prefer-offline --cache ~/.npm + node_modules/.bin/ng config cli.cache.path "~/.ng" + node_modules/.bin/ng build -c "openems,openems-edge-prod,prod" + + - name: Prepare UI asset + run: | + mkdir build + cd ui/target + zip -r ../../build/openems-ui.zip ./* + + - name: Save build-artifacts + uses: actions/upload-artifact@v3 + with: + name: build-artifacts + path: build/openems-ui.zip + + release: + runs-on: ubuntu-latest + needs: [build-java, build-ui] + steps: + - name: Load build-artifacts + uses: actions/download-artifact@v3 + with: + name: build-artifacts + path: build + + - name: Create draft Release + uses: softprops/action-gh-release@v1 + with: + draft: true + files: | + build/openems-edge.jar + build/openems-backend.jar + build/openems-ui.zip \ No newline at end of file From a822af32bfad43423d4c7e43fca0c3990883e616 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Aug 2023 09:21:11 +0200 Subject: [PATCH 26/32] Bump org.apache.felix:org.apache.felix.http.jetty from 5.0.6 to 5.1.0 in /cnf (#2334) * Bump org.apache.felix:org.apache.felix.http.jetty in /cnf Bumps org.apache.felix:org.apache.felix.http.jetty from 5.0.6 to 5.1.0. --- updated-dependencies: - dependency-name: org.apache.felix:org.apache.felix.http.jetty dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Update bndrun --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Stefan Feilmeier --- cnf/pom.xml | 2 +- io.openems.backend.application/BackendApp.bndrun | 2 +- io.openems.edge.application/EdgeApp.bndrun | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cnf/pom.xml b/cnf/pom.xml index c3b66681c7a..4c492266c55 100644 --- a/cnf/pom.xml +++ b/cnf/pom.xml @@ -208,7 +208,7 @@ org.apache.felix org.apache.felix.http.jetty - 5.0.6 + 5.1.0 diff --git a/io.openems.backend.application/BackendApp.bndrun b/io.openems.backend.application/BackendApp.bndrun index c21fbcb65db..9c3cc163ea8 100644 --- a/io.openems.backend.application/BackendApp.bndrun +++ b/io.openems.backend.application/BackendApp.bndrun @@ -105,7 +105,7 @@ org.apache.felix.configadmin;version='[1.9.26,1.9.27)',\ org.apache.felix.eventadmin;version='[1.6.4,1.6.5)',\ org.apache.felix.fileinstall;version='[3.7.4,3.7.5)',\ - org.apache.felix.http.jetty;version='[5.0.6,5.0.7)',\ + org.apache.felix.http.jetty;version='[5.1.0,5.1.1)',\ org.apache.felix.http.servlet-api;version='[2.1.0,2.1.1)',\ org.apache.felix.inventory;version='[1.1.0,1.1.1)',\ org.apache.felix.metatype;version='[1.2.4,1.2.5)',\ diff --git a/io.openems.edge.application/EdgeApp.bndrun b/io.openems.edge.application/EdgeApp.bndrun index 8de72d9193d..31ea438491e 100644 --- a/io.openems.edge.application/EdgeApp.bndrun +++ b/io.openems.edge.application/EdgeApp.bndrun @@ -374,7 +374,7 @@ org.apache.felix.configadmin;version='[1.9.26,1.9.27)',\ org.apache.felix.eventadmin;version='[1.6.4,1.6.5)',\ org.apache.felix.fileinstall;version='[3.7.4,3.7.5)',\ - org.apache.felix.http.jetty;version='[5.0.6,5.0.7)',\ + org.apache.felix.http.jetty;version='[5.1.0,5.1.1)',\ org.apache.felix.http.servlet-api;version='[2.1.0,2.1.1)',\ org.apache.felix.inventory;version='[1.1.0,1.1.1)',\ org.apache.felix.metatype;version='[1.2.4,1.2.5)',\ From aa9217c7a43a046f06a94673503a2a544305149a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Aug 2023 09:40:02 +0200 Subject: [PATCH 27/32] Bump org.jetbrains.kotlin:kotlin-osgi-bundle from 1.9.0 to 1.9.10 in /cnf (#2335) * Bump org.jetbrains.kotlin:kotlin-osgi-bundle in /cnf Bumps org.jetbrains.kotlin:kotlin-osgi-bundle from 1.9.0 to 1.9.10. --- updated-dependencies: - dependency-name: org.jetbrains.kotlin:kotlin-osgi-bundle dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Update bndrun --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Stefan Feilmeier --- cnf/pom.xml | 2 +- io.openems.backend.application/BackendApp.bndrun | 2 +- io.openems.edge.application/EdgeApp.bndrun | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cnf/pom.xml b/cnf/pom.xml index 4c492266c55..024867a8fae 100644 --- a/cnf/pom.xml +++ b/cnf/pom.xml @@ -302,7 +302,7 @@ org.jetbrains.kotlin kotlin-osgi-bundle - 1.9.0 + 1.9.10 org.jetbrains.kotlinx diff --git a/io.openems.backend.application/BackendApp.bndrun b/io.openems.backend.application/BackendApp.bndrun index 9c3cc163ea8..baf2d14f256 100644 --- a/io.openems.backend.application/BackendApp.bndrun +++ b/io.openems.backend.application/BackendApp.bndrun @@ -112,7 +112,7 @@ org.apache.felix.scr;version='[2.2.6,2.2.7)',\ org.apache.felix.webconsole;version='[4.7.2,4.7.3)',\ org.apache.felix.webconsole.plugins.ds;version='[2.2.0,2.2.1)',\ - org.jetbrains.kotlin.osgi-bundle;version='[1.9.0,1.9.1)',\ + org.jetbrains.kotlin.osgi-bundle;version='[1.9.10,1.9.11)',\ org.jsr-305;version='[3.0.2,3.0.3)',\ org.ops4j.pax.logging.pax-logging-api;version='[2.2.1,2.2.2)',\ org.ops4j.pax.logging.pax-logging-log4j2;version='[2.2.1,2.2.2)',\ diff --git a/io.openems.edge.application/EdgeApp.bndrun b/io.openems.edge.application/EdgeApp.bndrun index 31ea438491e..929acd15539 100644 --- a/io.openems.edge.application/EdgeApp.bndrun +++ b/io.openems.edge.application/EdgeApp.bndrun @@ -386,7 +386,7 @@ org.eclipse.jetty.io;version='[9.4.28,9.4.29)',\ org.eclipse.jetty.util;version='[9.4.28,9.4.29)',\ org.eclipse.paho.mqttv5.client;version='[1.2.5,1.2.6)',\ - org.jetbrains.kotlin.osgi-bundle;version='[1.9.0,1.9.1)',\ + org.jetbrains.kotlin.osgi-bundle;version='[1.9.10,1.9.11)',\ org.jsoup;version='[1.16.1,1.16.2)',\ org.jsr-305;version='[3.0.2,3.0.3)',\ org.openmuc.jmbus;version='[3.3.0,3.3.1)',\ From c8b91d70df2518eb155948ed21a87874e1f6d71c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Aug 2023 11:36:55 +0200 Subject: [PATCH 28/32] Bump io.reactivex.rxjava3:rxjava from 3.1.6 to 3.1.7 in /cnf (#2333) * Bump io.reactivex.rxjava3:rxjava from 3.1.6 to 3.1.7 in /cnf Bumps [io.reactivex.rxjava3:rxjava](https://github.com/ReactiveX/RxJava) from 3.1.6 to 3.1.7. - [Release notes](https://github.com/ReactiveX/RxJava/releases) - [Commits](https://github.com/ReactiveX/RxJava/compare/v3.1.6...v3.1.7) --- updated-dependencies: - dependency-name: io.reactivex.rxjava3:rxjava dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Update bndrun --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Stefan Feilmeier --- cnf/pom.xml | 2 +- io.openems.backend.application/BackendApp.bndrun | 2 +- io.openems.edge.application/EdgeApp.bndrun | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cnf/pom.xml b/cnf/pom.xml index 024867a8fae..5cc765a1e49 100644 --- a/cnf/pom.xml +++ b/cnf/pom.xml @@ -154,7 +154,7 @@ io.reactivex.rxjava3 rxjava - 3.1.6 + 3.1.7 diff --git a/io.openems.backend.application/BackendApp.bndrun b/io.openems.backend.application/BackendApp.bndrun index baf2d14f256..6ba1cde1518 100644 --- a/io.openems.backend.application/BackendApp.bndrun +++ b/io.openems.backend.application/BackendApp.bndrun @@ -98,7 +98,7 @@ io.openems.wrapper.retrofit-converter-gson;version=snapshot,\ io.openems.wrapper.retrofit-converter-scalars;version=snapshot,\ io.openems.wrapper.retrofit2;version=snapshot,\ - io.reactivex.rxjava3.rxjava;version='[3.1.6,3.1.7)',\ + io.reactivex.rxjava3.rxjava;version='[3.1.7,3.1.8)',\ org.apache.commons.commons-csv;version='[1.10.0,1.10.1)',\ org.apache.commons.commons-fileupload;version='[1.5.0,1.5.1)',\ org.apache.commons.commons-io;version='[2.13.0,2.13.1)',\ diff --git a/io.openems.edge.application/EdgeApp.bndrun b/io.openems.edge.application/EdgeApp.bndrun index 929acd15539..18eaa65a3a8 100644 --- a/io.openems.edge.application/EdgeApp.bndrun +++ b/io.openems.edge.application/EdgeApp.bndrun @@ -364,7 +364,7 @@ io.openems.wrapper.retrofit-converter-scalars;version=snapshot,\ io.openems.wrapper.retrofit2;version=snapshot,\ io.openems.wrapper.sdnotify;version=snapshot,\ - io.reactivex.rxjava3.rxjava;version='[3.1.6,3.1.7)',\ + io.reactivex.rxjava3.rxjava;version='[3.1.7,3.1.8)',\ javax.jmdns;version='[3.4.1,3.4.2)',\ javax.xml.soap-api;version='[1.4.0,1.4.1)',\ org.apache.commons.commons-csv;version='[1.10.0,1.10.1)',\ From cfc170ec035f5dc676db823a59d0eccb58f297d4 Mon Sep 17 00:00:00 2001 From: Lukas Rieger <73471197+lukasrgr@users.noreply.github.com> Date: Tue, 29 Aug 2023 13:21:50 +0200 Subject: [PATCH 29/32] UI: adjust energymonitor for mobile devices (#2323) --- .../edge/history/common/energy/flat/flat.html | 31 ++++++++++++++----- .../edge/history/common/energy/flat/flat.ts | 2 +- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/ui/src/app/edge/history/common/energy/flat/flat.html b/ui/src/app/edge/history/common/energy/flat/flat.html index 713e3d4895b..94a5b9ea5be 100644 --- a/ui/src/app/edge/history/common/energy/flat/flat.html +++ b/ui/src/app/edge/history/common/energy/flat/flat.html @@ -1,10 +1,27 @@ - - - - - + + + + + Edge.Index.Energymonitor.title + + + + + - \ No newline at end of file + + + + + + + + + + + + + \ No newline at end of file diff --git a/ui/src/app/edge/history/common/energy/flat/flat.ts b/ui/src/app/edge/history/common/energy/flat/flat.ts index 50e3fba5607..cc0c2e7d4b9 100644 --- a/ui/src/app/edge/history/common/energy/flat/flat.ts +++ b/ui/src/app/edge/history/common/energy/flat/flat.ts @@ -15,7 +15,7 @@ export class FlatComponent extends AbstractFlatWidget { protected autarchyValue: number | null; private static readonly EXCEL_TYPE = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8'; private static readonly EXCEL_EXTENSION = '.xlsx'; - + protected readonly isSmartphoneResolution = this.service.isSmartphoneResolution; protected override onCurrentData(currentData: CurrentData) { this.autarchyValue = From b57ee07bad00bd976b7a3efa9aa8f775b31119e1 Mon Sep 17 00:00:00 2001 From: huseyinsaht <34771592+huseyinsaht@users.noreply.github.com> Date: Wed, 30 Aug 2023 09:06:56 +0200 Subject: [PATCH 30/32] FENECON Commercial 30 Off-Grid: fix getGroundingContactor (#2322) * bug fix for grounding contactor * Add JUnit test --------- Co-authored-by: Stefan Feilmeier --- .../edge/ess/offgrid/api/OffGridSwitch.java | 2 +- .../offgrid/statemachine/ContextTest.java | 112 ++++++++++++++++++ 2 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/offgrid/statemachine/ContextTest.java diff --git a/io.openems.edge.ess.api/src/io/openems/edge/ess/offgrid/api/OffGridSwitch.java b/io.openems.edge.ess.api/src/io/openems/edge/ess/offgrid/api/OffGridSwitch.java index ae97d6339aa..ac30cda219e 100644 --- a/io.openems.edge.ess.api/src/io/openems/edge/ess/offgrid/api/OffGridSwitch.java +++ b/io.openems.edge.ess.api/src/io/openems/edge/ess/offgrid/api/OffGridSwitch.java @@ -114,7 +114,7 @@ public default Channel getGroundingContactorChannel() { * @return the Channel {@link Value} */ public default Optional getGroundingContactor() { - var groundingContactor = this.getMainContactorChannel().value(); + var groundingContactor = this.getGroundingContactorChannel().value(); return groundingContactor.asOptional().map(value -> { return value ? Contactor.CLOSE : Contactor.OPEN; }); diff --git a/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/offgrid/statemachine/ContextTest.java b/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/offgrid/statemachine/ContextTest.java new file mode 100644 index 00000000000..93cf2f60603 --- /dev/null +++ b/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/offgrid/statemachine/ContextTest.java @@ -0,0 +1,112 @@ +package io.openems.edge.ess.generic.offgrid.statemachine; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.edge.common.channel.Channel; +import io.openems.edge.common.component.AbstractOpenemsComponent; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.ess.offgrid.api.OffGridSwitch; + +public class ContextTest { + + private static class DummyOffGridSwitch extends AbstractOpenemsComponent implements OffGridSwitch { + + public DummyOffGridSwitch(String id) { + this(id, new io.openems.edge.common.channel.ChannelId[0]); + } + + public DummyOffGridSwitch(String id, io.openems.edge.common.channel.ChannelId[] additionalChannelIds) { + super(// + OpenemsComponent.ChannelId.values(), // + OffGridSwitch.ChannelId.values(), // + additionalChannelIds // + ); + for (Channel channel : this.channels()) { + channel.nextProcessImage(); + } + super.activate(null, id, "", true); + } + + /** + * Sets and applies the {@link OffGridSwitch.ChannelId#MAIN_CONTACTOR}. + * + * @param value the state of the MainContactor + * @return myself + */ + public DummyOffGridSwitch withMainContactor(boolean value) { + this._setMainContactor(value); + this.getMainContactorChannel().nextProcessImage(); + return this; + } + + /** + * Sets and applies the {@link OffGridSwitch.ChannelId#GROUNDING_CONTACTOR}. + * + * @param value the state of the GroundingContactor + * @return myself + */ + public DummyOffGridSwitch withGroundingContactor(boolean value) { + this._setGroundingContactor(value); + this.getGroundingContactorChannel().nextProcessImage(); + return this; + } + + @Override + public void setMainContactor(Contactor operation) throws IllegalArgumentException, OpenemsNamedException { + } + + @Override + public void setGroundingContactor(Contactor operation) throws IllegalArgumentException, OpenemsNamedException { + } + + } + + @Test + public void testIsOnGridContactorsSet() { + var sut = new DummyOffGridSwitch("offGridSwitch0"); + var context = new Context(null, null, null, sut, null, null); + + sut.withMainContactor(false); + sut.withGroundingContactor(false); + assertTrue(context.isOnGridContactorsSet()); + + sut.withMainContactor(true); + sut.withGroundingContactor(false); + assertFalse(context.isOnGridContactorsSet()); + + sut.withMainContactor(false); + sut.withGroundingContactor(true); + assertFalse(context.isOnGridContactorsSet()); + + sut.withMainContactor(true); + sut.withGroundingContactor(true); + assertFalse(context.isOnGridContactorsSet()); + } + + @Test + public void testIsOffGridContactorsSet() { + var sut = new DummyOffGridSwitch("offGridSwitch0"); + var context = new Context(null, null, null, sut, null, null); + + sut.withMainContactor(false); + sut.withGroundingContactor(false); + assertFalse(context.isOffGridContactorsSet()); + + sut.withMainContactor(true); + sut.withGroundingContactor(false); + assertFalse(context.isOffGridContactorsSet()); + + sut.withMainContactor(false); + sut.withGroundingContactor(true); + assertFalse(context.isOffGridContactorsSet()); + + sut.withMainContactor(true); + sut.withGroundingContactor(true); + assertTrue(context.isOffGridContactorsSet()); + } + +} From f0b052746e91a9f21983cae3fd9fb4605990ded0 Mon Sep 17 00:00:00 2001 From: Stefan Feilmeier Date: Fri, 1 Sep 2023 16:06:12 +0200 Subject: [PATCH 31/32] FEMS Backport 2023.9.0 (#2338) Common * Add script to create a OEM Release * Add script to create OpenEMS Edge + UI Debian Package * App-Center improvements * Backend handlers for App-Center requests * All latest updates, including * Apps for Alpitronic Hypercharger, Dezony, Cluster of charging stations, SDM630 meter, symmetric/asymmetric Peak-Shaving with ESS * Lots of improvements in UI (JsonFormly, validators, etc.) * Websocket connections * Re-enable lost connection detection (5 minutes) * Improve reconnection logic * Add helper utils, stream collectors and JUnit tests Edge * Add Unit "bar" * Update Persistence Priorities for UI/Aggregated Influx * FENECON Commercial Battery: cleanup + improvements * FENECON Home Battery: type detection; support Home 20 & 30 * Soltaro Battery: cleanup + improvements * **Resend historic data from local RRD4j** * add a resend worker * sends after reconnection to backend and a DELAY(5 minutes) the resend data to the backend * additional config persistencePriority for resend channels * rrd4j * write data every rounded to 5 minutes time instead of every 5 minutes * fix year view * removed 5 minutes offset when querying currentData * added versions to separate data schemas * changed currenct schema to store cumulated values only per hour * separated parent and RecordWorker * add resend notification * SG-Ready Heatpump: improvements * Network Settings: use UTF_8 instead of US_ASCII * ESS-Cluster: handle Start/Stop request * KEBA evcs: use display on demand * GoodWe BatteryInverter: improvements, compatibility with GoodWe 20/30 including up to 6 PV strings, read diagnostics information * KACO 10: fix possible NullPointerException when parsing IP Backend * Store server metrics in InfluxDB (e.g. backend0: edgewebsocket/Connections) * Improve log messages and error handling UI * Always show Overview page if user has access to multiple Edges * Changelog: show version or title (for UI pre-release); hide SNAPSHOT details for end-users * Latest improvements and bugfixes for EVCS Widget, App-Center, * Use "/demo" path for UI demo access (user 'admin', password 'admin') Co-authored-by: Michael Grill <59126309+michaelgrill@users.noreply.github.com> Co-authored-by: Lukas Rieger <73471197+lukasrgr@users.noreply.github.com> Co-authored-by: Maximilian Lang <35968713+mlang97@users.noreply.github.com> Co-authored-by: Sagar Venu <32655208+venu-sagar@users.noreply.github.com> Co-authored-by: Kai Jeschek <99220919+da-Kai@users.noreply.github.com> Co-authored-by: Mohammadmahdi Ataei <54065538+mahdiataie@users.noreply.github.com> Co-authored-by: Pooran Chandrashekaraiah <46567310+pooran-c@users.noreply.github.com> Co-authored-by: Sebastian Asen <47855186+sebastianasen@users.noreply.github.com> Co-authored-by: Stefan Feilmeier <3515268+sfeilmeier@users.noreply.github.com> Reviewed-by: Hueseyin Sahutoglu <34771592+huseyinsaht@users.noreply.github.com> Reviewed-by: Jan Seidemann <56681203+janseidemann@users.noreply.github.com> Reviewed-by: Lukas Rieger <73471197+lukasrgr@users.noreply.github.com> Reviewed-by: Michael Grill <59126309+michaelgrill@users.noreply.github.com> Reviewed-by: Sebastian Asen <47855186+sebastianasen@users.noreply.github.com> Reviewed-by: Stefan Feilmeier <3515268+sfeilmeier@users.noreply.github.com> --- .gitignore | 4 + doc/build/package.json | 2 +- doc/package-lock.json | 2 +- gradle.properties | 6 +- .../BackendApp.bndrun | 1 + .../backend/b2bwebsocket/WebsocketServer.java | 15 + .../common/metadata/AppCenterHandler.java | 244 ++++ .../common/metadata/AppCenterMetadata.java | 136 ++ .../openems/backend/common/metadata/User.java | 17 +- .../CoreJsonRpcRequestHandlerImpl.java | 5 + .../EdgeRpcRequestHandler.java | 10 +- .../edgewebsocket/EdgeWebsocketImpl.java | 22 +- .../openems/backend/edgewebsocket/OnOpen.java | 21 +- .../backend/edgewebsocket/OnRequest.java | 19 + .../edgewebsocket/SystemLogHandler.java | 5 +- .../edgewebsocket/WebsocketServer.java | 10 + .../backend/metadata/dummy/MetadataDummy.java | 26 +- .../backend/metadata/file/MetadataFile.java | 19 +- .../backend/metadata/odoo/MetadataOdoo.java | 108 +- .../openems/backend/metadata/odoo/MyUser.java | 6 +- .../metadata/odoo/odoo/OdooHandler.java | 212 ++- .../backend/metadata/odoo/odoo/OdooUtils.java | 8 +- .../metadata/odoo/postgres/PgUtils.java | 27 - .../aggregatedinflux/AllowedChannels.java | 26 +- .../timedata/influx/TimedataInfluxDb.java | 2 - .../timescaledb/TimedataTimescaleDb.java | 1 - .../backend/uiwebsocket/impl/OnRequest.java | 2 +- .../uiwebsocket/impl/UiWebsocketImpl.java | 17 + .../uiwebsocket/impl/WebsocketServer.java | 15 + .../backend/uiwebsocket/impl/WsData.java | 1 + .../src/io/openems/common/channel/Unit.java | 13 +- .../notification/CurrentDataNotification.java | 8 +- .../notification/ResendDataNotification.java | 6 +- .../response/AuthenticateResponse.java | 3 +- ...yHistoricTimeseriesExportXlsxResponse.java | 2 +- .../openems/common/session/AbstractUser.java | 12 +- .../timedata/CommonTimedataService.java | 12 +- .../openems/common/timedata/Resolution.java | 4 + .../io/openems/common/types/OptionsEnum.java | 44 + .../openems/common/utils/CollectorUtils.java | 24 + .../websocket/AbstractWebsocketClient.java | 3 +- .../websocket/ClientReconnectorWorker.java | 28 +- .../io/openems/common/websocket/WsData.java | 1 + .../openems/common/worker/AbstractWorker.java | 37 +- .../common/utils/CollectorUtilsTest.java | 28 + .../openems/common/utils/JsonUtilsTest.java | 20 + .../ClientReconnectorWorkerTest.java | 4 +- .../io/openems/edge/battery/api/Battery.java | 3 +- .../openems/edge/battery/bmw/BmwBattery.java | 2 + .../commercial/BatteryFeneconCommercial.java | 158 +-- .../BatteryFeneconCommercialImpl.java | 158 ++- .../DynamicChannelsAndSerialNumbersTest.java | 4 +- .../fenecon/home/BatteryFeneconHome.java | 24 + .../home/BatteryFeneconHomeHardwareType.java | 54 + .../fenecon/home/BatteryFeneconHomeImpl.java | 274 +++- ...va => FeneconHomeBatteryProtection52.java} | 2 +- .../home/FeneconHomeBatteryProtection64.java | 68 + .../home/BatteryFeneconHomeImplTest.java | 22 +- .../fenecon/home/TowersAndModulesTest.java | 55 +- .../BatterySoltaroClusterVersionC.java | 3 + .../BatterySoltaroClusterVersionCImpl.java | 12 - .../soltaro/cluster/versionc/RackChannel.java | 14 - .../BatterySoltaroSingleRackVersionC.java | 2 - .../BatterySoltaroSingleRackVersionCImpl.java | 1 - .../versionc/SoltaroBatteryVersionC.java | 143 ++ .../sinexcel/BatteryInverterSinexcel.java | 2 + .../sinexcel/BatteryInverterSinexcelImpl.java | 2 +- .../openems/edge/common/channel/Channel.java | 12 +- .../edge/common/channel/StateChannelDoc.java | 2 + .../common/channel/internal/AbstractDoc.java | 2 +- .../channel/internal/AbstractReadChannel.java | 23 +- .../component/AbstractOpenemsComponent.java | 5 +- .../src/io/openems/edge/common/meta/Meta.java | 7 +- .../modbusslave/ModbusRecordChannel.java | 8 +- .../edge/common/type/CircularTreeMap.java | 41 - .../src/io/openems/edge/common/user/User.java | 5 + .../edge/common/type/CircularTreeMapTest.java | 38 - .../edge/controller/api/backend/Config.java | 10 +- .../api/backend/ControllerApiBackend.java | 16 +- .../api/backend/ControllerApiBackendImpl.java | 24 +- .../edge/controller/api/backend/OnOpen.java | 3 + .../api/backend/ResendHistoricDataWorker.java | 252 ++++ .../api/backend/SendChannelValuesWorker.java | 179 ++- .../api/backend/WebsocketClient.java | 1 + .../backend/ControllerApiBackendImplTest.java | 5 +- .../edge/controller/api/backend/MyConfig.java | 22 + .../backend/ResendHistoricDataWorkerTest.java | 43 + .../backend/SendChannelValuesWorkerTest.java | 55 + ...ControllerEssTimeOfUseTariffDischarge.java | 2 + .../evcs/fixactivepower/Config.java | 3 + .../ControllerEvcsFixActivePowerImpl.java | 23 +- .../ControllerEvcsFixActivePowerImplTest.java | 1 + .../evcs/fixactivepower/MyConfig.java | 13 +- .../evcs/ControllerEvcsImplTest.java | 1 - .../ControllerIoHeatingElement.java | 7 +- .../sgready/ControllerIoHeatPumpSgReady.java | 5 +- .../ControllerIoHeatPumpSgReadyImpl.java | 36 +- .../edge/app/api/ModbusTcpApiReadWrite.java | 15 +- .../src/io/openems/edge/app/api/MqttApi.java | 7 +- .../edge/app/api/RestJsonApiReadOnly.java | 2 +- .../edge/app/api/RestJsonApiReadWrite.java | 9 +- .../edge/app/common/props/CommonProps.java | 92 +- .../app/common/props/CommunicationProps.java | 215 ++- .../edge/app/common/props/ComponentProps.java | 255 ++++ .../edge/app/common/props/PropsUtil.java | 21 + .../io/openems/edge/app/enums/MeterType.java | 28 + .../io/openems/edge/app/enums/ModbusType.java | 24 + .../edge/app/enums/OptionsFactory.java | 10 +- .../edge/app/enums/TranslatableEnum.java | 11 + .../edge/app/ess/PrepareBatteryExtension.java | 2 +- .../edge/app/evcs/AbstractEvcsApp.java | 43 - .../openems/edge/app/evcs/AlpitronicEvcs.java | 330 +++++ .../io/openems/edge/app/evcs/DezonyEvcs.java | 185 +++ .../io/openems/edge/app/evcs/EvcsCluster.java | 210 ++- .../io/openems/edge/app/evcs/EvcsProps.java | 185 +++ .../openems/edge/app/evcs/HardyBarthEvcs.java | 114 +- .../openems/edge/app/evcs/IesKeywattEvcs.java | 158 ++- .../io/openems/edge/app/evcs/KebaEvcs.java | 120 +- .../edge/app/hardware/KMtronic8Channel.java | 110 +- .../edge/app/heat/CombinedHeatAndPower.java | 135 +- .../io/openems/edge/app/heat/HeatProps.java | 209 +++ .../io/openems/edge/app/heat/HeatPump.java | 149 +- .../openems/edge/app/heat/HeatingElement.java | 212 +-- .../app/integratedsystem/FeneconHome.java | 32 +- .../edge/app/integratedsystem/FeneconHome.png | Bin 70955 -> 38175 bytes .../app/loadcontrol/ManualRelayControl.java | 25 +- .../app/loadcontrol/ThresholdControl.java | 24 +- .../edge/app/meter/AbstractMeterApp.java | 4 - .../edge/app/meter/CarloGavazziMeter.java | 17 +- .../openems/edge/app/meter/JanitzaMeter.java | 265 ++-- .../io/openems/edge/app/meter/KdkMeter.java | 146 +- .../io/openems/edge/app/meter/MeterProps.java | 72 + .../edge/app/meter/MicrocareSdm630Meter.java | 186 +++ .../openems/edge/app/meter/SocomecMeter.java | 142 +- .../edge/app/peakshaving/PeakShaving.java | 179 +++ .../app/peakshaving/PeakShavingProps.java | 110 ++ .../peakshaving/PhaseAccuratePeakShaving.java | 181 +++ .../app/pvinverter/AbstractPvInverter.java | 18 +- .../CommonPvInverterConfiguration.java | 41 +- .../edge/app/pvinverter/SmaPvInverter.java | 14 +- .../GridOptimizedCharge.java | 9 +- .../SelfConsumptionOptimization.java | 11 +- .../timeofusetariff/StromdaoCorrently.java | 2 +- .../edge/app/timeofusetariff/Tibber.java | 7 +- .../core/appmanager/AbstractOpenemsApp.java | 158 ++- .../AbstractOpenemsAppWithProps.java | 74 +- .../core/appmanager/AppCenterBackendUtil.java | 2 +- .../appmanager/AppCenterBackendUtilImpl.java | 48 +- .../openems/edge/core/appmanager/AppDef.java | 286 +++- .../core/appmanager/AppInstallWorker.java | 2 +- .../edge/core/appmanager/AppManagerImpl.java | 357 +++-- .../edge/core/appmanager/AppManagerUtil.java | 74 +- .../core/appmanager/AppManagerUtilImpl.java | 15 +- .../appmanager/AppManagerUtilSupplier.java | 12 + .../appmanager/ComponentManagerSupplier.java | 14 + .../edge/core/appmanager/ComponentUtil.java | 218 ++- .../core/appmanager/ComponentUtilImpl.java | 282 ++-- .../appmanager/ComponentUtilSupplier.java | 12 + .../edge/core/appmanager/JsonFormlyUtil.java | 1212 ----------------- .../edge/core/appmanager/Nameable.java | 54 + .../edge/core/appmanager/OpenemsApp.java | 29 +- .../core/appmanager/OpenemsAppCategory.java | 5 + .../core/appmanager/OpenemsAppStatus.java | 20 + .../core/appmanager/ResolveDependencies.java | 24 +- .../edge/core/appmanager/TranslationUtil.java | 26 +- .../io/openems/edge/core/appmanager/Type.java | 65 +- .../dependency/AppManagerAppHelperImpl.java | 360 +++-- .../ComponentAggregateTaskImpl.java | 10 +- .../appmanager/dependency/DependencyUtil.java | 49 +- .../appmanager/dependency/UpdateValues.java | 7 +- .../edge/core/appmanager/flag/Flag.java | 27 + .../edge/core/appmanager/flag/Flags.java | 15 + .../edge/core/appmanager/formly/Case.java | 31 + .../formly/DefaultValueOptions.java | 35 + .../edge/core/appmanager/formly/Exp.java | 149 ++ .../appmanager/formly/JsonFormlyUtil.java | 197 +++ .../formly/builder/CheckboxBuilder.java | 73 + .../formly/builder/FieldGroupBuilder.java | 67 + .../formly/builder/FormlyBuilder.java | 354 +++++ .../formly/builder/InputBuilder.java | 235 ++++ .../formly/builder/RangeBuilder.java | 49 + .../formly/builder/RepeatBuilder.java | 62 + .../formly/builder/SelectBuilder.java | 126 ++ .../formly/builder/SelectGroupBuilder.java | 86 ++ .../formly/builder/TextBuilder.java | 19 + .../formly/builder/selectgroup/Option.java | 50 + .../builder/selectgroup/OptionBuilder.java | 66 + .../selectgroup/OptionExpressions.java | 37 + .../builder/selectgroup/OptionGroup.java | 51 + .../selectgroup/OptionGroupBuilder.java | 47 + .../appmanager/formly/enums/DisplayType.java | 20 + .../appmanager/formly/enums/InputType.java | 18 + .../appmanager/formly/enums/Operator.java | 27 + .../appmanager/formly/enums/Validation.java | 26 + .../appmanager/formly/enums/Wrappers.java | 38 + .../formly/expression/ArrayExpression.java | 89 ++ .../formly/expression/BooleanExpression.java | 42 + .../formly/expression/StringExpression.java | 33 + .../formly/expression/Variable.java | 115 ++ .../edge/core/appmanager/jsonrpc/GetApp.java | 6 + .../core/appmanager/translation_de.properties | 127 +- .../core/appmanager/translation_en.properties | 127 +- .../validator/CheckAppsNotInstalled.java | 14 +- .../validator/CheckCardinality.java | 25 +- .../appmanager/validator/CheckRelayCount.java | 10 +- .../core/appmanager/validator/Checkables.java | 68 + .../appmanager/validator/ValidatorConfig.java | 40 +- .../appmanager/validator/ValidatorImpl.java | 12 +- .../validator/translation_de.properties | 2 +- .../validator/translation_en.properties | 2 +- .../host/OperatingSystemDebianSystemd.java | 10 +- .../edge/core/host/SystemUpdateHandler.java | 8 +- .../edge/app/evcs/TestEvcsCluster.java | 371 +++++ .../openems/edge/app/heat/TestHeatPump.java | 5 +- .../AppManagerImpSynchronizationTest.java | 191 +++ .../core/appmanager/AppManagerImplTest.java | 2 +- .../core/appmanager/AppManagerTestBundle.java | 211 ++- .../io/openems/edge/core/appmanager/Apps.java | 47 +- .../appmanager/ComponentUtilImplTest.java | 92 ++ .../appmanager/DummyAppManagerAppHelper.java | 104 ++ .../DummyPseudoComponentManager.java | 422 ++++++ .../edge/core/appmanager/DummyValidator.java | 4 +- .../core/appmanager/GetAppAssistantTest.java | 57 + .../edge/core/appmanager/MyConfig.java | 2 +- io.openems.edge.ess.cluster/bnd.bnd | 2 +- .../edge/ess/cluster/ChannelManager.java | 18 +- .../io/openems/edge/ess/cluster/Config.java | 7 +- .../openems/edge/ess/cluster/EssCluster.java | 3 +- .../edge/ess/cluster/EssClusterImpl.java | 89 +- .../edge/ess/cluster/EssClusterImplTest.java | 23 +- .../io/openems/edge/ess/cluster/MyConfig.java | 17 +- .../sma/sunnyisland/EssSmaSunnyIsland.java | 4 +- .../openems/edge/evcs/api/WriteHandler.java | 3 +- .../io/openems/edge/evcs/cluster/Config.java | 2 +- .../cluster/EvcsClusterPeakShavingImpl.java | 1 + .../edge/evcs/keba/kecontact/Config.java | 3 + .../keba/kecontact/EvcsKebaKeContactImpl.java | 3 + .../edge/evcs/keba/kecontact/ReadHandler.java | 7 +- .../kecontact/EvcsKebaKeContactImplTest.java | 1 + .../edge/evcs/keba/kecontact/MyConfig.java | 11 + .../GoodWeBatteryInverterImpl.java | 72 +- .../charger/AbstractGoodWeEtCharger.java | 5 + .../edge/goodwe/charger/GoodWeCharger.java | 3 +- .../charger/{ => singlestring}/ConfigPV1.java | 2 +- .../charger/{ => singlestring}/ConfigPV2.java | 2 +- .../{ => singlestring}/GoodWeChargerPv1.java | 5 +- .../{ => singlestring}/GoodWeChargerPv2.java | 5 +- .../edge/goodwe/charger/twostring/Config.java | 41 + .../twostring/GoodWeChargerTwoString.java | 48 + .../twostring/GoodWeChargerTwoStringImpl.java | 230 ++++ .../edge/goodwe/charger/twostring/PvPort.java | 26 + .../edge/goodwe/common/AbstractGoodWe.java | 148 +- .../edge/goodwe/common/ApplyPowerHandler.java | 18 +- .../io/openems/edge/goodwe/common/GoodWe.java | 285 +++- .../goodwe/common/enums/BatteryProtocol.java | 3 +- .../common/enums/DiagnosticStatusHigh.java | 35 - .../common/enums/DiagnosticStatusLow.java | 58 - ...eterType.java => GoodWeGridMeterType.java} | 4 +- .../common/enums/GoodWeHardwareType.java | 86 ++ .../{GoodweType.java => GoodWeType.java} | 4 +- .../goodwe/gridmeter/GoodWeGridMeterImpl.java | 61 +- .../GoodWeBatteryInverterImplTest.java | 6 +- .../goodwe/batteryinverter/TestStatic.java | 54 + .../GoodWeChargerPv1Test.java | 2 +- .../GoodWeChargerPv2Test.java | 2 +- .../charger/{ => singlestring}/MyConfig.java | 2 +- .../GoodWeChargerTwoStringImplTest.java | 29 + .../goodwe/charger/twostring/MyConfig.java | 99 ++ .../charger/twostring/RuleOfThreeTest.java | 80 ++ .../edge/goodwe/common/TestStatic.java | 113 ++ .../edge/goodwe/ess/GoodWeEssImplTest.java | 4 +- .../io/openems/edge/wago/Fieldbus4xxDI.java | 2 +- .../openems/edge/wago/Fieldbus523RO1Ch.java | 2 +- .../io/openems/edge/wago/Fieldbus5xxDO.java | 2 +- .../io/weidmueller/FieldbusChannelId.java | 2 +- .../core/KacoBlueplanetHybrid10CoreImpl.java | 2 +- .../ess/KacoBlueplanetHybrid10Ess.java | 2 +- .../edge/meter/api/ElectricityMeter.java | 16 +- io.openems.edge.meter.bgetech/bnd.bnd | 2 +- .../edge/meter/phoenixcontact/MyConfig.java | 1 - .../edge/meter/virtual/add/MyConfig.java | 1 - .../edge/meter/virtual/subtract/MyConfig.java | 1 - .../SchedulerFixedOrderImplTest.java | 3 +- .../edge/simulator/app/SimulatorAppImpl.java | 13 + .../timedata/SimulatorTimedataImpl.java | 13 + .../openems/edge/timedata/api/Timedata.java | 37 + .../openems/edge/timedata/api/Timeranges.java | 246 ++++ .../edge/timedata/test/DummyTimedata.java | 16 + .../edge/timedata/api/TimerangesTest.java | 136 ++ .../influxdb/TimedataInfluxDbImpl.java | 17 + .../edge/timedata/rrd4j/ChannelDef.java | 13 + .../openems/edge/timedata/rrd4j/Config.java | 8 +- .../edge/timedata/rrd4j/RecordWorker.java | 301 ++-- .../edge/timedata/rrd4j/Rrd4jConstants.java | 25 + .../edge/timedata/rrd4j/Rrd4jReadHandler.java | 624 +++++++++ .../edge/timedata/rrd4j/Rrd4jSupplier.java | 383 ++++++ .../timedata/rrd4j/TimedataRrd4jImpl.java | 550 +------- .../rrd4j/version/AbstractVersion.java | 18 + .../edge/timedata/rrd4j/version/Version.java | 109 ++ .../edge/timedata/rrd4j/version/Version1.java | 62 + .../edge/timedata/rrd4j/version/Version2.java | 94 ++ .../edge/timedata/rrd4j/version/Version3.java | 166 +++ .../rrd4j/version/VersionHandler.java | 119 ++ .../openems/edge/timedata/rrd4j/MyConfig.java | 23 + .../timedata/rrd4j/Rrd4jReadHandlerTest.java | 50 + .../timedata/rrd4j/Rrd4jSupplierTest.java | 24 + .../timedata/rrd4j/TimedataRrd4jImplTest.java | 26 +- .../timedata/rrd4j/version/Version3Test.java | 174 +++ .../rrd4j/version/VersionHandlerTest.java | 37 + .../timedata/rrd4j/version/VersionTest.java | 49 + .../edge/timeofusetariff/entsoe/MyConfig.java | 1 - .../shared/influxdb/proxy/InfluxQlProxy.java | 2 +- tools/build-debian-package.sh | 90 ++ tools/common.sh | 75 + tools/create-release.sh | 118 ++ tools/debian/DEBIAN/control | 9 + tools/debian/DEBIAN/postinst | 55 + tools/debian/etc/nginx/conf.d/80.conf | 45 + .../debian/etc/systemd/system/openems.service | 27 + tools/increase-patch-version.sh | 33 + tools/prepare-commit.sh | 1 + tools/prepare-next-snapshot.sh | 8 +- tools/prepare-release.sh | 8 +- ui/package-lock.json | 12 +- .../view/component/changelog.component.html | 7 +- .../view/component/changelog.component.ts | 3 +- .../view/component/changelog.constants.ts | 6 +- .../live/Controller/Evcs/modal/modal.html | 13 +- .../edge/live/Controller/Evcs/modal/modal.ts | 73 +- ui/src/app/edge/settings/app/app.module.ts | 7 +- .../formly-option-group-picker.component.html | 26 + .../formly-option-group-picker.component.ts | 69 + .../optionGroupPickerConfiguration.ts | 30 + .../formly-safe-input-modal.component.html | 2 +- .../formly-safe-input.extended.html | 13 +- .../safe-input/formly-safe-input.extended.ts | 68 +- .../edge/settings/app/index.component.html | 3 +- .../app/edge/settings/app/index.component.ts | 44 +- .../edge/settings/app/install.component.ts | 61 +- .../settings/app/jsonrpc/getAppAssistant.ts | 84 +- .../settings/app/keypopup/modal.component.ts | 76 +- ui/src/app/edge/settings/app/permissions.ts | 3 - .../app/edge/settings/app/single.component.ts | 24 +- .../app/edge/settings/app/update.component.ts | 25 +- .../settings/profile/profile.component.html | 2 +- ui/src/app/index/index.component.ts | 9 +- ui/src/app/shared/edge/edge.ts | 9 +- .../form-field-default-cases.wrapper.ts | 76 +- .../genericComponents/modal/abstractModal.ts | 10 +- .../modal/modal-line/modal-line.html | 6 +- .../modal/modal-line/modal-line.ts | 2 +- .../request/submitSetupProtocolRequest.ts | 148 -- ui/src/app/shared/pipe/pipe.ts | 6 +- .../shared/pipe/version/version.pipe.spec.ts | 25 + .../app/shared/pipe/version/version.pipe.ts | 28 + ui/src/app/shared/service/websocket.ts | 6 +- ui/src/app/user/user.component.html | 13 +- ui/src/app/user/user.component.ts | 5 +- ui/src/assets/i18n/de.json | 282 +++- ui/src/assets/i18n/en.json | 281 +++- 360 files changed, 17517 insertions(+), 4586 deletions(-) create mode 100644 io.openems.backend.common/src/io/openems/backend/common/metadata/AppCenterHandler.java create mode 100644 io.openems.backend.common/src/io/openems/backend/common/metadata/AppCenterMetadata.java create mode 100644 io.openems.common/test/io/openems/common/utils/CollectorUtilsTest.java create mode 100644 io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeHardwareType.java rename io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/{FeneconHomeBatteryProtection.java => FeneconHomeBatteryProtection52.java} (93%) create mode 100644 io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/FeneconHomeBatteryProtection64.java create mode 100644 io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/versionc/SoltaroBatteryVersionC.java delete mode 100644 io.openems.edge.common/src/io/openems/edge/common/type/CircularTreeMap.java delete mode 100644 io.openems.edge.common/test/io/openems/edge/common/type/CircularTreeMapTest.java create mode 100644 io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/ResendHistoricDataWorker.java create mode 100644 io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/ResendHistoricDataWorkerTest.java create mode 100644 io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/SendChannelValuesWorkerTest.java create mode 100644 io.openems.edge.core/src/io/openems/edge/app/common/props/ComponentProps.java create mode 100644 io.openems.edge.core/src/io/openems/edge/app/common/props/PropsUtil.java create mode 100644 io.openems.edge.core/src/io/openems/edge/app/enums/MeterType.java create mode 100644 io.openems.edge.core/src/io/openems/edge/app/enums/ModbusType.java delete mode 100644 io.openems.edge.core/src/io/openems/edge/app/evcs/AbstractEvcsApp.java create mode 100644 io.openems.edge.core/src/io/openems/edge/app/evcs/AlpitronicEvcs.java create mode 100644 io.openems.edge.core/src/io/openems/edge/app/evcs/DezonyEvcs.java create mode 100644 io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsProps.java create mode 100644 io.openems.edge.core/src/io/openems/edge/app/heat/HeatProps.java create mode 100644 io.openems.edge.core/src/io/openems/edge/app/meter/MeterProps.java create mode 100644 io.openems.edge.core/src/io/openems/edge/app/meter/MicrocareSdm630Meter.java create mode 100644 io.openems.edge.core/src/io/openems/edge/app/peakshaving/PeakShaving.java create mode 100644 io.openems.edge.core/src/io/openems/edge/app/peakshaving/PeakShavingProps.java create mode 100644 io.openems.edge.core/src/io/openems/edge/app/peakshaving/PhaseAccuratePeakShaving.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerUtilSupplier.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/ComponentManagerSupplier.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/ComponentUtilSupplier.java delete mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/JsonFormlyUtil.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppStatus.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/flag/Flag.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/flag/Flags.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/Case.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/DefaultValueOptions.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/Exp.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/JsonFormlyUtil.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/CheckboxBuilder.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/FieldGroupBuilder.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/FormlyBuilder.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/InputBuilder.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/RangeBuilder.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/RepeatBuilder.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/SelectBuilder.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/SelectGroupBuilder.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/TextBuilder.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/selectgroup/Option.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/selectgroup/OptionBuilder.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/selectgroup/OptionExpressions.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/selectgroup/OptionGroup.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/selectgroup/OptionGroupBuilder.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/enums/DisplayType.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/enums/InputType.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/enums/Operator.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/enums/Validation.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/enums/Wrappers.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/expression/ArrayExpression.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/expression/BooleanExpression.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/expression/StringExpression.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/expression/Variable.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Checkables.java create mode 100644 io.openems.edge.core/test/io/openems/edge/app/evcs/TestEvcsCluster.java create mode 100644 io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerImpSynchronizationTest.java create mode 100644 io.openems.edge.core/test/io/openems/edge/core/appmanager/ComponentUtilImplTest.java create mode 100644 io.openems.edge.core/test/io/openems/edge/core/appmanager/DummyAppManagerAppHelper.java create mode 100644 io.openems.edge.core/test/io/openems/edge/core/appmanager/DummyPseudoComponentManager.java create mode 100644 io.openems.edge.core/test/io/openems/edge/core/appmanager/GetAppAssistantTest.java rename io.openems.edge.goodwe/src/io/openems/edge/goodwe/charger/{ => singlestring}/ConfigPV1.java (96%) rename io.openems.edge.goodwe/src/io/openems/edge/goodwe/charger/{ => singlestring}/ConfigPV2.java (96%) rename io.openems.edge.goodwe/src/io/openems/edge/goodwe/charger/{ => singlestring}/GoodWeChargerPv1.java (95%) rename io.openems.edge.goodwe/src/io/openems/edge/goodwe/charger/{ => singlestring}/GoodWeChargerPv2.java (95%) create mode 100644 io.openems.edge.goodwe/src/io/openems/edge/goodwe/charger/twostring/Config.java create mode 100644 io.openems.edge.goodwe/src/io/openems/edge/goodwe/charger/twostring/GoodWeChargerTwoString.java create mode 100644 io.openems.edge.goodwe/src/io/openems/edge/goodwe/charger/twostring/GoodWeChargerTwoStringImpl.java create mode 100644 io.openems.edge.goodwe/src/io/openems/edge/goodwe/charger/twostring/PvPort.java delete mode 100644 io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/enums/DiagnosticStatusHigh.java delete mode 100644 io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/enums/DiagnosticStatusLow.java rename io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/enums/{GoodweGridMeterType.java => GoodWeGridMeterType.java} (85%) create mode 100644 io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/enums/GoodWeHardwareType.java rename io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/enums/{GoodweType.java => GoodWeType.java} (90%) create mode 100644 io.openems.edge.goodwe/test/io/openems/edge/goodwe/batteryinverter/TestStatic.java rename io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/{ => singlestring}/GoodWeChargerPv1Test.java (93%) rename io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/{ => singlestring}/GoodWeChargerPv2Test.java (93%) rename io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/{ => singlestring}/MyConfig.java (97%) create mode 100644 io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/twostring/GoodWeChargerTwoStringImplTest.java create mode 100644 io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/twostring/MyConfig.java create mode 100644 io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/twostring/RuleOfThreeTest.java create mode 100644 io.openems.edge.goodwe/test/io/openems/edge/goodwe/common/TestStatic.java create mode 100644 io.openems.edge.timedata.api/src/io/openems/edge/timedata/api/Timeranges.java create mode 100644 io.openems.edge.timedata.api/test/io/openems/edge/timedata/api/TimerangesTest.java create mode 100644 io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/ChannelDef.java create mode 100644 io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/Rrd4jConstants.java create mode 100644 io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/Rrd4jReadHandler.java create mode 100644 io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/Rrd4jSupplier.java create mode 100644 io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/version/AbstractVersion.java create mode 100644 io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/version/Version.java create mode 100644 io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/version/Version1.java create mode 100644 io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/version/Version2.java create mode 100644 io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/version/Version3.java create mode 100644 io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/version/VersionHandler.java create mode 100644 io.openems.edge.timedata.rrd4j/test/io/openems/edge/timedata/rrd4j/Rrd4jReadHandlerTest.java create mode 100644 io.openems.edge.timedata.rrd4j/test/io/openems/edge/timedata/rrd4j/Rrd4jSupplierTest.java create mode 100644 io.openems.edge.timedata.rrd4j/test/io/openems/edge/timedata/rrd4j/version/Version3Test.java create mode 100644 io.openems.edge.timedata.rrd4j/test/io/openems/edge/timedata/rrd4j/version/VersionHandlerTest.java create mode 100644 io.openems.edge.timedata.rrd4j/test/io/openems/edge/timedata/rrd4j/version/VersionTest.java create mode 100755 tools/build-debian-package.sh create mode 100644 tools/common.sh create mode 100644 tools/create-release.sh create mode 100755 tools/debian/DEBIAN/control create mode 100755 tools/debian/DEBIAN/postinst create mode 100644 tools/debian/etc/nginx/conf.d/80.conf create mode 100644 tools/debian/etc/systemd/system/openems.service create mode 100644 tools/increase-patch-version.sh create mode 100644 ui/src/app/edge/settings/app/formly/option-group-picker/formly-option-group-picker.component.html create mode 100644 ui/src/app/edge/settings/app/formly/option-group-picker/formly-option-group-picker.component.ts create mode 100644 ui/src/app/edge/settings/app/formly/option-group-picker/optionGroupPickerConfiguration.ts delete mode 100644 ui/src/app/shared/jsonrpc/request/submitSetupProtocolRequest.ts create mode 100644 ui/src/app/shared/pipe/version/version.pipe.spec.ts create mode 100644 ui/src/app/shared/pipe/version/version.pipe.ts diff --git a/.gitignore b/.gitignore index 09d65ab7bb8..18551ca6ac6 100644 --- a/.gitignore +++ b/.gitignore @@ -118,6 +118,7 @@ local.properties *.zip *.tar.gz *.rar +/*.deb # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* @@ -228,3 +229,6 @@ gradle-app.setting # OpenEMS temp files io.openems.edge.controller.api.mqtt/edge0 io.openems.edge.application/c:/ + +### Debian ### +!tools/debian/** \ No newline at end of file diff --git a/doc/build/package.json b/doc/build/package.json index 302f26467d1..72792e2cf0b 100644 --- a/doc/build/package.json +++ b/doc/build/package.json @@ -6,4 +6,4 @@ "@antora/site-generator-default": "^3.1.1" }, "devDependencies": {} -} +} \ No newline at end of file diff --git a/doc/package-lock.json b/doc/package-lock.json index 48e341a0954..64f5ffc722a 100644 --- a/doc/package-lock.json +++ b/doc/package-lock.json @@ -1,3 +1,3 @@ { "lockfileVersion": 1 -} +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 9d7a13c134b..39c57047bad 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,6 +5,6 @@ bnd_releases=https://bndtools.jfrog.io/bndtools/libs-release-local maven.repo.local=cnf org.gradle.caching=true -org.gradle.parallel=true -org.gradle.workers.max=4 -org.gradle.jvmargs=-Xms512m -Xmx1024m "-XX:MaxMetaspaceSize=256m" +org.gradle.parallel=false +org.gradle.workers.max=1 +org.gradle.jvmargs=-Xms256m -Xmx512m "-XX:MaxMetaspaceSize=256m" diff --git a/io.openems.backend.application/BackendApp.bndrun b/io.openems.backend.application/BackendApp.bndrun index 6ba1cde1518..b447b3a8a76 100644 --- a/io.openems.backend.application/BackendApp.bndrun +++ b/io.openems.backend.application/BackendApp.bndrun @@ -41,6 +41,7 @@ bnd.identity;id='org.apache.felix.eventadmin',\ bnd.identity;id='org.apache.felix.fileinstall',\ bnd.identity;id='org.apache.felix.metatype',\ + bnd.identity;id='io.openems.wrapper.pgbulkinsert',\ bnd.identity;id='io.openems.backend.alerting',\ bnd.identity;id='io.openems.backend.application',\ bnd.identity;id='io.openems.backend.b2brest',\ diff --git a/io.openems.backend.b2bwebsocket/src/io/openems/backend/b2bwebsocket/WebsocketServer.java b/io.openems.backend.b2bwebsocket/src/io/openems/backend/b2bwebsocket/WebsocketServer.java index 3f83ecb0526..690398fde27 100644 --- a/io.openems.backend.b2bwebsocket/src/io/openems/backend/b2bwebsocket/WebsocketServer.java +++ b/io.openems.backend.b2bwebsocket/src/io/openems/backend/b2bwebsocket/WebsocketServer.java @@ -1,7 +1,15 @@ package io.openems.backend.b2bwebsocket; +import java.time.Instant; + import org.slf4j.Logger; +import com.google.common.collect.TreeBasedTable; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; + +import io.openems.common.jsonrpc.notification.TimestampedDataNotification; +import io.openems.common.utils.ThreadPoolUtils; import io.openems.common.websocket.AbstractWebsocketServer; public class WebsocketServer extends AbstractWebsocketServer { @@ -15,6 +23,13 @@ public class WebsocketServer extends AbstractWebsocketServer { public WebsocketServer(Backend2BackendWebsocket parent, String name, int port, int poolSize, DebugMode debugMode) { super(name, port, poolSize, debugMode, (executor) -> { + // Store Metrics + var data = TreeBasedTable.create(); + var now = Instant.now().toEpochMilli(); + ThreadPoolUtils.debugMetrics(executor).forEach((key, value) -> { + data.put(now, "b2bwebsocket/" + key, new JsonPrimitive(value)); + }); + parent.timedataManager.write("backend0", new TimestampedDataNotification(data)); }); this.parent = parent; this.onOpen = new OnOpen(parent); diff --git a/io.openems.backend.common/src/io/openems/backend/common/metadata/AppCenterHandler.java b/io.openems.backend.common/src/io/openems/backend/common/metadata/AppCenterHandler.java new file mode 100644 index 00000000000..3578b21106d --- /dev/null +++ b/io.openems.backend.common/src/io/openems/backend/common/metadata/AppCenterHandler.java @@ -0,0 +1,244 @@ +package io.openems.backend.common.metadata; + +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.function.ThrowingBiConsumer; +import io.openems.common.function.ThrowingBiFunction; +import io.openems.common.function.ThrowingFunction; +import io.openems.common.jsonrpc.base.GenericJsonrpcResponseSuccess; +import io.openems.common.jsonrpc.base.JsonrpcRequest; +import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess; +import io.openems.common.jsonrpc.request.AddAppInstanceRequest; +import io.openems.common.jsonrpc.request.AppCenterAddDeinstallInstanceHistoryRequest; +import io.openems.common.jsonrpc.request.AppCenterAddInstallInstanceHistoryRequest; +import io.openems.common.jsonrpc.request.AppCenterAddRegisterKeyHistoryRequest; +import io.openems.common.jsonrpc.request.AppCenterAddUnregisterKeyHistoryRequest; +import io.openems.common.jsonrpc.request.AppCenterGetInstalledAppsRequest; +import io.openems.common.jsonrpc.request.AppCenterGetPossibleAppsRequest; +import io.openems.common.jsonrpc.request.AppCenterGetRegisteredKeysRequest; +import io.openems.common.jsonrpc.request.AppCenterInstallAppWithSuppliedKeyRequest; +import io.openems.common.jsonrpc.request.AppCenterIsAppFreeRequest; +import io.openems.common.jsonrpc.request.AppCenterIsKeyApplicableRequest; +import io.openems.common.jsonrpc.request.AppCenterRequest; +import io.openems.common.jsonrpc.request.ComponentJsonApiRequest; +import io.openems.common.jsonrpc.request.EdgeRpcRequest; +import io.openems.common.jsonrpc.response.AppCenterGetInstalledAppsResponse; +import io.openems.common.jsonrpc.response.AppCenterGetPossibleAppsResponse; +import io.openems.common.jsonrpc.response.AppCenterGetRegisteredKeysResponse; +import io.openems.common.jsonrpc.response.AppCenterIsAppFreeResponse; +import io.openems.common.jsonrpc.response.AppCenterIsKeyApplicableResponse; + +public final class AppCenterHandler { + + private static Logger LOG = LoggerFactory.getLogger(AppCenterHandler.class); + + private AppCenterHandler() { + super(); + } + + /** + * Handles a user rpc request regarding app center keys. + * + * @param metadata the metadata to handle the request with + * @param delegatedRequest the function to delegate the current request to a + * other {@link EdgeRpcRequest} + * @param request the {@link AppCenterRequest} + * @param user the user + * @param edgeId the edge id + * @return the {@link CompletableFuture} + * @throws OpenemsNamedException on error + */ + public static CompletableFuture handleUserRequest(// + final AppCenterMetadata.UiData metadata, // + final ThrowingFunction, OpenemsNamedException> delegatedRequest, // + final AppCenterRequest request, // + final User user, // + final String edgeId // + ) throws OpenemsNamedException { + Objects.requireNonNull(metadata, "No AppCenter Metadata provided."); + + CompletableFuture resultFuture = null; + + switch (request.getPayload().getMethod()) { + case AppCenterAddRegisterKeyHistoryRequest.METHOD: + resultFuture = handle(metadata, request, // + AppCenterAddRegisterKeyHistoryRequest::from, // + (m, r) -> m.sendAddRegisterKeyHistory(edgeId, r.appId, r.key, user)); + break; + + case AppCenterAddUnregisterKeyHistoryRequest.METHOD: + resultFuture = handle(metadata, request, // + AppCenterAddUnregisterKeyHistoryRequest::from, // + (m, r) -> m.sendAddUnregisterKeyHistory(edgeId, r.appId, r.key, user)); + break; + + case AppCenterGetRegisteredKeysRequest.METHOD: + resultFuture = handle(metadata, request, // + AppCenterGetRegisteredKeysRequest::from, // + (m, r) -> m.sendGetRegisteredKeys(edgeId, r.appId), // + AppCenterGetRegisteredKeysResponse::new); + break; + case AppCenterInstallAppWithSuppliedKeyRequest.METHOD: + resultFuture = handleAppCenterInstallAppWithSuppliedKeyRequest(// + request.getPayload(), // + delegatedRequest, // + metadata, // + user, // + edgeId // + ); + break; + case AppCenterIsAppFreeRequest.METHOD: + resultFuture = handle(metadata, request, // + AppCenterIsAppFreeRequest::from, // + (m, r) -> m.isAppFree(user, r.appId), // + AppCenterIsAppFreeResponse::new); + break; + } + + return resultFuture != null ? resultFuture : handleGeneric(metadata, request, edgeId); + } + + private static CompletableFuture handleAppCenterInstallAppWithSuppliedKeyRequest(// + final JsonrpcRequest request, // + final ThrowingFunction, OpenemsNamedException> delegatedRequest, // + final AppCenterMetadata.UiData metadata, // + final User user, // + final String edgeId // + ) throws OpenemsNamedException { + final var genericInstallRequest = AppCenterInstallAppWithSuppliedKeyRequest.from(request).installRequest; + final var componentRequest = ComponentJsonApiRequest.from(genericInstallRequest); + final var installAppRequest = AddAppInstanceRequest.from(componentRequest.getPayload()); + String key = installAppRequest.key; + if (key == null) { + key = metadata.getSuppliableKey(user, edgeId, installAppRequest.appId); + } + if (key == null) { + throw new OpenemsException("Unable to provide key!"); + } + + final var installRequest = new EdgeRpcRequest(edgeId, // + new ComponentJsonApiRequest(componentRequest.getComponentId(), // + new AddAppInstanceRequest(installAppRequest.appId, key, installAppRequest.alias, + installAppRequest.properties) // + )// + ); + return delegatedRequest.apply(installRequest); + } + + /** + * Handles a edge rpc request regarding app center keys. + * + * @param metadata the metadata to handle the request with + * @param request the {@link AppCenterRequest} + * @param edgeId the edge id + * @return the {@link CompletableFuture} + * @throws OpenemsNamedException on error + */ + public static CompletableFuture handleEdgeRequest(// + final AppCenterMetadata.EdgeData metadata, // + final AppCenterRequest request, // + final String edgeId // + ) throws OpenemsNamedException { + Objects.requireNonNull(metadata, "No AppCenter Metadata provided."); + + CompletableFuture resultFuture = null; + + switch (request.getPayload().getMethod()) { + case AppCenterAddInstallInstanceHistoryRequest.METHOD: + resultFuture = handle(metadata, request, // + AppCenterAddInstallInstanceHistoryRequest::from, // + (m, r) -> m.sendAddInstallAppInstanceHistory(r.key, edgeId, r.appId, r.instanceId, r.userId)); + break; + + case AppCenterAddDeinstallInstanceHistoryRequest.METHOD: + resultFuture = handle(metadata, request, // + AppCenterAddDeinstallInstanceHistoryRequest::from, // + (m, r) -> m.sendAddDeinstallAppInstanceHistory(edgeId, r.appId, r.instanceId, r.userId)); + break; + + case AppCenterGetInstalledAppsRequest.METHOD: + resultFuture = handle(metadata, request, // + AppCenterGetInstalledAppsRequest::from, // + (m, r) -> m.sendGetInstalledApps(edgeId), // + AppCenterGetInstalledAppsResponse::from); + break; + } + + return resultFuture != null ? resultFuture : handleGeneric(metadata, request, edgeId); + } + + /** + * Handles a generic rpc request regarding app center keys. + * + * @param metadata the metadata to handle the request with + * @param request the {@link AppCenterRequest} + * @param edgeId the edge id + * @return the {@link CompletableFuture} + * @throws OpenemsNamedException on error + */ + public static CompletableFuture handleGeneric(// + final AppCenterMetadata metadata, // + final AppCenterRequest request, // + final String edgeId // + ) throws OpenemsNamedException { + switch (request.getPayload().getMethod()) { + case AppCenterIsKeyApplicableRequest.METHOD: + return handle(metadata, request, // + AppCenterIsKeyApplicableRequest::from, // + (m, r) -> m.sendIsKeyApplicable(r.key, edgeId, r.appId), // + AppCenterIsKeyApplicableResponse::from); + + case AppCenterGetPossibleAppsRequest.METHOD: + return handle(metadata, request, // + AppCenterGetPossibleAppsRequest::from, // + (m, r) -> m.sendGetPossibleApps(r.key, edgeId), // + AppCenterGetPossibleAppsResponse::from); + } + return null; + } + + private static // + CompletableFuture handle(// + final METADATA metadata, // + final AppCenterRequest request, // + final ThrowingFunction requestMapper, // + final ThrowingBiFunction metadataCall, // + final ThrowingBiFunction resultMapper // + ) throws OpenemsNamedException { + var result = metadataCall.apply(metadata, requestMapper.apply(request.getPayload())); + if (result == null) { + if (resultMapper != null) { + LOG.warn("Got no result for request " + request.getPayload().getMethod() + " but expected one!"); + } + return CompletableFuture.completedFuture(new GenericJsonrpcResponseSuccess(request.id)); + } + return CompletableFuture.completedFuture(resultMapper.apply(request.id, result)); + } + + private static // + CompletableFuture handle(// + final METADATA metadata, // + final AppCenterRequest request, // + final ThrowingFunction requestMapper, // + final ThrowingBiConsumer metadataCall // + ) throws OpenemsNamedException { + return handle(metadata, request, requestMapper, (t, u) -> { + metadataCall.accept(t, u); + return null; + }, null); + } + +} diff --git a/io.openems.backend.common/src/io/openems/backend/common/metadata/AppCenterMetadata.java b/io.openems.backend.common/src/io/openems/backend/common/metadata/AppCenterMetadata.java new file mode 100644 index 00000000000..a5e22f243d7 --- /dev/null +++ b/io.openems.backend.common/src/io/openems/backend/common/metadata/AppCenterMetadata.java @@ -0,0 +1,136 @@ +package io.openems.backend.common.metadata; + +import java.util.UUID; + +import org.osgi.annotation.versioning.ProviderType; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; + +@ProviderType +public interface AppCenterMetadata { + + /** + * Sends a request if the key can be applied to the given edge and app id. + * + * @param key the key to be validated + * @param edgeId the edge the app gets installed + * @param appId the app that gets installed + * @return the result as a {@link JsonObject} + * @throws OpenemsNamedException on error + */ + public JsonObject sendIsKeyApplicable(String key, String edgeId, String appId) throws OpenemsNamedException; + + /** + * Sends a request to get all apps that can be installed with the given key. + * + * @param key the apps of the key + * @param edgeId the apps on which edge + * @return the bundles and their apps + * @throws OpenemsNamedException on error + */ + public JsonArray sendGetPossibleApps(String key, String edgeId) throws OpenemsNamedException; + + @ProviderType + public static interface EdgeData extends AppCenterMetadata { + + /** + * Sends a request to add a install app history entry. + * + * @param key the key that the app gets installed with + * @param edgeId the edge the app gets installed on + * @param appId the app that gets installed + * @param instanceId the instanceId of the installed app + * @param userId the user who added the instance + * @throws OpenemsNamedException on error + */ + public void sendAddInstallAppInstanceHistory(String key, String edgeId, String appId, UUID instanceId, + String userId) throws OpenemsNamedException; + + /** + * Sends a request to add a deinstall history entry. + * + * @param edgeId the edge the instance gets removed + * @param appId the id of the app + * @param instanceId the instanceId of the removed instance + * @param userId the user who removed the instance + * @throws OpenemsNamedException on error + */ + public void sendAddDeinstallAppInstanceHistory(String edgeId, String appId, UUID instanceId, String userId) + throws OpenemsNamedException; + + /** + * Sends a request to get all installed apps on the edge that are logged in the + * backend these apps may not be actually on the edge. + * + * @param edgeId the apps on which edge + * @return the installed apps + * @throws OpenemsNamedException on error + */ + public JsonObject sendGetInstalledApps(String edgeId) throws OpenemsNamedException; + + } + + @ProviderType + public static interface UiData extends AppCenterMetadata { + + /** + * Sends a request to register a key. + * + * @param edgeId the edge the key gets registered to + * @param appId the appId that gets registered + * @param key the key to register to the app + * @param user the user who added the registration + * @throws OpenemsNamedException on error + */ + public void sendAddRegisterKeyHistory(String edgeId, String appId, String key, User user) + throws OpenemsNamedException; + + /** + * Sends a request to unregister a key. + * + * @param edgeId the edge + * @param appId the id of the app + * @param key the key + * @param user the user who removed the registration + * @throws OpenemsNamedException on error + */ + public void sendAddUnregisterKeyHistory(String edgeId, String appId, String key, User user) + throws OpenemsNamedException; + + /** + * Sends a request to get all registered keys to the given edge and app. + * + * @param edgeId the edge the registered key can be applied on + * @param appId the app of the registed key + * @return the result as a {@link JsonObject} + * @throws OpenemsNamedException on error + */ + public JsonArray sendGetRegisteredKeys(String edgeId, String appId) throws OpenemsNamedException; + + /** + * Gets a key that can be supplied to the installation of the given app. + * + * @param user the requested user + * @param edgeId the edge to install the app on + * @param appId the app to install + * @return the key or null if none can be supplied + * @throws OpenemsNamedException on error + */ + public String getSuppliableKey(User user, String edgeId, String appId) throws OpenemsNamedException; + + /** + * Gets if the given app is free. + * + * @param user the requested user + * @param appId the id of the app + * @return true if the app is free + * @throws OpenemsNamedException on error + */ + public boolean isAppFree(User user, String appId) throws OpenemsNamedException; + + } + +} \ No newline at end of file diff --git a/io.openems.backend.common/src/io/openems/backend/common/metadata/User.java b/io.openems.backend.common/src/io/openems/backend/common/metadata/User.java index f458fbbb201..49141329f3b 100644 --- a/io.openems.backend.common/src/io/openems/backend/common/metadata/User.java +++ b/io.openems.backend.common/src/io/openems/backend/common/metadata/User.java @@ -23,13 +23,19 @@ public class User extends AbstractUser { */ private final String token; - public User(String id, String name, String token, Language language, Role globalRole) { - this(id, name, token, language, globalRole, new TreeMap<>()); + /** + * True, if the current User can see multiple edges. + */ + private final boolean hasMultipleEdges; + + public User(String id, String name, String token, Language language, Role globalRole, boolean hasMultipleEdges) { + this(id, name, token, language, globalRole, new TreeMap<>(), hasMultipleEdges); } public User(String id, String name, String token, Language language, Role globalRole, - NavigableMap roles) { + NavigableMap roles, boolean hasMultipleEdges) { super(id, name, language, globalRole, roles); + this.hasMultipleEdges = hasMultipleEdges; this.token = token; } @@ -93,4 +99,9 @@ public static List generateEdgeMetadatas(User user, Metadata metad return metadatas; } + @Override + public boolean hasMultipleEdges() { + return this.hasMultipleEdges; + } + } diff --git a/io.openems.backend.core/src/io/openems/backend/core/jsonrpcrequesthandler/CoreJsonRpcRequestHandlerImpl.java b/io.openems.backend.core/src/io/openems/backend/core/jsonrpcrequesthandler/CoreJsonRpcRequestHandlerImpl.java index 24ebcfd8441..35ed13d761c 100644 --- a/io.openems.backend.core/src/io/openems/backend/core/jsonrpcrequesthandler/CoreJsonRpcRequestHandlerImpl.java +++ b/io.openems.backend.core/src/io/openems/backend/core/jsonrpcrequesthandler/CoreJsonRpcRequestHandlerImpl.java @@ -13,6 +13,7 @@ import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.ReferenceCardinality; import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.component.annotations.ReferencePolicyOption; import org.osgi.service.metatype.annotations.Designate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,6 +26,7 @@ import io.openems.backend.common.jsonrpc.response.GetEdgesChannelsValuesResponse; import io.openems.backend.common.jsonrpc.response.GetEdgesStatusResponse; import io.openems.backend.common.jsonrpc.response.GetEdgesStatusResponse.EdgeInfo; +import io.openems.backend.common.metadata.AppCenterMetadata; import io.openems.backend.common.metadata.Metadata; import io.openems.backend.common.metadata.User; import io.openems.backend.common.timedata.TimedataManager; @@ -54,6 +56,9 @@ public class CoreJsonRpcRequestHandlerImpl extends AbstractOpenemsBackendCompone @Reference(cardinality = ReferenceCardinality.MANDATORY, policy = ReferencePolicy.DYNAMIC) protected volatile Metadata metadata; + @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY) + protected volatile AppCenterMetadata.UiData appCenterMetadata; + @Reference(cardinality = ReferenceCardinality.MANDATORY, policy = ReferencePolicy.DYNAMIC) protected volatile TimedataManager timedataManager; diff --git a/io.openems.backend.core/src/io/openems/backend/core/jsonrpcrequesthandler/EdgeRpcRequestHandler.java b/io.openems.backend.core/src/io/openems/backend/core/jsonrpcrequesthandler/EdgeRpcRequestHandler.java index a5879a3532e..1e171fd3584 100644 --- a/io.openems.backend.core/src/io/openems/backend/core/jsonrpcrequesthandler/EdgeRpcRequestHandler.java +++ b/io.openems.backend.core/src/io/openems/backend/core/jsonrpcrequesthandler/EdgeRpcRequestHandler.java @@ -3,10 +3,12 @@ import java.util.UUID; import java.util.concurrent.CompletableFuture; +import io.openems.backend.common.metadata.AppCenterHandler; import io.openems.backend.common.metadata.User; import io.openems.common.exceptions.OpenemsError; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess; +import io.openems.common.jsonrpc.request.AppCenterRequest; import io.openems.common.jsonrpc.request.ComponentJsonApiRequest; import io.openems.common.jsonrpc.request.CreateComponentConfigRequest; import io.openems.common.jsonrpc.request.DeleteComponentConfigRequest; @@ -48,8 +50,13 @@ protected CompletableFuture handleRequest(User user, UUID messa var request = edgeRpcRequest.getPayload(); user.assertEdgeRoleIsAtLeast(EdgeRpcRequest.METHOD, edgeRpcRequest.getEdgeId(), Role.GUEST); - CompletableFuture resultFuture; + CompletableFuture resultFuture; switch (request.getMethod()) { + case AppCenterRequest.METHOD: + resultFuture = AppCenterHandler.handleUserRequest(this.parent.appCenterMetadata, // + t -> this.handleRequest(user, messageId, t), // + AppCenterRequest.from(request), user, edgeId); + break; case QueryHistoricTimeseriesDataRequest.METHOD: resultFuture = this.handleQueryHistoricDataRequest(edgeId, user, @@ -167,7 +174,6 @@ private CompletableFuture handleQueryHistoricEnergyPerPe var data = this.parent.timedataManager.queryHistoricEnergyPerPeriod(// edgeId, request.getFromDate(), request.getToDate(), request.getChannels(), request.getResolution()); - // JSON-RPC response return CompletableFuture .completedFuture(new QueryHistoricTimeseriesEnergyPerPeriodResponse(request.getId(), data)); } diff --git a/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/EdgeWebsocketImpl.java b/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/EdgeWebsocketImpl.java index 1624acbaf22..5328ad580a5 100644 --- a/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/EdgeWebsocketImpl.java +++ b/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/EdgeWebsocketImpl.java @@ -1,5 +1,6 @@ package io.openems.backend.edgewebsocket; +import java.time.Instant; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; @@ -17,6 +18,8 @@ import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.component.annotations.ReferencePolicyOption; import org.osgi.service.event.Event; import org.osgi.service.event.EventAdmin; import org.osgi.service.event.EventHandler; @@ -25,11 +28,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.collect.TreeBasedTable; import com.google.gson.JsonElement; import com.google.gson.JsonNull; +import com.google.gson.JsonPrimitive; import io.openems.backend.common.component.AbstractOpenemsBackendComponent; import io.openems.backend.common.edgewebsocket.EdgeWebsocket; +import io.openems.backend.common.metadata.AppCenterMetadata; import io.openems.backend.common.metadata.Metadata; import io.openems.backend.common.metadata.User; import io.openems.backend.common.timedata.TimedataManager; @@ -41,6 +47,7 @@ import io.openems.common.jsonrpc.base.JsonrpcRequest; import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess; import io.openems.common.jsonrpc.notification.SystemLogNotification; +import io.openems.common.jsonrpc.notification.TimestampedDataNotification; import io.openems.common.jsonrpc.request.AuthenticatedRpcRequest; import io.openems.common.jsonrpc.request.SubscribeSystemLogRequest; import io.openems.common.jsonrpc.response.AuthenticatedRpcResponse; @@ -58,6 +65,9 @@ }) public class EdgeWebsocketImpl extends AbstractOpenemsBackendComponent implements EdgeWebsocket, EventHandler { + private static final String EDGE_ID = "backend0"; + private static final String COMPONENT_ID = "edgewebsocket"; + private final Logger log = LoggerFactory.getLogger(EdgeWebsocketImpl.class); private final SystemLogHandler systemLogHandler; private final ScheduledExecutorService debugLogExecutor = Executors.newSingleThreadScheduledExecutor(); @@ -65,6 +75,9 @@ public class EdgeWebsocketImpl extends AbstractOpenemsBackendComponent implement @Reference protected volatile Metadata metadata; + @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY) + protected volatile AppCenterMetadata.EdgeData appCenterMetadata; + @Reference protected volatile TimedataManager timedataManager; @@ -86,10 +99,11 @@ public EdgeWebsocketImpl() { private void activate(Config config) { this.config = config; this.debugLogExecutor.scheduleWithFixedDelay(() -> { - this.log.info(new StringBuilder("[monitor] ") // - .append("Edge-Connections: ") - .append(this.server != null ? this.server.getConnections().size() : "initializing") // - .toString()); + var data = TreeBasedTable.create(); + var now = Instant.now().toEpochMilli(); + data.put(now, COMPONENT_ID + "/Connections", + new JsonPrimitive(this.server != null ? this.server.getConnections().size() : 0)); + this.timedataManager.write(EDGE_ID, new TimestampedDataNotification(data)); }, 10, 10, TimeUnit.SECONDS); if (this.metadata.isInitialized()) { diff --git a/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/OnOpen.java b/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/OnOpen.java index 426101240d3..c05507d0405 100644 --- a/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/OnOpen.java +++ b/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/OnOpen.java @@ -26,21 +26,24 @@ public void run(WebSocket ws, JsonObject handshake) { // get apikey from handshake var apikeyOpt = JsonUtils.getAsOptionalString(handshake, "apikey"); if (!apikeyOpt.isPresent()) { - throw new OpenemsException("Apikey is missing in handshake"); + throw new OpenemsException("Apikey is missing in handshake. " // + + "Remote [" + ws.getRemoteSocketAddress() + "]"); } apikey = apikeyOpt.get().trim(); // get edgeId for apikey var edgeIdOpt = this.parent.metadata.getEdgeIdForApikey(apikey); if (!edgeIdOpt.isPresent()) { - throw new OpenemsException("Unable to authenticate this Apikey."); + throw new OpenemsException("Unable to authenticate this Apikey. " // + + "Remote [" + ws.getRemoteSocketAddress() + "]"); } var edgeId = edgeIdOpt.get(); // get metadata for Edge var edgeOpt = this.parent.metadata.getEdge(edgeId); if (!edgeOpt.isPresent()) { - throw new OpenemsException("Unable to get metadata for Edge [" + edgeId + "]"); + throw new OpenemsException("Unable to get metadata for Edge [" + edgeId + "]. " // + + "Remote [" + ws.getRemoteSocketAddress() + "]"); } var edge = edgeOpt.get(); @@ -53,13 +56,17 @@ public void run(WebSocket ws, JsonObject handshake) { } catch (OpenemsException e) { if (this.parent.metadata.isInitialized()) { // close websocket - ws.closeConnection(CloseFrame.REFUSE, - "Connection to backend failed. Apikey [" + apikey + "]. Error: " + e.getMessage()); + ws.closeConnection(CloseFrame.REFUSE, "Connection to backend failed. " // + + "Apikey [" + apikey + "]. " // + + "Remote [" + ws.getRemoteSocketAddress() + "] " // + + "Error: " + e.getMessage()); } else { // close websocket ws.closeConnection(CloseFrame.TRY_AGAIN_LATER, - "Connection to backend failed. Metadata is not yet initialized. Apikey [" + apikey - + "]. Error: " + e.getMessage()); + "Connection to backend failed. Metadata is not yet initialized. " // + + "Apikey [" + apikey + "]. " // + + "Remote [" + ws.getRemoteSocketAddress() + "] " // + + "Error: " + e.getMessage()); } } } diff --git a/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/OnRequest.java b/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/OnRequest.java index 100f9362a8f..778d47a9a80 100644 --- a/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/OnRequest.java +++ b/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/OnRequest.java @@ -6,11 +6,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import io.openems.backend.common.metadata.AppCenterHandler; import io.openems.common.exceptions.OpenemsError; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; import io.openems.common.jsonrpc.base.JsonrpcRequest; import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess; +import io.openems.common.jsonrpc.request.AppCenterRequest; public class OnRequest implements io.openems.common.websocket.OnRequest { @@ -24,6 +26,23 @@ public OnRequest(EdgeWebsocketImpl parent) { @Override public CompletableFuture run(WebSocket ws, JsonrpcRequest request) throws OpenemsException, OpenemsNamedException { + + final WsData wsData = ws.getAttachment(); + final var edgeId = wsData.getEdgeId().get(); + + CompletableFuture resultFuture = null; + + switch (request.getMethod()) { + case AppCenterRequest.METHOD: + resultFuture = AppCenterHandler.handleEdgeRequest(this.parent.appCenterMetadata, // + AppCenterRequest.from(request), edgeId); + break; + + } + + if (resultFuture != null) { + return resultFuture; + } this.parent.logWarn(this.log, "Unhandled Request: " + request); throw OpenemsError.JSONRPC_UNHANDLED_METHOD.exception(request.getMethod()); } diff --git a/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/SystemLogHandler.java b/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/SystemLogHandler.java index da0a6b7d5b8..83ac04c49a1 100644 --- a/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/SystemLogHandler.java +++ b/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/SystemLogHandler.java @@ -2,7 +2,6 @@ import java.util.HashSet; import java.util.Set; -import java.util.TreeMap; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; @@ -82,7 +81,7 @@ public void handleSystemLogNotification(String edgeId, SystemLogNotification not // No Tokens exist, but we still receive Notification? -> send unsubscribe try { var dummyGuestUser = new User("internal", "UnsubscribeSystemLogNotification", - UUID.randomUUID().toString(), Language.EN, Role.GUEST, new TreeMap<>()); + UUID.randomUUID().toString(), Language.EN, Role.GUEST, false); this.parent.send(edgeId, dummyGuestUser, SubscribeSystemLogRequest.unsubscribe()); this.parent.logInfo(this.log, edgeId, "Was still sending SystemLogNotification. Sent unsubscribe."); @@ -104,7 +103,7 @@ public void handleSystemLogNotification(String edgeId, SystemLogNotification not // error -> send unsubscribe try { var dummyGuestUser = new User("internal", "UnsubscribeSystemLogNotification", - UUID.randomUUID().toString(), Language.EN, Role.GUEST, new TreeMap<>()); + UUID.randomUUID().toString(), Language.EN, Role.GUEST, false); this.handleSubscribeSystemLogRequest(edgeId, dummyGuestUser, token, SubscribeSystemLogRequest.unsubscribe()); diff --git a/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/WebsocketServer.java b/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/WebsocketServer.java index 76c729d3316..ac7397e9367 100644 --- a/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/WebsocketServer.java +++ b/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/WebsocketServer.java @@ -11,7 +11,9 @@ import org.java_websocket.WebSocket; import org.slf4j.Logger; +import com.google.common.collect.TreeBasedTable; import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; @@ -20,6 +22,7 @@ import io.openems.common.jsonrpc.notification.TimestampedDataNotification; import io.openems.common.types.SystemLog; import io.openems.common.utils.JsonUtils; +import io.openems.common.utils.ThreadPoolUtils; import io.openems.common.websocket.AbstractWebsocketServer; public class WebsocketServer extends AbstractWebsocketServer { @@ -33,6 +36,13 @@ public class WebsocketServer extends AbstractWebsocketServer { public WebsocketServer(EdgeWebsocketImpl parent, String name, int port, int poolSize, DebugMode debugMode) { super(name, port, poolSize, debugMode, (executor) -> { + // Store Metrics + var data = TreeBasedTable.create(); + var now = Instant.now().toEpochMilli(); + ThreadPoolUtils.debugMetrics(executor).forEach((key, value) -> { + data.put(now, "edgewebsocket/" + key, new JsonPrimitive(value)); + }); + parent.timedataManager.write("backend0", new TimestampedDataNotification(data)); }); this.parent = parent; this.onOpen = new OnOpen(parent); diff --git a/io.openems.backend.metadata.dummy/src/io/openems/backend/metadata/dummy/MetadataDummy.java b/io.openems.backend.metadata.dummy/src/io/openems/backend/metadata/dummy/MetadataDummy.java index ed3d59dd3cf..85305f65d55 100644 --- a/io.openems.backend.metadata.dummy/src/io/openems/backend/metadata/dummy/MetadataDummy.java +++ b/io.openems.backend.metadata.dummy/src/io/openems/backend/metadata/dummy/MetadataDummy.java @@ -94,21 +94,39 @@ private void deactivate() { public User authenticate(String username, String password) throws OpenemsNamedException { var name = "User #" + this.nextUserId.incrementAndGet(); var token = UUID.randomUUID().toString(); - var user = new User(username, name, token, this.defaultLanguage, Role.ADMIN); + var user = new User(username, name, token, this.defaultLanguage, Role.ADMIN, this.hasMultipleEdges()); this.users.put(user.getId(), user); return user; } @Override public User authenticate(String token) throws OpenemsNamedException { - for (User user : this.users.values()) { - if (user.getToken().equals(token)) { - return user; + for (var user : this.users.values()) { + if (!user.getToken().equals(token)) { + continue; } + final var hasMultipleEdges = this.hasMultipleEdges(); + final User returnUser; + if (user.hasMultipleEdges() != hasMultipleEdges) { + returnUser = this.createUser(user.getId(), user.getName(), user.getToken(), hasMultipleEdges); + this.users.put(token, returnUser); + } else { + returnUser = user; + } + + return returnUser; } throw OpenemsError.COMMON_AUTHENTICATION_FAILED.exception(); } + private User createUser(String username, String name, String token, boolean hasMultipleEdges) { + return new User(username, name, token, this.defaultLanguage, Role.ADMIN, this.hasMultipleEdges()); + } + + private boolean hasMultipleEdges() { + return this.edges.size() > 1; + } + @Override public void logout(User user) { this.users.remove(user.getId(), user); diff --git a/io.openems.backend.metadata.file/src/io/openems/backend/metadata/file/MetadataFile.java b/io.openems.backend.metadata.file/src/io/openems/backend/metadata/file/MetadataFile.java index 2297b023c91..d5715878d72 100644 --- a/io.openems.backend.metadata.file/src/io/openems/backend/metadata/file/MetadataFile.java +++ b/io.openems.backend.metadata.file/src/io/openems/backend/metadata/file/MetadataFile.java @@ -92,12 +92,11 @@ public class MetadataFile extends AbstractMetadata implements Metadata, EventHan @Reference private EventAdmin eventAdmin; - private User user; + private User user = this.generateUser(); private String path = ""; public MetadataFile() { super("Metadata.File"); - this.user = MetadataFile.generateUser(); } @Activate @@ -118,7 +117,7 @@ private void deactivate() { @Override public User authenticate(String username, String password) throws OpenemsNamedException { - return this.user; + return this.user = this.generateUser(); } @Override @@ -131,7 +130,7 @@ public User authenticate(String token) throws OpenemsNamedException { @Override public void logout(User user) { - this.user = MetadataFile.generateUser(); + this.user = this.generateUser(); } @Override @@ -218,13 +217,21 @@ private synchronized void refreshData() { for (MyEdge edge : edges) { this.edges.put(edge.getId(), edge); } + + final var previousUser = this.user; + final var hasMultipleEdges = edges.size() > 1; + if (previousUser.hasMultipleEdges() != hasMultipleEdges) { + this.user = new User(previousUser.getId(), previousUser.getName(), previousUser.getToken(), + previousUser.getLanguage(), previousUser.getGlobalRole(), previousUser.getEdgeRoles(), + hasMultipleEdges); + } } this.setInitialized(); } - private static User generateUser() { + private User generateUser() { return new User(MetadataFile.USER_ID, MetadataFile.USER_NAME, UUID.randomUUID().toString(), - MetadataFile.LANGUAGE, MetadataFile.USER_GLOBAL_ROLE); + MetadataFile.LANGUAGE, MetadataFile.USER_GLOBAL_ROLE, this.edges.size() > 1); } @Override diff --git a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/MetadataOdoo.java b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/MetadataOdoo.java index fffe03aa007..9016cc9de5c 100644 --- a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/MetadataOdoo.java +++ b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/MetadataOdoo.java @@ -9,6 +9,7 @@ import java.util.NavigableMap; import java.util.Optional; import java.util.TreeMap; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; @@ -27,12 +28,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.collect.Sets; import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import io.openems.backend.common.metadata.AbstractMetadata; import io.openems.backend.common.metadata.AlertingSetting; +import io.openems.backend.common.metadata.AppCenterMetadata; import io.openems.backend.common.metadata.Edge; import io.openems.backend.common.metadata.EdgeHandler; import io.openems.backend.common.metadata.Mailer; @@ -66,7 +70,8 @@ @EventTopics({ // Edge.Events.ALL_EVENTS // }) -public class MetadataOdoo extends AbstractMetadata implements Metadata, Mailer, EventHandler { +public class MetadataOdoo extends AbstractMetadata implements AppCenterMetadata, AppCenterMetadata.EdgeData, + AppCenterMetadata.UiData, Metadata, Mailer, EventHandler { private static final int EXECUTOR_MIN_THREADS = 1; private static final int EXECUTOR_MAX_THREADS = 50; @@ -133,6 +138,13 @@ public User authenticate(String sessionId) throws OpenemsNamedException { var result = this.odooHandler.authenticateSession(sessionId); // Parse Result + var jUser = JsonUtils.getAsJsonObject(result, "user"); + var odooUserId = JsonUtils.getAsInt(jUser, "id"); + var login = JsonUtils.getAsString(jUser, "login"); + var name = JsonUtils.getAsString(jUser, "name"); + var language = Language.from(JsonUtils.getAsString(jUser, "language")); + var globalRole = Role.getRole(JsonUtils.getAsString(jUser, "global_role")); + var hasMultipleEdges = JsonUtils.getAsBoolean(jUser, "has_multiple_edges"); var jDevices = JsonUtils.getAsJsonArray(result, "devices"); NavigableMap roles = new TreeMap<>(); for (JsonElement device : jDevices) { @@ -140,24 +152,20 @@ public User authenticate(String sessionId) throws OpenemsNamedException { var role = Role.getRole(JsonUtils.getAsString(device, "role")); roles.put(edgeId, role); } - var jUser = JsonUtils.getAsJsonObject(result, "user"); - var odooUserId = JsonUtils.getAsInt(jUser, "id"); - - var user = new MyUser(// - odooUserId, // - JsonUtils.getAsString(jUser, "login"), // - JsonUtils.getAsString(jUser, "name"), // - sessionId, // - Language.from(JsonUtils.getAsString(jUser, "language")), // - Role.getRole(JsonUtils.getAsString(jUser, "global_role")), // - roles); - this.users.put(user.getId(), user); + var user = new MyUser(odooUserId, login, name, sessionId, language, globalRole, roles, hasMultipleEdges); + var oldUser = this.users.put(login, user); + if (oldUser != null) { + oldUser.getEdgeRoles().forEach((edgeId, role) -> { + user.setRole(edgeId, role); + }); + } return user; } @Override public void logout(User user) { + this.users.remove(user.getId()); this.odooHandler.logout(user.getToken()); } @@ -230,6 +238,7 @@ public void logError(Logger log, String message) { @Override public void addEdgeToUser(User user, Edge edge) throws OpenemsNamedException { this.odooHandler.assignEdgeToUser((MyUser) user, (MyEdge) edge, OdooUserRole.INSTALLER); + user.setRole(edge.getId(), Role.INSTALLER); } @Override @@ -401,6 +410,79 @@ public void sendMail(ZonedDateTime sendAt, String template, JsonElement params) } } + @Override + public JsonObject sendIsKeyApplicable(String key, String edgeId, String appId) throws OpenemsNamedException { + return this.odooHandler.getIsKeyApplicable(key, edgeId, appId); + } + + @Override + public void sendAddInstallAppInstanceHistory(String key, String edgeId, String appId, UUID instanceId, + String userId) throws OpenemsNamedException { + this.odooHandler.getAddInstallAppInstanceHistory(key, edgeId, appId, instanceId, userId); + } + + @Override + public void sendAddDeinstallAppInstanceHistory(String edgeId, String appId, UUID instanceId, String userId) + throws OpenemsNamedException { + this.odooHandler.getAddDeinstallAppInstanceHistory(edgeId, appId, instanceId, userId); + } + + @Override + public void sendAddRegisterKeyHistory(String edgeId, String appId, String key, User user) + throws OpenemsNamedException { + this.odooHandler.getAddRegisterKeyHistory(edgeId, appId, key, (MyUser) user); + } + + @Override + public void sendAddUnregisterKeyHistory(String edgeId, String appId, String key, User user) + throws OpenemsNamedException { + this.odooHandler.getAddUnregisterKeyHistory(edgeId, appId, key, (MyUser) user); + } + + @Override + public JsonArray sendGetRegisteredKeys(String edgeId, String appId) throws OpenemsNamedException { + var response = this.odooHandler.getRegisteredKeys(edgeId, appId); + return JsonUtils.getAsOptionalJsonArray(response, "keys") // + .orElse(new JsonArray()) // + ; + } + + @Override + public JsonArray sendGetPossibleApps(String key, String edgeId) throws OpenemsNamedException { + var response = this.odooHandler.getPossibleApps(key, edgeId); + return JsonUtils.getAsJsonArray(response, "bundles"); + } + + @Override + public JsonObject sendGetInstalledApps(String edgeId) throws OpenemsNamedException { + return this.odooHandler.getInstalledApps(edgeId); + } + + @Override + public String getSuppliableKey(// + final User user, // + final String edgeId, // + final String appId // + ) throws OpenemsNamedException { + if (this.isAppFree(user, appId)) { + return ""; + } + if (!user.getRole(edgeId).map(r -> r.isAtLeast(Role.INSTALLER)).orElse(false)) { + return null; + } + return ""; + } + + @Override + public boolean isAppFree(// + final User user, // + final String appId // + ) throws OpenemsNamedException { + return Sets.newHashSet(// + "App.Hardware.KMtronic8Channel" // + ).contains(appId); + } + @Override public List getUserAlertingSettings(String edgeId) throws OpenemsException { return this.odooHandler.getUserAlertingSettings(edgeId); diff --git a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/MyUser.java b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/MyUser.java index b7219b0903d..276dbd49252 100644 --- a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/MyUser.java +++ b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/MyUser.java @@ -9,10 +9,10 @@ public class MyUser extends User { private final int odooId; - + public MyUser(int odooId, String login, String name, String token, Language language, Role globalRole, - NavigableMap roles) { - super(login, name, token, language, globalRole, roles); + NavigableMap roles, boolean hasMultipleEdges) { + super(login, name, token, language, globalRole, roles, hasMultipleEdges); this.odooId = odooId; } diff --git a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/OdooHandler.java b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/OdooHandler.java index 3024f01d9c4..c1fa8067a9b 100644 --- a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/OdooHandler.java +++ b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/OdooHandler.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.UUID; import java.util.concurrent.Future; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -398,7 +399,7 @@ private Map updateCompany(MyUser user, JsonObject companyJson) t * @throws OpenemsNamedException on error */ public byte[] getOdooSetupProtocolReport(int setupProtocolId) throws OpenemsNamedException { - return OdooUtils.getOdooReport(this.credentials, "fems.report_fems_setup_protocol_template", setupProtocolId); + return OdooUtils.getOdooReport(this.credentials, "openems.report_openems_setup_protocol_template", setupProtocolId); } /** @@ -411,7 +412,7 @@ public byte[] getOdooSetupProtocolReport(int setupProtocolId) throws OpenemsName */ public int submitSetupProtocol(MyUser user, JsonObject setupProtocolJson) throws OpenemsNamedException { var userJson = JsonUtils.getAsJsonObject(setupProtocolJson, "customer"); - var edgeJson = JsonUtils.getAsJsonObject(setupProtocolJson, "fems"); + var edgeJson = JsonUtils.getAsJsonObject(setupProtocolJson, "edge"); var installerJson = JsonUtils.getAsJsonObject(setupProtocolJson, "installer"); var oem = OpenemsOEM.Manufacturer.valueOf(JsonUtils.getAsString(setupProtocolJson, "oem").toUpperCase()); @@ -578,9 +579,9 @@ private int createOdooUser(JsonObject userJson, String password, OpenemsOEM.Manu * @throws OpenemsException on error */ private void addTagToPartner(int userId) throws OpenemsException { - var createdViaIbnTag = OdooUtils.getObjectReference(this.credentials, "fems", + var createdViaIbnTag = OdooUtils.getObjectReference(this.credentials, "openems", "res_partner_category_created_via_ibn"); - var customerTag = OdooUtils.getObjectReference(this.credentials, "fems", "res_partner_category_customer"); + var customerTag = OdooUtils.getObjectReference(this.credentials, "openems", "res_partner_category_customer"); var partnerId = this.getOdooPartnerId(userId); @@ -825,18 +826,20 @@ private void sendRegistrationMail(int odooUserId, OpenemsOEM.Manufacturer oem) t * @param odooUserId Odoo user id to send the mail * @param password password for the user * @param oem OEM name - * @throws OpenemsNamedException error */ - private void sendRegistrationMail(int odooUserId, String password, OpenemsOEM.Manufacturer oem) - throws OpenemsNamedException { - OdooUtils.sendAdminJsonrpcRequest(this.credentials, "/openems_backend/sendRegistrationEmail", - JsonUtils.buildJsonObject() // - .add("params", JsonUtils.buildJsonObject() // - .addProperty("userId", odooUserId) // - .addProperty("password", password) // - .addProperty("oem", oem) // - .build()) // - .build()); + private void sendRegistrationMail(int odooUserId, String password, OpenemsOEM.Manufacturer oem) { + try { + OdooUtils.sendAdminJsonrpcRequest(this.credentials, "/openems_backend/sendRegistrationEmail", + JsonUtils.buildJsonObject() // + .add("params", JsonUtils.buildJsonObject() // + .addProperty("userId", odooUserId) // + .addProperty("password", password) // + .addProperty("oem", oem) // + .build()) // + .build()); + } catch (OpenemsNamedException e) { + this.log.warn("Unable to send registration mail for Odoo user id [" + odooUserId + "]", e); + } } /** @@ -928,6 +931,185 @@ public Optional getSerialNumberForEdge(Edge edge) { return Optional.empty(); } + /** + * Gets if the given key can be applied to the given app and edge id. + * + * @param key the key to be validated + * @param edgeId the edgeId the app should get installed + * @param appId the appId of the app that should get installed + * @return the Response result as a JsonObject + * @throws OpenemsNamedException on error + */ + public JsonObject getIsKeyApplicable(String key, String edgeId, String appId) throws OpenemsNamedException { + var request = JsonUtils.buildJsonObject() // + .add("params", JsonUtils.buildJsonObject() // + .addProperty("key", key) // + .addProperty("edgeId", edgeId) // + .addPropertyIfNotNull("appId", appId) // + .build()) // + .build(); + + var result = JsonUtils.getAsJsonObject(OdooUtils.sendAdminJsonrpcRequest(this.credentials, + "/openems_app_center/is_key_applicable", request).result); + return result; + } + + /** + * Gets the response to a add install app instance history entry. + * + * @param key the key to install the app with + * @param edgeId the id of the edge the app gets installed on + * @param appId the app that gets installed + * @param instanceId the instanceId of the create instance + * @param userId the user who added the instance + * @return the result as {@link JsonObject} + * @throws OpenemsNamedException on error + */ + public JsonObject getAddInstallAppInstanceHistory(String key, String edgeId, String appId, UUID instanceId, + String userId) throws OpenemsNamedException { + var request = JsonUtils.buildJsonObject() // + .add("params", JsonUtils.buildJsonObject() // + .addProperty("key", key) // + .addProperty("edgeId", edgeId) // + .addProperty("appId", appId) // + .addProperty("instanceId", instanceId.toString()) // + .addPropertyIfNotNull("userId", userId) // + .build()) // + .build(); + + return JsonUtils.getAsJsonObject(OdooUtils.sendAdminJsonrpcRequest(this.credentials, + "/openems_app_center/add_install_app_instance_history", request).result); + } + + /** + * Gets the response to add a deinstall app instance history entry. + * + * @param edgeId the edge the app gets removed on + * @param appId the appId of the removed instance + * @param instanceId the instanceId of the removed instance + * @param userId the user who removed the instance + * @return the result as {@link JsonObject} + * @throws OpenemsNamedException on error + */ + public JsonObject getAddDeinstallAppInstanceHistory(String edgeId, String appId, UUID instanceId, String userId) + throws OpenemsNamedException { + var request = JsonUtils.buildJsonObject() // + .add("params", JsonUtils.buildJsonObject() // + .addProperty("edgeId", edgeId) // + .addProperty("appId", appId) // + .addProperty("instanceId", instanceId.toString()) // + .addPropertyIfNotNull("userId", userId) // + .build()) // + .build(); + return JsonUtils.getAsJsonObject(OdooUtils.sendAdminJsonrpcRequest(this.credentials, + "/openems_app_center/add_deinstall_app_instance_history", request).result); + } + + /** + * Gets the response to register a key. + * + * @param edgeId the edgeId the key gets registered on. + * @param appId the appId the key gets registered to. + * @param key the key that gets registered + * @param user the user who registered the key + * @return the result as {@link JsonObject} + * @throws OpenemsNamedException on error + */ + public JsonObject getAddRegisterKeyHistory(String edgeId, String appId, String key, MyUser user) + throws OpenemsNamedException { + var request = JsonUtils.buildJsonObject() // + .add("params", JsonUtils.buildJsonObject() // + .addProperty("edgeId", edgeId) // + .addProperty("key", key) // + .addPropertyIfNotNull("appId", appId) // + .addProperty("userId", user.getId()) // + .build()) // + .build(); + return JsonUtils.getAsJsonObject(OdooUtils.sendAdminJsonrpcRequest(this.credentials, + "/openems_app_center/add_register_key_history", request).result); + } + + /** + * Gets the response to unregister a key. + * + * @param edgeId the edgeId the registered key was assigned to. + * @param appId the appId the registered key was assigned to or null if + * assigned to edge. + * @param key the registered key + * @param user the user who deregistered the key + * @return the response result as a {@link JsonObject} + * @throws OpenemsNamedException on error + */ + public JsonObject getAddUnregisterKeyHistory(String edgeId, String appId, String key, MyUser user) + throws OpenemsNamedException { + var request = JsonUtils.buildJsonObject() // + .add("params", JsonUtils.buildJsonObject() // + .addProperty("edgeId", edgeId) // + .addProperty("key", key) // + .addPropertyIfNotNull("appId", appId) // + .addProperty("user", user.getId()) // + .build()) + .build(); + + return JsonUtils.getAsJsonObject(OdooUtils.sendAdminJsonrpcRequest(this.credentials, + "/openems_app_center/add_deregister_key_history", request).result); + } + + /** + * Gets the registered keys to a edge and app. + * + * @param edgeId the edge the key is registered on + * @param appId the app the key is registered to + * @return the result as {@link JsonObject} + * @throws OpenemsNamedException on error + */ + public JsonObject getRegisteredKeys(String edgeId, String appId) throws OpenemsNamedException { + var request = JsonUtils.buildJsonObject() // + .add("params", JsonUtils.buildJsonObject() // + .addProperty("edgeId", edgeId) // + .addPropertyIfNotNull("appId", appId) // + .build()) // + .build(); + return JsonUtils.getAsJsonObject(OdooUtils.sendAdminJsonrpcRequest(this.credentials, + "/openems_app_center/get_registered_key", request).result); + } + + /** + * Gets the possible apps to install with this key. + * + * @param key the apps of which key + * @param edgeId the apps on which edge + * @return the result as {@link JsonObject} + * @throws OpenemsNamedException on error + */ + public JsonObject getPossibleApps(String key, String edgeId) throws OpenemsNamedException { + var request = JsonUtils.buildJsonObject() // + .add("params", JsonUtils.buildJsonObject() // + .addProperty("edgeId", edgeId) // + .addProperty("key", key) // + .build()) // + .build(); + return JsonUtils.getAsJsonObject(OdooUtils.sendAdminJsonrpcRequest(this.credentials, + "/openems_app_center/get_possible_apps", request).result); + } + + /** + * Gets the installed apps. + * + * @param edgeId the apps on which edge + * @return the result as {@link JsonObject} + * @throws OpenemsNamedException on error + */ + public JsonObject getInstalledApps(String edgeId) throws OpenemsNamedException { + var request = JsonUtils.buildJsonObject() // + .add("params", JsonUtils.buildJsonObject() // + .addProperty("edgeId", edgeId) // + .build()) // + .build(); + return JsonUtils.getAsJsonObject(OdooUtils.sendAdminJsonrpcRequest(this.credentials, + "/openems_app_center/get_installed_apps", request).result); + } + /** * get all alerting settings for specified edge. * diff --git a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/OdooUtils.java b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/OdooUtils.java index e708654b376..514d6422ea3 100644 --- a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/OdooUtils.java +++ b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/OdooUtils.java @@ -155,6 +155,12 @@ public static SuccessResponseAndHeaders sendJsonrpcRequest(String url, String co throw OpenemsError.COMMON_AUTHENTICATION_FAILED.exception(); default: + // for OpenemsExceptions from Odoo only throw OpenemsException with message for + // more readability + if (dataName.endsWith("OpenemsException")) { + throw new OpenemsException(dataMessage); + } + var exception = "Exception for Request [" + request.toString() + "] to URL [" + url + "]: " // + dataMessage + ";" // + " Code [" + code + "]" // @@ -408,7 +414,7 @@ protected static int getObjectReference(Credentials credentials, String module, throws OpenemsException { try { // Execute XML request - var resultObj = (Object[]) executeKw(credentials, "ir.model.data", "get_object_reference", + var resultObj = (Object[]) executeKw(credentials, "ir.model.data", "check_object_reference", new Object[] { module, name }); if (resultObj == null) { throw new OpenemsException( diff --git a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/postgres/PgUtils.java b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/postgres/PgUtils.java index dc3af1a53fb..20dfd4ed9e9 100644 --- a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/postgres/PgUtils.java +++ b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/postgres/PgUtils.java @@ -5,38 +5,11 @@ import java.time.ZonedDateTime; import io.openems.backend.metadata.odoo.Field; -import io.openems.backend.metadata.odoo.odoo.FieldValue; import io.openems.backend.metadata.odoo.odoo.OdooUtils; import io.openems.common.exceptions.OpenemsException; public class PgUtils { - /** - * Adds a message in Odoo Chatter ('mail.thread'). - * - * @param credentials the Odoo credentials - * @param model Odoo model (e.g. 'res.partner') - * @param id id of model - * @param message the message - * @throws OpenemsException on error - */ - protected static void addChatterMessage(Credentials credentials, String model, int id, String message) - throws OpenemsException { - } - - /** - * Update a record in Odoo. - * - * @param credentials the Odoo credentials - * @param model the Odoo model - * @param ids ids of model to update - * @param fieldValues fields and values that should be written - * @throws OpenemsException on error - */ - public static void write(Credentials credentials, String model, Integer[] ids, FieldValue... fieldValues) - throws OpenemsException { - } - /** * Return the Field of the ResultSet. * diff --git a/io.openems.backend.timedata.aggregatedinflux/src/io/openems/backend/timedata/aggregatedinflux/AllowedChannels.java b/io.openems.backend.timedata.aggregatedinflux/src/io/openems/backend/timedata/aggregatedinflux/AllowedChannels.java index 57c5290d7c9..c76eda5460d 100644 --- a/io.openems.backend.timedata.aggregatedinflux/src/io/openems/backend/timedata/aggregatedinflux/AllowedChannels.java +++ b/io.openems.backend.timedata.aggregatedinflux/src/io/openems/backend/timedata/aggregatedinflux/AllowedChannels.java @@ -38,18 +38,22 @@ private AllowedChannels() { .put("_sum/ConsumptionActivePowerL1", DataType.LONG) // .put("_sum/ConsumptionActivePowerL2", DataType.LONG) // .put("_sum/ConsumptionActivePowerL3", DataType.LONG) // - .put("_sum/EssDischargePower", DataType.LONG) // used for xlsx export - .putAll(multiChannels("io", 0, 9, "Relay", 1, 9, DataType.LONG)) // .putAll(multiChannels("evcs", 0, 9, "ActualPower", DataType.LONG)) // + .putAll(multiChannels("io", 0, 9, "Relay", 1, 9, DataType.LONG)) // .putAll(multiChannels("meter", 0, 9, "ActivePower", DataType.LONG)) // .putAll(multiChannels("meter", 0, 9, "ActivePowerL", 1, 4, DataType.LONG)) // - .put("ess0/Soc", DataType.LONG) // - .put("ess0/ActivePower", DataType.LONG) // - .put("ctrlIoHeatPump0/Status", DataType.LONG) // - .put("ctrlIoHeatingElement0/Level", DataType.LONG) // + .put("ctrlGridOptimizedCharge0/_PropertyMaximumSellToGridPower", DataType.LONG) // .put("ctrlGridOptimizedCharge0/DelayChargeMaximumChargeLimit", DataType.LONG) // .put("ctrlGridOptimizedCharge0/SellToGridLimitMinimumChargeLimit", DataType.LONG) // - .put("ctrlGridOptimizedCharge0/_PropertyMaximumSellToGridPower", DataType.LONG) // + .put("ctrlIoHeatPump0/RegularStateTime", DataType.LONG) // + .put("ctrlIoHeatPump0/RecommendationStateTime", DataType.LONG) // + .put("ctrlIoHeatPump0/ForceOnStateTime", DataType.LONG) // + .put("ctrlIoHeatPump0/LockStateTime", DataType.LONG) // + .put("ctrlIoHeatPump0/Status", DataType.LONG) // + .put("ctrlIoHeatingElement0/Level", DataType.LONG) // + .put("ess0/Soc", DataType.LONG) // + .put("ess0/ActivePower", DataType.LONG) // + .put("_sum/EssDischargePower", DataType.LONG) // used for xlsx export .build(); ALLOWED_CUMULATED_CHANNELS = ImmutableMap.builder() // @@ -57,16 +61,18 @@ private AllowedChannels() { .put("_sum/EssDcDischargeEnergy", DataType.LONG) // .put("_sum/GridSellActiveEnergy", DataType.LONG) // .put("_sum/ProductionActiveEnergy", DataType.LONG) // + .put("_sum/ProductionAcActiveEnergy", DataType.LONG) // + .put("_sum/ProductionDcActiveEnergy", DataType.LONG) // .put("_sum/ConsumptionActiveEnergy", DataType.LONG) // .put("_sum/GridBuyActiveEnergy", DataType.LONG) // .put("_sum/EssActiveChargeEnergy", DataType.LONG) // .put("_sum/EssActiveDischargeEnergy", DataType.LONG) // - .put("_sum/ProductionAcActiveEnergy", DataType.LONG) // - .put("_sum/ProductionDcActiveEnergy", DataType.LONG) // + .putAll(multiChannels("charger", 0, 5, "ActualEnergy", DataType.LONG)) // .putAll(multiChannels("evcs", 0, 9, "ActiveConsumptionEnergy", DataType.LONG)) // - .putAll(multiChannels("meter", 0, 9, "ActiveProductionEnergy", DataType.LONG)) // .putAll(multiChannels("io", 0, 9, "ActiveProductionEnergy", DataType.LONG)) // + .putAll(multiChannels("meter", 0, 9, "ActiveProductionEnergy", DataType.LONG)) // .putAll(multiChannels("pvInverter", 0, 9, "ActiveProductionEnergy", DataType.LONG)) // + .put("ctrlEssTimeOfUseTariffDischarge0/DelayedTime", DataType.LONG) // .put("ctrlGridOptimizedCharge0/AvoidLowChargingTime", DataType.LONG) // .put("ctrlGridOptimizedCharge0/NoLimitationTime", DataType.LONG) // .put("ctrlGridOptimizedCharge0/SellToGridLimitTime", DataType.LONG) // diff --git a/io.openems.backend.timedata.influx/src/io/openems/backend/timedata/influx/TimedataInfluxDb.java b/io.openems.backend.timedata.influx/src/io/openems/backend/timedata/influx/TimedataInfluxDb.java index c2847c3aad2..d317bb74629 100644 --- a/io.openems.backend.timedata.influx/src/io/openems/backend/timedata/influx/TimedataInfluxDb.java +++ b/io.openems.backend.timedata.influx/src/io/openems/backend/timedata/influx/TimedataInfluxDb.java @@ -222,8 +222,6 @@ private void writeData(// channelEntry.getKey(), // channelEntry.getValue()); } - - this.influxConnector.write(point); } } diff --git a/io.openems.backend.timedata.timescaledb/src/io/openems/backend/timedata/timescaledb/TimedataTimescaleDb.java b/io.openems.backend.timedata.timescaledb/src/io/openems/backend/timedata/timescaledb/TimedataTimescaleDb.java index 9c38771435a..e9000a2dd63 100644 --- a/io.openems.backend.timedata.timescaledb/src/io/openems/backend/timedata/timescaledb/TimedataTimescaleDb.java +++ b/io.openems.backend.timedata.timescaledb/src/io/openems/backend/timedata/timescaledb/TimedataTimescaleDb.java @@ -97,7 +97,6 @@ public void write(String edgeId, AggregatedDataNotification data) { @Override public void write(String edgeId, ResendDataNotification data) { // TODO Auto-generated method stub - } @Override diff --git a/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/OnRequest.java b/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/OnRequest.java index 69fc91884a0..d6a92a7f6a9 100644 --- a/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/OnRequest.java +++ b/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/OnRequest.java @@ -17,6 +17,7 @@ import io.openems.backend.common.jsonrpc.request.SetUserAlertingConfigsRequest; import io.openems.backend.common.jsonrpc.request.SetUserInformationRequest; import io.openems.backend.common.jsonrpc.request.SubmitSetupProtocolRequest; +import io.openems.backend.common.jsonrpc.request.SubscribeEdgesRequest; import io.openems.backend.common.jsonrpc.response.AddEdgeToUserResponse; import io.openems.backend.common.jsonrpc.response.GetUserAlertingConfigsResponse; import io.openems.backend.common.jsonrpc.response.GetUserInformationResponse; @@ -35,7 +36,6 @@ import io.openems.common.jsonrpc.request.GetEdgesRequest; import io.openems.common.jsonrpc.request.LogoutRequest; import io.openems.common.jsonrpc.request.SubscribeChannelsRequest; -import io.openems.common.jsonrpc.request.SubscribeEdgesRequest; import io.openems.common.jsonrpc.request.SubscribeSystemLogRequest; import io.openems.common.jsonrpc.request.UpdateUserLanguageRequest; import io.openems.common.jsonrpc.response.AuthenticateResponse; diff --git a/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/UiWebsocketImpl.java b/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/UiWebsocketImpl.java index 89afe0e1257..802d8b7d9bd 100644 --- a/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/UiWebsocketImpl.java +++ b/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/UiWebsocketImpl.java @@ -1,10 +1,12 @@ package io.openems.backend.uiwebsocket.impl; +import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; @@ -17,6 +19,10 @@ import org.osgi.service.metatype.annotations.Designate; import org.slf4j.Logger; +import com.google.common.collect.TreeBasedTable; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; + import io.openems.backend.common.component.AbstractOpenemsBackendComponent; import io.openems.backend.common.edgewebsocket.EdgeCache; import io.openems.backend.common.edgewebsocket.EdgeWebsocket; @@ -31,6 +37,7 @@ import io.openems.common.jsonrpc.base.JsonrpcNotification; import io.openems.common.jsonrpc.base.JsonrpcRequest; import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess; +import io.openems.common.jsonrpc.notification.TimestampedDataNotification; import io.openems.common.utils.ThreadPoolUtils; import io.openems.common.websocket.AbstractWebsocketServer.DebugMode; @@ -45,6 +52,9 @@ }) public class UiWebsocketImpl extends AbstractOpenemsBackendComponent implements UiWebsocket, EventHandler { + private static final String EDGE_ID = "backend0"; + private static final String COMPONENT_ID = "uiwebsocket"; + private final ScheduledExecutorService debugLogExecutor = Executors.newSingleThreadScheduledExecutor(); protected WebsocketServer server = null; @@ -70,6 +80,13 @@ public UiWebsocketImpl() { @Activate private void activate(Config config) { this.config = config; + this.debugLogExecutor.scheduleWithFixedDelay(() -> { + var data = TreeBasedTable.create(); + var now = Instant.now().toEpochMilli(); + data.put(now, COMPONENT_ID + "/Connections", + new JsonPrimitive(this.server != null ? this.server.getConnections().size() : 0)); + this.timedataManager.write(EDGE_ID, new TimestampedDataNotification(data)); + }, 10, 10, TimeUnit.SECONDS); } @Deactivate diff --git a/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/WebsocketServer.java b/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/WebsocketServer.java index d5240e85937..2ebdea3695a 100644 --- a/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/WebsocketServer.java +++ b/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/WebsocketServer.java @@ -1,12 +1,20 @@ package io.openems.backend.uiwebsocket.impl; +import java.time.Instant; + import org.java_websocket.WebSocket; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.collect.TreeBasedTable; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; + import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; import io.openems.common.jsonrpc.base.JsonrpcMessage; +import io.openems.common.jsonrpc.notification.TimestampedDataNotification; +import io.openems.common.utils.ThreadPoolUtils; import io.openems.common.websocket.AbstractWebsocketServer; public class WebsocketServer extends AbstractWebsocketServer { @@ -22,6 +30,13 @@ public class WebsocketServer extends AbstractWebsocketServer { public WebsocketServer(UiWebsocketImpl parent, String name, int port, int poolSize, DebugMode debugMode) { super(name, port, poolSize, debugMode, (executor) -> { + // Store Metrics + var data = TreeBasedTable.create(); + var now = Instant.now().toEpochMilli(); + ThreadPoolUtils.debugMetrics(executor).forEach((key, value) -> { + data.put(now, "uiwebsocket/" + key, new JsonPrimitive(value)); + }); + parent.timedataManager.write("backend0", new TimestampedDataNotification(data)); }); this.parent = parent; this.onOpen = new OnOpen(parent); diff --git a/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/WsData.java b/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/WsData.java index 93d05baf4f8..765870108f2 100644 --- a/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/WsData.java +++ b/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/WsData.java @@ -185,6 +185,7 @@ public synchronized void handleSubscribeChannelsRequest(String edgeId, Subscribe * @param edgeIds the edges to subscribe */ public void handleSubscribeEdgesRequest(Set edgeIds) { + // TODO maybe only add and remove on explicit request this.subscribedEdges = edgeIds; } diff --git a/io.openems.common/src/io/openems/common/channel/Unit.java b/io.openems.common/src/io/openems/common/channel/Unit.java index a064ff5b296..8ad4a2ced40 100644 --- a/io.openems.common/src/io/openems/common/channel/Unit.java +++ b/io.openems.common/src/io/openems/common/channel/Unit.java @@ -274,7 +274,16 @@ public enum Unit { /** * Unit of Resistance [uOhm]. */ - MICROOHM("uOhm", OHM, -6); + MICROOHM("uOhm", OHM, -6), + + // ########## + // Pressure + // ########## + + /** + * Unit of Pressure [bar]. + */ + BAR("bar"); public final String symbol; public final Unit baseUnit; @@ -353,7 +362,7 @@ public String format(Object value, OpenemsType type) { MILLIWATT, WATT_HOURS, OHM, KILOOHM, SECONDS, AMPERE_HOURS, HOUR, CUMULATED_SECONDS, KILOAMPERE_HOURS, KILOVOLT_AMPERE, KILOVOLT_AMPERE_REACTIVE, KILOVOLT_AMPERE_REACTIVE_HOURS, KILOWATT_HOURS, MICROOHM, MILLIAMPERE_HOURS, MILLIOHM, MILLISECONDS, MINUTE, THOUSANDTH, VOLT_AMPERE_HOURS, - VOLT_AMPERE_REACTIVE_HOURS, WATT_HOURS_BY_WATT_PEAK, CUMULATED_WATT_HOURS -> // + VOLT_AMPERE_REACTIVE_HOURS, WATT_HOURS_BY_WATT_PEAK, CUMULATED_WATT_HOURS, BAR -> // value + " " + this.symbol; case ON_OFF -> // diff --git a/io.openems.common/src/io/openems/common/jsonrpc/notification/CurrentDataNotification.java b/io.openems.common/src/io/openems/common/jsonrpc/notification/CurrentDataNotification.java index 9497aa62363..b96ea55ab02 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/notification/CurrentDataNotification.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/notification/CurrentDataNotification.java @@ -7,6 +7,7 @@ import com.google.gson.JsonObject; import io.openems.common.jsonrpc.base.JsonrpcNotification; +import io.openems.common.utils.JsonUtils; /** * Represents a JSON-RPC Notification for sending the current data of all @@ -35,11 +36,8 @@ public CurrentDataNotification(Map data) { @Override public JsonObject getParams() { - var p = new JsonObject(); - for (Entry entry : this.data.entrySet()) { - p.add(entry.getKey(), entry.getValue()); - } - return p; + return this.data.entrySet().stream() // + .collect(JsonUtils.toJsonObject(Entry::getKey, Entry::getValue)); } } diff --git a/io.openems.common/src/io/openems/common/jsonrpc/notification/ResendDataNotification.java b/io.openems.common/src/io/openems/common/jsonrpc/notification/ResendDataNotification.java index 55369473296..43c9dc0b851 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/notification/ResendDataNotification.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/notification/ResendDataNotification.java @@ -14,7 +14,9 @@ * "jsonrpc": "2.0", * "method": "resendData", * "params": { - * [channelAddress]: string | number + * [timestamp: epoch in milliseconds]: { + * [channelAddress]: {@link JsonElement} + * } * } * } * @@ -38,4 +40,4 @@ public ResendDataNotification(TreeBasedTable data) { super(ResendDataNotification.METHOD, data); } -} \ No newline at end of file +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/response/AuthenticateResponse.java b/io.openems.common/src/io/openems/common/jsonrpc/response/AuthenticateResponse.java index 3a54d9a3f4d..a528760694a 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/response/AuthenticateResponse.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/response/AuthenticateResponse.java @@ -60,7 +60,8 @@ public JsonObject getResult() { .add("user", JsonUtils.buildJsonObject() // .addProperty("id", this.user.getId()) // .addProperty("name", this.user.getName()) // - .addProperty("language", this.language.name()) // + .addProperty("language", this.language.name())// + .addProperty("hasMultipleEdges", this.user.hasMultipleEdges())// .add("globalRole", this.user.getGlobalRole().asJson()) // .build()) // .add("edges", EdgeMetadata.toJson(this.edges)) // diff --git a/io.openems.common/src/io/openems/common/jsonrpc/response/QueryHistoricTimeseriesExportXlsxResponse.java b/io.openems.common/src/io/openems/common/jsonrpc/response/QueryHistoricTimeseriesExportXlsxResponse.java index cf7b9061053..83ae6e9d5df 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/response/QueryHistoricTimeseriesExportXlsxResponse.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/response/QueryHistoricTimeseriesExportXlsxResponse.java @@ -170,7 +170,7 @@ private static byte[] generatePayload(String edgeId, ZonedDateTime fromDate, Zon protected static void addBasicInfo(Worksheet ws, String edgeId, ZonedDateTime fromDate, ZonedDateTime toDate, ResourceBundle translationBundle) { - XlsxUtils.addStringValueBold(ws, 0, 0, "FEMS-Nr."); + XlsxUtils.addStringValueBold(ws, 0, 0, "Edge-Nr."); XlsxUtils.addStringValue(ws, 0, 1, edgeId); XlsxUtils.addStringValueBold(ws, 1, 0, translationBundle.getString("exportCreatedOn")); XlsxUtils.addStringValue(ws, 1, 1, ZonedDateTime.now().format(XlsxUtils.DATE_TIME_FORMATTER)); diff --git a/io.openems.common/src/io/openems/common/session/AbstractUser.java b/io.openems.common/src/io/openems/common/session/AbstractUser.java index 18c2c64cfc7..97e064bd888 100644 --- a/io.openems.common/src/io/openems/common/session/AbstractUser.java +++ b/io.openems.common/src/io/openems/common/session/AbstractUser.java @@ -3,6 +3,7 @@ import java.util.Collections; import java.util.NavigableMap; import java.util.Optional; +import java.util.TreeMap; /** * Represents a User; shared by OpenEMS Backend @@ -34,7 +35,7 @@ public abstract class AbstractUser { /** * Roles per Edge-ID. */ - private final NavigableMap roles; + private final NavigableMap roles = new TreeMap<>(); protected AbstractUser(String id, String name, Language language, Role globalRole, NavigableMap roles) { @@ -42,7 +43,7 @@ protected AbstractUser(String id, String name, Language language, Role globalRol this.name = name; this.language = language; this.globalRole = globalRole; - this.roles = roles; + this.roles.putAll(roles); } public String getId() { @@ -109,4 +110,11 @@ public void setRole(String edgeId, Role role) { this.roles.put(edgeId, role); } + /** + * Gets the Number of Devices, that the user is allowed to see. + * + * @return the numberOfDevices + */ + public abstract boolean hasMultipleEdges(); + } diff --git a/io.openems.common/src/io/openems/common/timedata/CommonTimedataService.java b/io.openems.common/src/io/openems/common/timedata/CommonTimedataService.java index 6bbfb42bd99..fdebd1f183c 100644 --- a/io.openems.common/src/io/openems/common/timedata/CommonTimedataService.java +++ b/io.openems.common/src/io/openems/common/timedata/CommonTimedataService.java @@ -38,6 +38,10 @@ public default QueryHistoricTimeseriesExportXlsxResponse handleQueryHistoricTime var energyData = this.queryHistoricEnergy(edgeId, request.getFromDate(), request.getToDate(), QueryHistoricTimeseriesExportXlsxResponse.ENERGY_CHANNELS); + if (powerData == null || energyData == null) { + return null; + } + try { return new QueryHistoricTimeseriesExportXlsxResponse(request.getId(), edgeId, request.getFromDate(), request.getToDate(), powerData, energyData, language); @@ -86,7 +90,7 @@ public static Resolution calculateResolution(ZonedDateTime fromDate, ZonedDateTi * * @param edgeId the Edge-ID * @param request the {@link QueryHistoricTimeseriesDataRequest} - * @return the query result + * @return the query result; possibly null */ public default SortedMap> queryHistoricData(String edgeId, QueryHistoricTimeseriesDataRequest request) throws OpenemsNamedException { @@ -106,7 +110,7 @@ public default SortedMap> * @param toDate the To-Date * @param channels the Channels * @param resolution the {@link Resolution} - * @return the query result + * @return the query result; possibly null */ public SortedMap> queryHistoricData(String edgeId, ZonedDateTime fromDate, ZonedDateTime toDate, Set channels, Resolution resolution) @@ -119,7 +123,7 @@ public SortedMap> queryHis * @param fromDate the From-Date * @param toDate the To-Date * @param channels the Channels - * @return the query result + * @return the query result; possibly null */ public SortedMap queryHistoricEnergy(String edgeId, ZonedDateTime fromDate, ZonedDateTime toDate, Set channels) throws OpenemsNamedException; @@ -138,7 +142,7 @@ public SortedMap queryHistoricEnergy(String edgeId, * @param toDate the To-Date * @param channels the Channels * @param resolution the {@link Resolution} - * @return the query result + * @return the query result; possibly null */ public SortedMap> queryHistoricEnergyPerPeriod(String edgeId, ZonedDateTime fromDate, ZonedDateTime toDate, Set channels, Resolution resolution) diff --git a/io.openems.common/src/io/openems/common/timedata/Resolution.java b/io.openems.common/src/io/openems/common/timedata/Resolution.java index 9124f7d0227..8afcc764ba1 100644 --- a/io.openems.common/src/io/openems/common/timedata/Resolution.java +++ b/io.openems.common/src/io/openems/common/timedata/Resolution.java @@ -58,4 +58,8 @@ public Long toSeconds() { return Duration.of(this.value, this.unit).toSeconds(); } + @Override + public String toString() { + return this.value + " " + this.unit; + } } diff --git a/io.openems.common/src/io/openems/common/types/OptionsEnum.java b/io.openems.common/src/io/openems/common/types/OptionsEnum.java index 981b3880d82..f2b705d7f3b 100644 --- a/io.openems.common/src/io/openems/common/types/OptionsEnum.java +++ b/io.openems.common/src/io/openems/common/types/OptionsEnum.java @@ -45,4 +45,48 @@ public default String asCamelCase() { public default boolean isUndefined() { return this.equals(this.getUndefined()); } + + /** + * Gets the Option value from a value or null (not UNDEFINED!). + * + * @param OptionsEnum + * @param enumClass the enum class + * @param value the value of the Option + * @return the enum value or null + */ + public static & OptionsEnum> T getOption(Class enumClass, int value) { + for (var e : enumClass.getEnumConstants()) { + if (e.getValue() == value) { + return e; + } + } + return null; + } + + /** + * Gets the Option value from a value. + * + * @param OptionsEnum + * @param enumClass the enum class + * @param value the value of the Option + * @return the enum value or getUndefined + */ + @SuppressWarnings("unchecked") + public static & OptionsEnum> T getOptionOrUndefined(Class enumClass, int value) { + var enumConstants = enumClass.getEnumConstants(); + if (enumConstants.length == 0) { + return null; + } + for (var e : enumConstants) { + if (e.getValue() == value) { + return e; + } + } + + if (enumClass.isInstance(enumConstants[0].getUndefined())) { + // TODO: Refactor OptionsEnum to support + return (T) enumConstants[0].getUndefined(); + } + return null; + } } diff --git a/io.openems.common/src/io/openems/common/utils/CollectorUtils.java b/io.openems.common/src/io/openems/common/utils/CollectorUtils.java index 667a75b7f76..f244178bd0b 100644 --- a/io.openems.common/src/io/openems/common/utils/CollectorUtils.java +++ b/io.openems.common/src/io/openems/common/utils/CollectorUtils.java @@ -1,10 +1,13 @@ package io.openems.common.utils; import java.util.Map; +import java.util.Map.Entry; import java.util.function.Function; import java.util.stream.Collector; import java.util.stream.Collectors; +import com.google.common.collect.TreeBasedTable; + public final class CollectorUtils { private CollectorUtils() { @@ -32,4 +35,25 @@ private CollectorUtils() { return Collectors.groupingBy(firstKeyMapper, Collectors.toMap(secondKeyMapper, valueMapper)); } + /** + * Creates a {@link Collector} which collects the given input to a + * {@link TreeBasedTable}. + * + * @param the type of the first map key + * @param the type of the second map key + * @param the type of the value + * @return the {@link Collector} + */ + public static final , KEY2 extends Comparable, VALUE> // + Collector>, ?, TreeBasedTable> toTreeBasedTable() { + return Collector.of(TreeBasedTable::create, (t, u) -> { + for (var entry : u.getValue().entrySet()) { + t.put(u.getKey(), entry.getKey(), entry.getValue()); + } + }, (t, u) -> { + t.putAll(u); + return t; + }); + } + } diff --git a/io.openems.common/src/io/openems/common/websocket/AbstractWebsocketClient.java b/io.openems.common/src/io/openems/common/websocket/AbstractWebsocketClient.java index 40ce77becef..7152b2b18d8 100644 --- a/io.openems.common/src/io/openems/common/websocket/AbstractWebsocketClient.java +++ b/io.openems.common/src/io/openems/common/websocket/AbstractWebsocketClient.java @@ -139,9 +139,8 @@ public void onClose(int code, String reason, boolean remote) { AbstractWebsocketClient.this.reconnectorWorker.triggerNextRun(); } }; - // Disable lost connection detection // https://github.com/TooTallNate/Java-WebSocket/wiki/Lost-connection-detection - this.ws.setConnectionLostTimeout(0); + this.ws.setConnectionLostTimeout(300); // initialize WsData var wsData = AbstractWebsocketClient.this.createWsData(); diff --git a/io.openems.common/src/io/openems/common/websocket/ClientReconnectorWorker.java b/io.openems.common/src/io/openems/common/websocket/ClientReconnectorWorker.java index 1cb260653e0..fc6f95c41a1 100644 --- a/io.openems.common/src/io/openems/common/websocket/ClientReconnectorWorker.java +++ b/io.openems.common/src/io/openems/common/websocket/ClientReconnectorWorker.java @@ -7,6 +7,7 @@ import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; import org.java_websocket.WebSocketImpl; import org.java_websocket.client.WebSocketClient; @@ -56,14 +57,21 @@ protected void forever() throws Exception { if (ws.getReadyState() != ReadyState.NOT_YET_CONNECTED) { // Copy of WebSocketClient#reconnectBlocking. // Do not 'reset' if WebSocket has never been connected before. - resetWebSocketClient(ws); + this.parent.logInfo(this.log, "# Reset WebSocket Client..."); + resetWebSocketClient(ws, this.parent::createWsData); + this.parent.logInfo(this.log, "# Reset WebSocket Client... done"); } try { + this.parent.logInfo(this.log, "# Connect Blocking [" + CONNECT_TIMEOUT_SECONDS + "]..."); ws.connectBlocking(CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS); + this.parent.logInfo(this.log, "# Connect Blocking [" + CONNECT_TIMEOUT_SECONDS + "]... done"); + } catch (IllegalStateException e) { // Catch "WebSocketClient objects are not reuseable" thrown by // WebSocketClient#connect(). Set WebSocketClient#connectReadThread to `null`. - resetWebSocketClient(ws); + this.parent.logInfo(this.log, "# Reset WebSocket Client after Exception... " + e.getMessage()); + resetWebSocketClient(ws, this.parent::createWsData); + this.parent.logInfo(this.log, "# Reset WebSocket Client after Exception... done"); } var end = Instant.now(); @@ -75,16 +83,20 @@ protected void forever() throws Exception { /** * This method is a copy of {@link WebSocketClient} reset()-method, because the - * original one may block at the call of 'closeBlocking()' method. + * original one may block at the call of 'closeBlocking()' method. It also sets + * the new attachment from the attachment supplier. * *

    * Waiting for https://github.com/TooTallNate/Java-WebSocket/pull/1251 to be * merged. * - * @param ws the {@link WebSocketClient} + * @param the type of the attachment + * @param ws the {@link WebSocketClient} + * @param attachmentSupplier the supplier for the new attachment * @throws Exception on error */ - protected static void resetWebSocketClient(WebSocketClient ws) throws Exception { + protected static void resetWebSocketClient(WebSocketClient ws, Supplier attachmentSupplier) + throws Exception { /* * Get methods and fields via Reflection */ @@ -155,7 +167,11 @@ protected static void resetWebSocketClient(WebSocketClient ws) throws Exception // closeLatch = new CountDownLatch(1); -> to reflection closeLatchField.set(ws, new CountDownLatch(1)); // this.engine = new WebSocketImpl(this, this.draft); -> to reflection - engineField.set(ws, new WebSocketImpl(ws, draft)); + final var newEngine = new WebSocketImpl(ws, draft); + final var newAttachment = attachmentSupplier.get(); + newAttachment.setWebsocket(ws); + newEngine.setAttachment(newAttachment); + engineField.set(ws, newEngine); } @Override diff --git a/io.openems.common/src/io/openems/common/websocket/WsData.java b/io.openems.common/src/io/openems/common/websocket/WsData.java index dc448a981a0..f7a9f595ac7 100644 --- a/io.openems.common/src/io/openems/common/websocket/WsData.java +++ b/io.openems.common/src/io/openems/common/websocket/WsData.java @@ -31,6 +31,7 @@ public abstract class WsData { /** * Holds Futures for JSON-RPC Requests. */ + // TODO add timeout to requestFutures private final ConcurrentHashMap> requestFutures = new ConcurrentHashMap<>(); /** diff --git a/io.openems.common/src/io/openems/common/worker/AbstractWorker.java b/io.openems.common/src/io/openems/common/worker/AbstractWorker.java index feffe43dc5f..7a3e7b4bd82 100644 --- a/io.openems.common/src/io/openems/common/worker/AbstractWorker.java +++ b/io.openems.common/src/io/openems/common/worker/AbstractWorker.java @@ -35,32 +35,57 @@ public abstract class AbstractWorker { private final AtomicBoolean isStopped = new AtomicBoolean(false); private final Mutex cycleMutex = new Mutex(false); + /** + * Initializes the worker and starts the worker thread. + * + * @param name the name of the worker thread + * @param initiallyTriggerNextRun true if the {@link AbstractWorker#forever()} method + * should get called immediately; if not false + */ + public void activate(String name, boolean initiallyTriggerNextRun) { + this.startWorker(name, initiallyTriggerNextRun); + } + /** * Initializes the worker and starts the worker thread. * * @param name the name of the worker thread */ public void activate(String name) { - this.startWorker(name); + this.activate(name, true); } /** * Modifies the worker thread. * - * @param name the name of the worker thread + * @param name the name of the worker thread + * @param initiallyTriggerNextRun true if the {@link AbstractWorker#forever()} method + * should get called immediately; if not false */ - public void modified(String name) { + public void modified(String name, boolean initiallyTriggerNextRun) { if (!this.thread.isAlive() && !this.thread.isInterrupted() && !this.isStopped.get()) { - this.startWorker(name); + this.startWorker(name, initiallyTriggerNextRun); } } - private void startWorker(String name) { + /** + * Modifies the worker thread. + * + * @param name the name of the worker thread + */ + public void modified(String name) { + this.modified(name, true); + } + + private void startWorker(String name, boolean autoTriggerNextRun) { if (name != null) { this.thread.setName(name); } this.thread.start(); - this.triggerNextRun(); + + if (autoTriggerNextRun) { + this.triggerNextRun(); + } } /** diff --git a/io.openems.common/test/io/openems/common/utils/CollectorUtilsTest.java b/io.openems.common/test/io/openems/common/utils/CollectorUtilsTest.java new file mode 100644 index 00000000000..72a22541892 --- /dev/null +++ b/io.openems.common/test/io/openems/common/utils/CollectorUtilsTest.java @@ -0,0 +1,28 @@ +package io.openems.common.utils; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import com.google.common.collect.ImmutableTable; +import com.google.common.collect.Table.Cell; + +public class CollectorUtilsTest { + + @Test + public void testToDoubleMap() { + final var map = ImmutableTable.builder() // + .put("row", 1, 0.5) // + .build(); + + final var collectedMap = map.cellSet().stream() // + .collect(CollectorUtils.toDoubleMap(// + Cell::getRowKey, // + Cell::getColumnKey, // + Cell::getValue) // + ); + + assertEquals(0.5, collectedMap.get("row").get(1), 0); + } + +} diff --git a/io.openems.common/test/io/openems/common/utils/JsonUtilsTest.java b/io.openems.common/test/io/openems/common/utils/JsonUtilsTest.java index 046f2377bf2..4d196e5c3ac 100644 --- a/io.openems.common/test/io/openems/common/utils/JsonUtilsTest.java +++ b/io.openems.common/test/io/openems/common/utils/JsonUtilsTest.java @@ -11,6 +11,7 @@ import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.List; +import java.util.Map.Entry; import java.util.Optional; import java.util.UUID; import java.util.stream.Stream; @@ -18,6 +19,7 @@ import org.junit.Test; import org.junit.function.ThrowingRunnable; +import com.google.common.collect.ImmutableMap; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonNull; @@ -111,6 +113,24 @@ public void testJsonArrayCollector() throws OpenemsNamedException { assertEquals(j, l); } + @Test + public void testJsonObjectCollector() throws OpenemsNamedException { + final var map = ImmutableMap.builder() // + .put("1", new JsonPrimitive("1")) // + .put("2", new JsonPrimitive(2)) // + .put("3", new JsonPrimitive(3.25)) // + .put("4", new JsonPrimitive(false)) // + .build(); + + final var jsonObject = map.entrySet().parallelStream() // + .collect(JsonUtils.toJsonObject(Entry::getKey, Entry::getValue)); + + assertEquals("1", jsonObject.get("1").getAsString()); + assertEquals(2, jsonObject.get("2").getAsInt()); + assertEquals(3.25, jsonObject.get("3").getAsDouble(), 0.0); + assertEquals(false, jsonObject.get("4").getAsBoolean()); + } + @Test public void testBuilder() throws OpenemsNamedException { JsonUtils.buildJsonArray(JSON_ARRAY) // diff --git a/io.openems.common/test/io/openems/common/websocket/ClientReconnectorWorkerTest.java b/io.openems.common/test/io/openems/common/websocket/ClientReconnectorWorkerTest.java index 0fa4149b29e..39697e8793e 100644 --- a/io.openems.common/test/io/openems/common/websocket/ClientReconnectorWorkerTest.java +++ b/io.openems.common/test/io/openems/common/websocket/ClientReconnectorWorkerTest.java @@ -87,8 +87,8 @@ protected void logError(Logger log, String message) { @Ignore @Test public void testResetWebSocketClient() throws Exception { - var ws = new MyWebsocketClient("name", URI.create("ws://localhost")).ws; - ClientReconnectorWorker.resetWebSocketClient(ws); + final var websocketClient = new MyWebsocketClient("name", URI.create("ws://localhost")); + ClientReconnectorWorker.resetWebSocketClient(websocketClient.ws, websocketClient::createWsData); } } diff --git a/io.openems.edge.battery.api/src/io/openems/edge/battery/api/Battery.java b/io.openems.edge.battery.api/src/io/openems/edge/battery/api/Battery.java index f7e376a313b..b3ef4be55c3 100644 --- a/io.openems.edge.battery.api/src/io/openems/edge/battery/api/Battery.java +++ b/io.openems.edge.battery.api/src/io/openems/edge/battery/api/Battery.java @@ -192,7 +192,8 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { * */ MIN_CELL_VOLTAGE(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIVOLT)), + .unit(Unit.MILLIVOLT) // + .persistencePriority(PersistencePriority.HIGH)), /** * Maximum cell voltage. diff --git a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/BmwBattery.java b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/BmwBattery.java index 7f203c0d394..b5afa188d90 100644 --- a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/BmwBattery.java +++ b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/BmwBattery.java @@ -3,6 +3,7 @@ import org.osgi.service.event.EventHandler; import io.openems.common.channel.AccessMode; +import io.openems.common.channel.PersistencePriority; import io.openems.common.channel.Unit; import io.openems.common.types.OpenemsType; import io.openems.edge.battery.bmw.enums.BmsState; @@ -262,6 +263,7 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId .accessMode(AccessMode.READ_ONLY)), // SERIAL_NUMBER(Doc.of(OpenemsType.INTEGER) // + .persistencePriority(PersistencePriority.HIGH) // .accessMode(AccessMode.READ_ONLY)), // SOFTWARE_VERSION(Doc.of(OpenemsType.INTEGER) // diff --git a/io.openems.edge.battery.fenecon.commercial/src/io/openems/edge/battery/fenecon/commercial/BatteryFeneconCommercial.java b/io.openems.edge.battery.fenecon.commercial/src/io/openems/edge/battery/fenecon/commercial/BatteryFeneconCommercial.java index cedc2ddc580..075a35cae80 100644 --- a/io.openems.edge.battery.fenecon.commercial/src/io/openems/edge/battery/fenecon/commercial/BatteryFeneconCommercial.java +++ b/io.openems.edge.battery.fenecon.commercial/src/io/openems/edge/battery/fenecon/commercial/BatteryFeneconCommercial.java @@ -2,6 +2,7 @@ import io.openems.common.channel.AccessMode; import io.openems.common.channel.Level; +import io.openems.common.channel.PersistencePriority; import io.openems.common.channel.Unit; import io.openems.common.types.OpenemsType; import io.openems.edge.battery.api.Battery; @@ -136,11 +137,13 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { .text("The maximum number of stop attempts failed")), // NUMBER_OF_TOWERS(new IntegerDoc() // .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.HIGH) // .text("Number of modules per tower") // .onChannelChange( BatteryFeneconCommercialImpl::updateNumberOfTowersAndModulesAndCells)), NUMBER_OF_MODULES_PER_TOWER(new IntegerDoc() // .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.HIGH) // .text("Number of modules per tower") // .onChannelChange( BatteryFeneconCommercialImpl::updateNumberOfTowersAndModulesAndCells)), @@ -245,92 +248,56 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { .unit(Unit.CUMULATED_WATT_HOURS)), // // 1.2 SysProtectMessage - SYSTEM_FAULT_COUNTERS(Doc.of(OpenemsType.INTEGER) // - .text("Fault counters")), // - FAULT_STATUS(Doc.of(OpenemsType.BOOLEAN) // - .text("Fault Status")), // - POWER_ON(Doc.of(OpenemsType.BOOLEAN) // - .text("Power On")), // - LOW_SELF_CONSUMPTION_STATUS(Doc.of(Level.WARNING) // - .text("Low self consumption status")), // - FAULT(Doc.of(OpenemsType.BOOLEAN) // - .text("Fault")), // - RUNNING(Doc.of(OpenemsType.BOOLEAN) // - .text("Running")), // - EXTERNAL_COMMUNICATION_ONLY_UNDER_STANDBY(Doc.of(Level.WARNING) // - .text("External communication only under standby")), // - MAIN_SWITCH_STATUS(Doc.of(OpenemsType.BOOLEAN) // - .text("Main switch status")), // - BATTERY_ONLINE(Doc.of(OpenemsType.BOOLEAN) // - .text("Battery online")), // - PCS_ONLINE(Doc.of(OpenemsType.BOOLEAN) // - .text("Pcs online")), // - UPS_ONLINE(Doc.of(OpenemsType.BOOLEAN) // - .text("Ups online")), // - STS_ONLINE(Doc.of(OpenemsType.BOOLEAN) // - .text("Sts online")), // - BATTERY_18650_LOW(Doc.of(OpenemsType.BOOLEAN) // - .text("Battery 18650 Low")), // - MASTER_CPU_INITIALIZE(Doc.of(OpenemsType.BOOLEAN) // - .text("Master cpu initilalize")), // - SLAVE_CPU_INITIALIZE(Doc.of(OpenemsType.BOOLEAN) // - .text("Slave cpu initilalize")), // - BATTERY_SYSTEM_INITIALIZE_ACTIVE(Doc.of(OpenemsType.BOOLEAN) // - .text("Battery system initilalize active")), // - PCS_INITIALIZE_ACTIVE(Doc.of(OpenemsType.BOOLEAN) // - .text("Pcs initilalize active")), // - UPS_INITIALIZE_ACTIVE(Doc.of(OpenemsType.BOOLEAN) // - .text("Ups initilalize active")), // - MASTER_CPU_INITIALIZE_FINISH(Doc.of(OpenemsType.BOOLEAN) // - .text("Master cpu initilalize finish")), // - SLAVE_CPU_INITIALIZE_FINISH(Doc.of(OpenemsType.BOOLEAN) // - .text("Slave cpu initilalize finish")), // - BATTERY_SYSTEM_INITIALIZE_FINISH(Doc.of(OpenemsType.BOOLEAN) // - .text("Battetry system initilalize finish")), // - PCS_INITIALIZE_FINISH(Doc.of(OpenemsType.BOOLEAN) // - .text("Pcs initilalize finish")), // - UPS_INITIALIZE_FINISH(Doc.of(OpenemsType.BOOLEAN) // - .text("Ups initilalize finish")), // - MASTER_CPU_INITIALIZE_FAIL(Doc.of(OpenemsType.BOOLEAN) // - .text("Master cpu initilalize fail")), // - SLAVE_CPU_INITIALIZE_FAIL(Doc.of(OpenemsType.BOOLEAN) // - .text("Slave cpu initilalize fail")), // - BATTERY_SYSTEM_INITIALIZE_FAIL(Doc.of(OpenemsType.BOOLEAN) // - .text("Battery system initilalize fail")), // - PCS_INITIALIZE_FAIL(Doc.of(OpenemsType.BOOLEAN) // - .text("Pcs initilalize fail")), // - UPS_INITIALIZE_FAIL(Doc.of(OpenemsType.BOOLEAN) // - .text("Ups initilalize fail")), // - DRY_CONTACT_FAIL(Doc.of(Level.WARNING) // - .text("Dry contact fail")), // + SYSTEM_FAULT_COUNTERS(Doc.of(OpenemsType.INTEGER)), // + FAULT_STATUS(Doc.of(OpenemsType.BOOLEAN)), // + POWER_ON(Doc.of(OpenemsType.BOOLEAN)), // + LOW_SELF_CONSUMPTION_STATUS(Doc.of(OpenemsType.BOOLEAN)), // + FAULT(Doc.of(OpenemsType.BOOLEAN)), // + RUNNING(Doc.of(OpenemsType.BOOLEAN)), // + EXTERNAL_COMMUNICATION_ONLY_UNDER_STANDBY(Doc.of(OpenemsType.BOOLEAN)), // + MAIN_SWITCH_STATUS(Doc.of(OpenemsType.BOOLEAN)), // + BATTERY_ONLINE(Doc.of(OpenemsType.BOOLEAN)), // + PCS_ONLINE(Doc.of(OpenemsType.BOOLEAN)), // + UPS_ONLINE(Doc.of(OpenemsType.BOOLEAN)), // + STS_ONLINE(Doc.of(OpenemsType.BOOLEAN)), // + BATTERY_18650_LOW(Doc.of(OpenemsType.BOOLEAN)), // + MASTER_CPU_INITIALIZE(Doc.of(OpenemsType.BOOLEAN)), // + SLAVE_CPU_INITIALIZE(Doc.of(OpenemsType.BOOLEAN)), // + BATTERY_SYSTEM_INITIALIZE_ACTIVE(Doc.of(OpenemsType.BOOLEAN)), // + PCS_INITIALIZE_ACTIVE(Doc.of(OpenemsType.BOOLEAN)), // + UPS_INITIALIZE_ACTIVE(Doc.of(OpenemsType.BOOLEAN)), // + MASTER_CPU_INITIALIZE_FINISH(Doc.of(OpenemsType.BOOLEAN)), // + SLAVE_CPU_INITIALIZE_FINISH(Doc.of(OpenemsType.BOOLEAN)), // + BATTERY_SYSTEM_INITIALIZE_FINISH(Doc.of(OpenemsType.BOOLEAN)), // + PCS_INITIALIZE_FINISH(Doc.of(OpenemsType.BOOLEAN)), // + UPS_INITIALIZE_FINISH(Doc.of(OpenemsType.BOOLEAN)), // + MASTER_CPU_INITIALIZE_FAIL(Doc.of(OpenemsType.BOOLEAN)), // + SLAVE_CPU_INITIALIZE_FAIL(Doc.of(OpenemsType.BOOLEAN)), // + BATTERY_SYSTEM_INITIALIZE_FAIL(Doc.of(OpenemsType.BOOLEAN)), // + PCS_INITIALIZE_FAIL(Doc.of(OpenemsType.BOOLEAN)), // + UPS_INITIALIZE_FAIL(Doc.of(OpenemsType.BOOLEAN)), // + DRY_CONTACT_FAIL(Doc.of(OpenemsType.BOOLEAN)), // POWER_SUPPLY_24V_FAIL(Doc.of(Level.WARNING) // .text("Power supply 24V fail")), // EEPROM2_FAULT(Doc.of(Level.WARNING) // .text("Eeprom 2 fault")), // - BATTERY_18650_FAULT(Doc.of(Level.WARNING) // - .text("Battery 18650 fault")), // + BATTERY_18650_FAULT(Doc.of(OpenemsType.BOOLEAN)), // BATTERY_SYSTEM_FAULT(Doc.of(Level.WARNING) // .text("Batery System fault")), // NO_BATTERY(Doc.of(Level.WARNING) // .text("No battery")), // - PCS_FAULT(Doc.of(OpenemsType.BOOLEAN) // - .text("Pcs fault")), // - NO_PCS(Doc.of(OpenemsType.BOOLEAN) // - .text("No pcs")), // - UPS_FAULT(Doc.of(Level.WARNING) // - .text("Ups fault")), // - NO_UPS(Doc.of(Level.WARNING) // - .text("No ups")), // + PCS_FAULT(Doc.of(OpenemsType.BOOLEAN)), // + NO_PCS(Doc.of(OpenemsType.BOOLEAN)), // + UPS_FAULT(Doc.of(OpenemsType.BOOLEAN)), // + NO_UPS(Doc.of(OpenemsType.BOOLEAN)), // INSULATION_RESISTANCE_DETECTION_FAULT(Doc.of(Level.WARNING) // .text("Insulation resistance detection fault")), // SLAVE_MCU_FAULT(Doc.of(Level.WARNING) // .text("Slave mcu fault")), // SYSTEM_TEMPERATURE_FAULT(Doc.of(Level.WARNING) // .text("System temperature fault")), // - PCS_STOP(Doc.of(OpenemsType.BOOLEAN) // - .text("Pcs stop")), // - METER_FAULT(Doc.of(Level.WARNING) // - .text("Meter fault")), // + PCS_STOP(Doc.of(OpenemsType.BOOLEAN)), // + METER_FAULT(Doc.of(OpenemsType.BOOLEAN)), // BATTERY_TOWERS_TEMPERATURE_SENSORS_FAULT(Doc.of(Level.WARNING) // .text("Battery towers temperature sensor fault")), // SYSTEM_TEMPERATURE_SENSORS_FAULT(Doc.of(Level.WARNING) // @@ -347,26 +314,20 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { .text("Eeprom fault")), // FLASH_FAULT(Doc.of(Level.WARNING) // .text("Flash fault")), // - EMS_FAULT(Doc.of(Level.WARNING) // - .text("Ems fault")), // + EMS_FAULT(Doc.of(OpenemsType.BOOLEAN)), // SD_FAULT(Doc.of(Level.WARNING) // .text("Sd fault")), // - BATTERY_18650_WARNING(Doc.of(Level.WARNING) // - .text("Battery 18650 warning")), // + BATTERY_18650_WARNING(Doc.of(OpenemsType.BOOLEAN)), // MASTER_BATTERY_WARNING(Doc.of(Level.WARNING) // .text("Master battery warning")), // - PCS_WARNING(Doc.of(Level.WARNING) // - .text("Pcs warning")), // - UPS_WARNING(Doc.of(Level.WARNING) // - .text("Ups warning")), // + PCS_WARNING(Doc.of(OpenemsType.BOOLEAN)), // + UPS_WARNING(Doc.of(OpenemsType.BOOLEAN)), // SLAVE_MCU_WARNING(Doc.of(Level.WARNING) // .text("Slave mcu warning")), // - SYSTEM_TOO_MUCH_OVER_TEMPERATURE_WARNING(Doc.of(Level.WARNING) // - .text("System too much over temperature warning")), // + SYSTEM_TOO_MUCH_OVER_TEMPERATURE_WARNING(Doc.of(OpenemsType.BOOLEAN)), // SYSTEM_OVER_TEMPERATURE_WARNING(Doc.of(Level.WARNING) // .text("System over tmeperature warning")), // - SYSTEM_TOO_MUCH_LOW_TEMPERATURE_WARNING(Doc.of(Level.WARNING) // - .text("Syste, too much low temperature warning")), // + SYSTEM_TOO_MUCH_LOW_TEMPERATURE_WARNING(Doc.of(OpenemsType.BOOLEAN)), // SYSTEM_LOW_TEMPERATURE_WARNING(Doc.of(Level.WARNING) // .text("System low temperature warning")), // FAN_FAULT(Doc.of(Level.WARNING) // @@ -378,22 +339,16 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { .text("System temperature sensors warning")), // STS_WARNING(Doc.of(Level.WARNING) // .text("Sts warning")), // - PCS_TEMPERATURE_WARNING(Doc.of(Level.WARNING) // - .text("Pcb temperature warning")), // - PCS_OVER_TEMPERATURE(Doc.of(Level.WARNING) // - .text("Pcs over Temperature")), // - OVER_TEMPERATURE_STOP_PCS(Doc.of(Level.WARNING) // - .text("Over temperature stop PCS")), // - LOW_TEMPERATURE_STOP_PCS(Doc.of(Level.WARNING) // - .text("Low temperature stop PCS")), // - OVER_CURRENT_STOP_CHARGING(Doc.of(Level.WARNING) // - .text("Over current stop charging")), // - OVER_CURRENT_STOP_DISCHARGING(Doc.of(Level.WARNING) // - .text("Over current stop discharging")), // - OVER_TEMPERATURE_STOP_CHARGING(Doc.of(Level.WARNING) // - .text("Over temperature stop charging")), // - LOW_TEMPERATURE_STOP_DISCHARGING(Doc.of(Level.WARNING) // - .text("Low temperature stop discharging")), // + PCS_TEMPERATURE_WARNING(Doc.of(OpenemsType.BOOLEAN)), // + PCS_OVER_TEMPERATURE(Doc.of(OpenemsType.BOOLEAN)), // + COMMUNICATION_STOP_CHARGING(Doc.of(OpenemsType.BOOLEAN)), // + COMMUNICATION_STOP_DISCHARGING(Doc.of(OpenemsType.BOOLEAN)), // + OVER_TEMPERATURE_STOP_PCS(Doc.of(OpenemsType.BOOLEAN)), // + LOW_TEMPERATURE_STOP_PCS(Doc.of(OpenemsType.BOOLEAN)), // + OVER_CURRENT_STOP_CHARGING(Doc.of(OpenemsType.BOOLEAN)), // + OVER_CURRENT_STOP_DISCHARGING(Doc.of(OpenemsType.BOOLEAN)), // + OVER_TEMPERATURE_STOP_CHARGING(Doc.of(OpenemsType.BOOLEAN)), // + LOW_TEMPERATURE_STOP_DISCHARGING(Doc.of(OpenemsType.BOOLEAN)), // VOLTAGE_DIFFERENCE_HIGH_STOP_PCS(Doc.of(Level.WARNING) // .text("Voltage difference high stop PCS")), // POWER_HIGH_STOP_PCS(Doc.of(Level.WARNING) // @@ -420,6 +375,9 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { BATTERY_MIN_CELL_VOLT(Doc.of(OpenemsType.INTEGER) // .accessMode(AccessMode.READ_ONLY)// .unit(Unit.MILLIVOLT)), // + MASTER_SERIAL_NUMBER(Doc.of(OpenemsType.STRING) // + .persistencePriority(PersistencePriority.HIGH) // + .accessMode(AccessMode.READ_ONLY)), // BATTERY_NOMINAL_POWER(Doc.of(OpenemsType.LONG) // .accessMode(AccessMode.READ_ONLY)// .unit(Unit.WATT)), // diff --git a/io.openems.edge.battery.fenecon.commercial/src/io/openems/edge/battery/fenecon/commercial/BatteryFeneconCommercialImpl.java b/io.openems.edge.battery.fenecon.commercial/src/io/openems/edge/battery/fenecon/commercial/BatteryFeneconCommercialImpl.java index f2d979b9212..b35aa283f69 100644 --- a/io.openems.edge.battery.fenecon.commercial/src/io/openems/edge/battery/fenecon/commercial/BatteryFeneconCommercialImpl.java +++ b/io.openems.edge.battery.fenecon.commercial/src/io/openems/edge/battery/fenecon/commercial/BatteryFeneconCommercialImpl.java @@ -29,11 +29,13 @@ import io.openems.common.channel.AccessMode; import io.openems.common.channel.Level; +import io.openems.common.channel.PersistencePriority; import io.openems.common.channel.Unit; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; import io.openems.common.types.ChannelAddress; import io.openems.common.types.OpenemsType; +import io.openems.common.utils.StringUtils; import io.openems.edge.battery.api.Battery; import io.openems.edge.battery.fenecon.commercial.statemachine.Context; import io.openems.edge.battery.fenecon.commercial.statemachine.StateMachine; @@ -52,6 +54,7 @@ import io.openems.edge.bridge.modbus.api.element.StringWordElement; import io.openems.edge.bridge.modbus.api.element.UnsignedDoublewordElement; import io.openems.edge.bridge.modbus.api.element.UnsignedWordElement; +import io.openems.edge.bridge.modbus.api.element.WordOrder; import io.openems.edge.bridge.modbus.api.task.FC16WriteRegistersTask; import io.openems.edge.bridge.modbus.api.task.FC3ReadRegistersTask; import io.openems.edge.common.channel.BooleanWriteChannel; @@ -145,25 +148,34 @@ protected void deactivate() { SCALE_FACTOR_MINUS_2); /** - * Generates serial number based on specific bitwise operation. Helps to build - * Master, Sub-master and Modules Serial Numbers. - * - * @param value Read value from the Modbus register. - * @return {@link String} buildSerialNumber. + * Converts master and sub master versions. e.g.input: "012001SM05" --> + * MASTER_MCU_HARDWARE_VERSION, "100201MS50" */ - protected static final ElementToChannelConverter SERIAL_NUMBER_CONVERTER = new ElementToChannelConverter(v -> { + protected static final ElementToChannelConverter VERSION_CONVERTER = new ElementToChannelConverter(v -> { if (v == null) { return null; } String value = TypeUtils.getAsType(OpenemsType.STRING, v); - var readString = new StringBuilder(value); var result = new StringBuilder(); - var reverse = new StringBuilder(); for (var i = 0; i <= value.length() - 2; i += 2) { - var subString = new StringBuilder(readString.substring(i, i + 2)); - reverse = subString.reverse(); - result = result.append(reverse); - reverse = null; + result.append(StringUtils.reverse(value.substring(i, i + 2))); + } + return result.toString(); + }); + + /** + * Generates serial number converter helper to read master and battery module + * serial numbers. e.g. input: "17005740232238EMSD0W0000" --> + * MASTER_SERIAL_NUMBER, "00000WSDEM38222340570017" + */ + protected static final ElementToChannelConverter SERIAL_NUMBER_CONVERTER = new ElementToChannelConverter(v -> { + if (v == null) { + return null; + } + String value = TypeUtils.getAsType(OpenemsType.STRING, Long.toHexString((long) v)); + var result = new StringBuilder(); + for (var i = 0; i <= value.length() - 2; i += 2) { + result.insert(0, value.substring(i, i + 2)); } return result.toString(); }); @@ -174,14 +186,18 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException { // Versions new FC3ReadRegistersTask(0, Priority.LOW, // m(BatteryFeneconCommercial.ChannelId.MASTER_MCU_HARDWARE_VERSION, new StringWordElement(0, 5), - SERIAL_NUMBER_CONVERTER), + VERSION_CONVERTER), m(BatteryFeneconCommercial.ChannelId.MASTER_MCU_FIRMWARE_VERSION, new StringWordElement(5, 2), - SERIAL_NUMBER_CONVERTER)), // + VERSION_CONVERTER)), // new FC3ReadRegistersTask(2176, Priority.LOW, // m(BatteryFeneconCommercial.ChannelId.SLAVE_MCU_HARDWARE_VERSION, new StringWordElement(2176, 5), - SERIAL_NUMBER_CONVERTER), + VERSION_CONVERTER), m(BatteryFeneconCommercial.ChannelId.SLAVE_MCU_FIRMWARE_VERSION, new StringWordElement(2181, 2), - SERIAL_NUMBER_CONVERTER)), + VERSION_CONVERTER)), + + new FC3ReadRegistersTask(2762, Priority.LOW, // + m(BatteryFeneconCommercial.ChannelId.MASTER_SERIAL_NUMBER, new StringWordElement(2762, 12), + VERSION_CONVERTER)), new FC3ReadRegistersTask(17, Priority.LOW, // m(new UnsignedWordElement(17)).build().onUpdateCallback(new ByteElement(this, // @@ -255,9 +271,8 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException { m(BatteryFeneconCommercial.ChannelId.INSULATION_RESISTANCE_AT_POSITIVE_POLE, new SignedDoublewordElement(2708).wordOrder(LSWMSW), SCALE_FACTOR_MINUS_1), // m(BatteryFeneconCommercial.ChannelId.INSULATION_RESISTANCE_AT_NEGATIVE_POLE, - new SignedDoublewordElement(2710).wordOrder(LSWMSW), SCALE_FACTOR_MINUS_1)), // - - new FC3ReadRegistersTask(2720, Priority.LOW, // + new SignedDoublewordElement(2710).wordOrder(LSWMSW), SCALE_FACTOR_MINUS_1), // + new DummyRegisterElement(2712, 2719), // m(BatteryFeneconCommercial.ChannelId.TOTAL_CHARGE_CAPACITY_WATT_HOURS, new SignedQuadruplewordElement(2720).wordOrder(LSWMSW), SCALE_FACTOR_MINUS_1), // m(BatteryFeneconCommercial.ChannelId.TOTAL_DISCHARGE_CAPACITY_WATT_HOURS, @@ -273,13 +288,15 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException { .setNextValue((value & 0x100) >> 8); this.channel(BatteryFeneconCommercial.ChannelId.LOW_SELF_CONSUMPTION_STATUS) .setNextValue((value & 0x200) >> 9); - this.channel(BatteryFeneconCommercial.ChannelId.FAULT).setNextValue((value & 0x400) >> 10); + this.channel(BatteryFeneconCommercial.ChannelId.FAULT)// + .setNextValue((value & 0x400) >> 10); this.channel(BatteryFeneconCommercial.ChannelId.RUNNING) .setNextValue((value & 0x1000) >> 12); this.channel(BatteryFeneconCommercial.ChannelId.EXTERNAL_COMMUNICATION_ONLY_UNDER_STANDBY) .setNextValue((value & 0x2000) >> 13); - }), // + })), // + new FC3ReadRegistersTask(2730, Priority.LOW, // m(new BitsWordElement(2730, this) // .bit(0, BatteryFeneconCommercial.ChannelId.MAIN_SWITCH_STATUS) // .bit(1, BatteryFeneconCommercial.ChannelId.BATTERY_ONLINE) // @@ -351,8 +368,10 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException { .bit(12, BatteryFeneconCommercial.ChannelId.STS_WARNING) // .bit(13, BatteryFeneconCommercial.ChannelId.PCS_TEMPERATURE_WARNING) // .bit(14, BatteryFeneconCommercial.ChannelId.PCS_OVER_TEMPERATURE) // + .bit(15, BatteryFeneconCommercial.ChannelId.COMMUNICATION_STOP_CHARGING) // ), // m(new BitsWordElement(2735, this) // + .bit(0, BatteryFeneconCommercial.ChannelId.COMMUNICATION_STOP_DISCHARGING) // .bit(1, BatteryFeneconCommercial.ChannelId.OVER_TEMPERATURE_STOP_PCS) // .bit(2, BatteryFeneconCommercial.ChannelId.LOW_TEMPERATURE_STOP_PCS) // .bit(3, BatteryFeneconCommercial.ChannelId.OVER_CURRENT_STOP_CHARGING) // @@ -379,8 +398,9 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException { m(BatteryFeneconCommercial.ChannelId.BATTERY_NOMINAL_POWER, new UnsignedDoublewordElement(2774).wordOrder(LSWMSW)), // m(BatteryFeneconCommercial.ChannelId.BATTERY_AVAILABLE_POWER, - new UnsignedDoublewordElement(2776).wordOrder(LSWMSW)), // - new DummyRegisterElement(2778, 2784), // + new UnsignedDoublewordElement(2776).wordOrder(LSWMSW))), // + + new FC3ReadRegistersTask(2785, Priority.HIGH, // m(new UnsignedWordElement(2785)).build().onUpdateCallback(value -> { if (value == null) { return; @@ -472,9 +492,9 @@ private synchronized void initializeTowerModulesChannels(int numberOfTowers, int // Cold Data, 2s frequency ==> Master Machine Summary Low Map 1.3 new FC3ReadRegistersTask(towerOffset + 8, Priority.LOW, // m(generateTowerChannel(this, towerNum, "SUB_MASTER_HARDWARE_VERSION", OpenemsType.STRING, - Unit.NONE), new StringWordElement(towerOffset + 8, 5), SERIAL_NUMBER_CONVERTER), + Unit.NONE), new StringWordElement(towerOffset + 8, 5), VERSION_CONVERTER), m(generateTowerChannel(this, towerNum, "SUB_MASTER_FIRMWARE_VERSION", OpenemsType.STRING, - Unit.NONE), new StringWordElement(towerOffset + 13, 2), SERIAL_NUMBER_CONVERTER)), + Unit.NONE), new StringWordElement(towerOffset + 13, 2), VERSION_CONVERTER)), // Sub-Master BMS RO, Hot Data, 250ms frequency ==> Master Machine Summary Fast // Map 1.2 new FC3ReadRegistersTask(towerOffset, Priority.LOW, // @@ -553,7 +573,7 @@ private synchronized void initializeTowerModulesChannels(int numberOfTowers, int Unit.NONE), new UnsignedWordElement(towerOffset + 25)), // new DummyRegisterElement(towerOffset + 26, towerOffset + 27), // m(generateTowerChannel(this, towerNum, "PRODUCT_SERIAL_NUMBER", OpenemsType.STRING, - Unit.NONE), new StringWordElement(towerOffset + 28, 8), SERIAL_NUMBER_CONVERTER), + Unit.NONE), new StringWordElement(towerOffset + 28, 8), VERSION_CONVERTER), m(generateTowerChannel(this, towerNum, "NOMINAL_CAPACITY", OpenemsType.LONG, Unit.AMPERE_HOURS), new UnsignedWordElement(towerOffset + 36), SCALE_FACTOR_MINUS_1), // @@ -583,17 +603,17 @@ private synchronized void initializeTowerModulesChannels(int numberOfTowers, int new FC3ReadRegistersTask(towerOffset + 48, Priority.LOW, // m(new BitsWordElement(towerOffset + 48, this) // .bit(0, generateTowerChannel(this, towerNum, "STATUS", BOOLEAN, Unit.NONE)) // - .bit(1, generateTowerChannel(this, towerNum, "24V_STATUS", BOOLEAN, Unit.NONE)) // - .bit(2, generateTowerChannel(this, towerNum, "BATTERY_CHARGING_18650_STATUS", + .bit(8, generateTowerChannel(this, towerNum, "24V_STATUS", BOOLEAN, Unit.NONE)) // + .bit(9, generateTowerChannel(this, towerNum, "BATTERY_CHARGING_18650_STATUS", BOOLEAN, Unit.NONE)) // - .bit(3, generateTowerChannel(this, towerNum, "MAIN_SWITCH_STATUS", BOOLEAN, - Unit.NONE)) // - .bit(9, generateTowerChannel(this, towerNum, "NO_24V_POWER_SUPPLY", BOOLEAN, + .bit(10, generateTowerChannel(this, towerNum, "MAIN_SWITCH_STATUS", BOOLEAN, Unit.NONE)) // - .bit(10, generateTowerChannel(this, towerNum, "MAIN_SWITCH_OFF", BOOLEAN, + ), // + m(new BitsWordElement(towerOffset + 49, this) // + .bit(0, generateTowerChannel(this, towerNum, "NO_24V_POWER_SUPPLY", BOOLEAN, Unit.NONE)) // + .bit(1, generateTowerChannel(this, towerNum, "MAIN_SWITCH_OFF", BOOLEAN, Unit.NONE)) // ), // - new DummyRegisterElement(towerOffset + 49), // m(new BitsWordElement(towerOffset + 50, this) // .bit(0, generateTowerChannel(this, towerNum, "E2PROM_FAULT", Level.WARNING)) // .bit(1, generateTowerChannel(this, towerNum, "BATTERY_18650_FAULT", Level.WARNING)) // @@ -633,18 +653,20 @@ private synchronized void initializeTowerModulesChannels(int numberOfTowers, int .bit(4, generateTowerChannel(this, towerNum, "OVER_SOC_LEVEL_2", BOOLEAN, Unit.NONE)) // .bit(5, generateTowerChannel(this, towerNum, "LOW_SOC_LEVEL_2", BOOLEAN, Unit.NONE)) // - .bit(7, generateTowerChannel(this, towerNum, "CELL_OVER_VOLTAGE_LEVEL_3", - Level.WARNING)) // + .bit(7, generateTowerChannel(this, towerNum, "CELL_OVER_VOLTAGE_LEVEL_3", BOOLEAN, + Unit.NONE)) // .bit(8, generateTowerChannel(this, towerNum, "CELL_LOW_VOLTAGE_LEVEL_3", BOOLEAN, Unit.NONE)) // .bit(9, generateTowerChannel(this, towerNum, "CELL_OVER_TEMPERATURE_LEVEL_3", - Level.WARNING)) // + BOOLEAN, Unit.NONE)) // .bit(10, generateTowerChannel(this, towerNum, "CELL_LOW_TEMPERATURE_LEVEL_3", - Level.WARNING)) // - .bit(11, generateTowerChannel(this, towerNum, "OVER_CURRENT_LEVEL_3", - Level.WARNING)) // - .bit(12, generateTowerChannel(this, towerNum, "OVER_SOC_LEVEL_3", Level.WARNING)) // - .bit(13, generateTowerChannel(this, towerNum, "LOW_SOC_LEVEL_3", Level.WARNING))), // + BOOLEAN, Unit.NONE)) // + .bit(11, generateTowerChannel(this, towerNum, "OVER_CURRENT_LEVEL_3", BOOLEAN, + Unit.NONE)) // + .bit(12, generateTowerChannel(this, towerNum, "OVER_SOC_LEVEL_3", BOOLEAN, + Unit.NONE)) // + .bit(13, generateTowerChannel(this, towerNum, "LOW_SOC_LEVEL_3", BOOLEAN, + Unit.NONE))), // m(generateTowerChannel(this, towerNum, "CURRENT_SCALE_5", OpenemsType.LONG, Unit.MICROAMPERE), new UnsignedDoublewordElement(towerOffset + 52), SCALE_FACTOR_1) .wordOrder(LSWMSW), // @@ -889,16 +911,23 @@ private synchronized void initializeTowerModulesChannels(int numberOfTowers, int + "_CELL_007_TEMPERATURE")).setNextValue(cellTemperature7); }), // m(generateTowerChannel(this, towerNum, - getSingleModulePrefix(module) + "_BALANCING_FLAG", BOOLEAN, Unit.NONE), - new UnsignedWordElement(towerOffset + 128 + module * 20 + 16)), // - m(new UnsignedWordElement(towerOffset + 128 + module * 20 - + 17/* - * Start Address is 3200 and the tower offset: 768 and the module offset: 12 - */)).build().onUpdateCallback(new ByteElement(this, // - ByteElement.Shifter.ONLY_FIRST_CHANNEL, // - generateTowerChannel(this, towerNum, - getSingleModulePrefix(module) + "_MODULE_STATUS", - OpenemsType.INTEGER, Unit.NONE))))); + getSingleModulePrefix(module) + "_BALANCING_FLAG", OpenemsType.BOOLEAN, + Unit.NONE), new UnsignedWordElement(towerOffset + 128 + module * 20 + 16)), // + m(new UnsignedWordElement(towerOffset + 128 + module * 20 + 17 + /* + * Start Address is 3200 and the tower offset: 768 and the module offset: 12 + */ + )).build().onUpdateCallback(new ByteElement(this, // + ByteElement.Shifter.ONLY_FIRST_CHANNEL, // + generateTowerChannel(this, towerNum, + getSingleModulePrefix(module) + "_MODULE_STATUS", OpenemsType.INTEGER, + Unit.NONE))), // + m(generateTowerChannel(this, towerNum, getSingleModulePrefix(module) + "_SERIAL_NUMBER", + OpenemsType.INTEGER, Unit.NONE, PersistencePriority.HIGH), + new UnsignedDoublewordElement(towerOffset + 128 + module * 20 + 18) + .wordOrder(WordOrder.LSWMSW), + SERIAL_NUMBER_CONVERTER)// + )); } } } @@ -1005,6 +1034,27 @@ private static io.openems.edge.common.channel.ChannelId generateTowerChannel(Bat return channelId; } + /** + * Generates a tower channel with a specific channelIdSuffix,openemsType and + * channelUnit. + * + * @param parent the parent component + * @param tower number of the Tower + * @param channelIdSuffix e.g. "STATUS_ALARM" + * @param openemsType specified type e.g. "INTEGER" + * @param channelUnit specified type e.g. "NONE" + * @param persistence specified type e.g. "LOW" + * @return a channel with Channel-ID "TOWER_1_STATUS_ALARM" + */ + private static io.openems.edge.common.channel.ChannelId generateTowerChannel(BatteryFeneconCommercialImpl parent, + int tower, String channelIdSuffix, OpenemsType openemsType, Unit channelUnit, + PersistencePriority persistence) { + var channelId = new ChannelIdImpl("TOWER_" + tower + "_" + channelIdSuffix, + Doc.of(openemsType).unit(channelUnit).persistencePriority(persistence)); + parent.addChannel(channelId); + return channelId; + } + /** * Generates a Channel-ID for channels that are specific to a tower. * @@ -1084,7 +1134,7 @@ private void initializeTowerSettings() { /** * Updates the Channel if its current value is not equal to the new value. * - * @param channelId Sinexcel Channel-Id + * @param channelId FeneconCommercialBattery Channel-Id * @param newValue Integer value. * @throws IllegalArgumentException on error */ @@ -1191,8 +1241,8 @@ public void handleEvent(Event event) { } /** - * TODO, to be updated. Update the BATTERY_CHARGE_MAX_CURRENT accordingly, and - * trigger updateSoc onChange. + * Update the BATTERY_CHARGE_MAX_CURRENT accordingly, and trigger updateSoc + * onChange. */ private void setBatteryCurrentLimits() { var chargeMaxCurrent = this.getChargeMaxCurrent(); diff --git a/io.openems.edge.battery.fenecon.commercial/test/io/openems/edge/battery/fenecon/commercial/DynamicChannelsAndSerialNumbersTest.java b/io.openems.edge.battery.fenecon.commercial/test/io/openems/edge/battery/fenecon/commercial/DynamicChannelsAndSerialNumbersTest.java index c46f0e156de..b28b1513542 100644 --- a/io.openems.edge.battery.fenecon.commercial/test/io/openems/edge/battery/fenecon/commercial/DynamicChannelsAndSerialNumbersTest.java +++ b/io.openems.edge.battery.fenecon.commercial/test/io/openems/edge/battery/fenecon/commercial/DynamicChannelsAndSerialNumbersTest.java @@ -59,7 +59,7 @@ public void testSerialNum() throws Exception { .input(SUB_MASTER_HARDWARE_VERSION, "109101BM60")); checkDynamicChannels(battery, TOWERS, MODULES, CELLS / MODULES); - assertEquals("011910MB06", BatteryFeneconCommercialImpl.SERIAL_NUMBER_CONVERTER.elementToChannel("109101BM60")); + assertEquals("011910MB06", BatteryFeneconCommercialImpl.VERSION_CONVERTER.elementToChannel("109101BM60")); componentTest.next(new TestCase()); componentTest.next(new TestCase()); @@ -72,7 +72,7 @@ public void testSerialNum() throws Exception { .input(NUMBER_OF_CELLS_PER_MODULE, CELLS) // .input(MASTER_MCU_HARDWARE_VERSION, "100201MS50")); - assertEquals("012010SM05", BatteryFeneconCommercialImpl.SERIAL_NUMBER_CONVERTER.elementToChannel("100201MS50")); + assertEquals("012010SM05", BatteryFeneconCommercialImpl.VERSION_CONVERTER.elementToChannel("100201MS50")); } /** diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHome.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHome.java index c0d4e0596c1..c7abeac1d3b 100644 --- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHome.java +++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHome.java @@ -2,6 +2,7 @@ import io.openems.common.channel.AccessMode; import io.openems.common.channel.Level; +import io.openems.common.channel.PersistencePriority; import io.openems.common.channel.Unit; import io.openems.common.types.OpenemsType; import io.openems.edge.battery.api.Battery; @@ -45,6 +46,24 @@ public default void _setBmsControl(Boolean value) { this.getBmsControlChannel().setNextValue(value); } + /** + * Gets the Channel for {@link ChannelId#BATTERY_HARDWARE_TYPE}. + * + * @return the Channel + */ + public default Channel getBatteryHardwareTypeChannel() { + return this.channel(BatteryFeneconHome.ChannelId.BATTERY_HARDWARE_TYPE); + } + + /** + * Gets the Hardware Device Type. See {@link ChannelId#BATTERY_HARDWARE_TYPE}. + * + * @return the Channel {@link Value} + */ + public default BatteryFeneconHomeHardwareType getBatteryHardwareType() { + return this.getBatteryHardwareTypeChannel().value().asEnum(); + } + /** * Gets the target Start/Stop mode from config or StartStop-Channel. * @@ -584,12 +603,14 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId NUMBER_OF_MODULES_PER_TOWER(new IntegerDoc() // .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.HIGH) // .text("Number of modules per tower") // .onChannelChange(BatteryFeneconHomeImpl::updateNumberOfTowersAndModules)), NUMBER_OF_TOWERS(Doc.of(OpenemsType.INTEGER) // .unit(Unit.NONE) // .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.HIGH) // .text("Number of towers of the built system")), TOWER_2_BMS_SOFTWARE_VERSION(new IntegerDoc() // @@ -609,6 +630,9 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId .accessMode(AccessMode.READ_ONLY) // .text("Bms software version of first tower")), + BATTERY_HARDWARE_TYPE(Doc.of(BatteryFeneconHomeHardwareType.values()) // + .onChannelChange(BatteryFeneconHomeImpl::updateNumberOfTowersAndModules)), + BMS_CONTROL(Doc.of(OpenemsType.BOOLEAN) // .text("BMS CONTROL(1: Shutdown, 0: no action)")), STATE_MACHINE(Doc.of(State.values()) // diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeHardwareType.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeHardwareType.java new file mode 100644 index 00000000000..ccb3f0380c7 --- /dev/null +++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeHardwareType.java @@ -0,0 +1,54 @@ +package io.openems.edge.battery.fenecon.home; + +import io.openems.common.types.OptionsEnum; +import io.openems.edge.battery.protection.BatteryProtectionDefinition; + +public enum BatteryFeneconHomeHardwareType implements OptionsEnum { + + BATTERY_52(52, "Fenecon Home Battery 52Ah", 2200, 42, 49, 14, 3, new FeneconHomeBatteryProtection52()), // + BATTERY_64(64, "Fenecon Home Battery 64,4Ah", 2650, 40.6f, 49.7f, 14, 5, new FeneconHomeBatteryProtection64()); // + + /** + * Defaults to {@link #BATTERY_52} to avoid detection failure with old firmware + * versions. + */ + public static final BatteryFeneconHomeHardwareType DEFAULT = BATTERY_52; + + public final int capacityPerModule; // [Wh] + public final float moduleMinVoltage; // [V]; e.g. 3.0 V x 14 Cells per Module + public final float moduleMaxVoltage; // [V]; e.g. 3.5 V x 14 Cells per Module + public final int cellsPerModule; + public final int tempSensorsPerModule; + public final BatteryProtectionDefinition batteryProtection; + + private final int value; + private final String type; + + private BatteryFeneconHomeHardwareType(int value, String type, int capacityPerModule, float moduleMinVoltage, + float moduleMaxVoltage, int cellsPerModule, int tempSensorsPerModule, + BatteryProtectionDefinition batteryProtection) { + this.value = value; + this.type = type; + this.capacityPerModule = capacityPerModule; + this.moduleMinVoltage = moduleMinVoltage; + this.moduleMaxVoltage = moduleMaxVoltage; + this.cellsPerModule = cellsPerModule; + this.tempSensorsPerModule = tempSensorsPerModule; + this.batteryProtection = batteryProtection; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.type; + } + + @Override + public OptionsEnum getUndefined() { + return DEFAULT; + } +} \ No newline at end of file diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImpl.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImpl.java index 07290107234..3f05d82cd60 100644 --- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImpl.java +++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImpl.java @@ -4,6 +4,7 @@ import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; @@ -24,11 +25,13 @@ import io.openems.common.channel.AccessMode; import io.openems.common.channel.Level; +import io.openems.common.channel.PersistencePriority; import io.openems.common.channel.Unit; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; import io.openems.common.types.ChannelAddress; import io.openems.common.types.OpenemsType; +import io.openems.common.types.OptionsEnum; import io.openems.edge.battery.api.Battery; import io.openems.edge.battery.fenecon.home.statemachine.Context; import io.openems.edge.battery.fenecon.home.statemachine.StateMachine; @@ -39,6 +42,7 @@ import io.openems.edge.bridge.modbus.api.ElementToChannelConverter; import io.openems.edge.bridge.modbus.api.ModbusComponent; import io.openems.edge.bridge.modbus.api.ModbusProtocol; +import io.openems.edge.bridge.modbus.api.ModbusUtils; import io.openems.edge.bridge.modbus.api.element.BitsWordElement; import io.openems.edge.bridge.modbus.api.element.DummyRegisterElement; import io.openems.edge.bridge.modbus.api.element.ModbusElement; @@ -50,6 +54,7 @@ import io.openems.edge.common.channel.Channel; import io.openems.edge.common.channel.ChannelId.ChannelIdImpl; import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.channel.internal.OpenemsTypeDoc; import io.openems.edge.common.component.ComponentManager; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; @@ -73,10 +78,6 @@ public class BatteryFeneconHomeImpl extends AbstractOpenemsModbusComponent implements ModbusComponent, OpenemsComponent, Battery, EventHandler, ModbusSlave, StartStoppable, BatteryFeneconHome { - private static final int SENSORS_PER_MODULE = 14; - private static final int MODULE_MIN_VOLTAGE = 42; // [V] - private static final int MODULE_MAX_VOLTAGE = 49; // [V]; 3.5 V x 14 Cells per Module - private static final int CAPACITY_PER_MODULE = 2200; // [Wh] private static final String SERIAL_NUMBER_PREFIX_BMS = "519100001009"; private static final String SERIAL_NUMBER_PREFIX_MODULE = "519110001210"; @@ -97,7 +98,7 @@ protected void setModbus(BridgeModbus modbus) { } private Config config; - private BatteryProtection batteryProtection = null; + private BatteryProtection batteryProtection; // set in constructor public BatteryFeneconHomeImpl() { super(// @@ -108,21 +109,24 @@ public BatteryFeneconHomeImpl() { BatteryProtection.ChannelId.values(), // BatteryFeneconHome.ChannelId.values() // ); + this.updateHardwareType(BatteryFeneconHomeHardwareType.DEFAULT); } @Activate private void activate(ComponentContext context, Config config) throws OpenemsException { this.config = config; + // Predefine BatteryProtection. Later adapted to the hardware type. + this.batteryProtection = BatteryProtection.create(this) // + .applyBatteryProtectionDefinition(new FeneconHomeBatteryProtection52(), this.componentManager) // + .build(); + if (super.activate(context, config.id(), config.alias(), config.enabled(), config.modbusUnitId(), this.cm, "Modbus", config.modbus_id())) { return; } - // Initialize Battery-Protection - this.batteryProtection = BatteryProtection.create(this) // - .applyBatteryProtectionDefinition(new FeneconHomeBatteryProtection(), this.componentManager) // - .build(); + this.detectHardwareType(); } @Override @@ -133,7 +137,6 @@ protected void deactivate() { @Override public void handleEvent(Event event) { - if (!this.isEnabled()) { return; } @@ -318,7 +321,10 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException { m(BatteryFeneconHome.ChannelId.TOWER_1_BMS_SOFTWARE_VERSION, new UnsignedWordElement(12000))), // new FC3ReadRegistersTask(10000, Priority.LOW, // m(BatteryFeneconHome.ChannelId.TOWER_0_BMS_SOFTWARE_VERSION, new UnsignedWordElement(10000)), // - new DummyRegisterElement(10001, 10023), // + new DummyRegisterElement(10001, 10018), // + m(BatteryFeneconHome.ChannelId.BATTERY_HARDWARE_TYPE, new UnsignedWordElement(10019), + SCALE_FACTOR_MINUS_1), // + new DummyRegisterElement(10020, 10023), // m(BatteryFeneconHome.ChannelId.NUMBER_OF_MODULES_PER_TOWER, new UnsignedWordElement(10024))), // new FC3ReadRegistersTask(44000, Priority.HIGH, // m(new BitsWordElement(44000, this) // @@ -327,34 +333,75 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException { } /** - * Generates prefix for Channel-IDs for Cell Temperature and Voltage channels. - * - *

    - * "%03d" creates string number with leading zeros - * - * @param num number of the Cell - * @param module number of the Module - * @param tower number of the Tower - * @return a prefix e.g. "TOWER_1_MODULE_2_CELL_003" + * Detects the Hardware Type and updates the HardwareType Channel. + * + * @throws OpenemsException on error */ - private static String getSingleCellPrefix(int tower, int module, int num) { - return "TOWER_" + tower + "_MODULE_" + module + "_CELL_" + String.format("%03d", num); + private void detectHardwareType() throws OpenemsException { + // Set Battery-Protection + ModbusUtils.readELementOnce(this.getModbusProtocol(), new UnsignedWordElement(10019), true) // + .thenAccept(value -> { + if (value == null) { + return; + } + + var hardwareType = parseHardwareTypeFromRegisterValue(value); + if (hardwareType == null) { + this.logWarn(this.log, "Unable to Identify Hardware Type from Register value [" + value + "]"); + hardwareType = BatteryFeneconHomeHardwareType.DEFAULT; + } + this.updateHardwareType(hardwareType); + }); + } + + /** + * Get GoodWe hardware version from register value. + * + * @param value Register value not formated with SCALE_FACTOR_MINUS_1 + * @return type as {@link GoodweHardwareType} or null + */ + public static BatteryFeneconHomeHardwareType parseHardwareTypeFromRegisterValue(int value) { + return OptionsEnum.getOption(BatteryFeneconHomeHardwareType.class, value / 10); + } + + /** + * Sets the BatteryHardwareTypeChannel and updates the BatteryProtection. + * + * @param hardwareType the {@link BatteryFeneconHomeHardwareType} + */ + private void updateHardwareType(BatteryFeneconHomeHardwareType hardwareType) { + this.getBatteryHardwareTypeChannel().setNextValue(hardwareType); + + // Set Battery Protection depending on the hardware type + this.batteryProtection = BatteryProtection.create(this) // + .applyBatteryProtectionDefinition(hardwareType.batteryProtection, this.componentManager) // + .build(); } /** * Generates a Channel-ID for channels that are specific to a tower. * - * @param tower number of the Tower - * @param channelIdSuffix e.g. "STATUS_ALARM" - * @param openemsType specified type e.g. "INTEGER" + * @param tower number of the Tower + * @param channelIdSuffix e.g. "STATUS_ALARM" + * @param openemsType specified type e.g. "INTEGER" + * @param additionalDocConfig the additional doc configuration * @return a channel with Channel-ID "TOWER_1_STATUS_ALARM" */ - private ChannelIdImpl generateTowerChannel(int tower, String channelIdSuffix, OpenemsType openemsType) { - var channelId = new ChannelIdImpl("TOWER_" + tower + "_" + channelIdSuffix, Doc.of(openemsType)); + private ChannelIdImpl generateTowerChannel(int tower, String channelIdSuffix, OpenemsType openemsType, + Consumer> additionalDocConfig) { + final var doc = Doc.of(openemsType); + if (additionalDocConfig != null) { + additionalDocConfig.accept(doc); + } + var channelId = new ChannelIdImpl("TOWER_" + tower + "_" + channelIdSuffix, doc); this.addChannel(channelId); return channelId; } + private ChannelIdImpl generateTowerChannel(int tower, String channelIdSuffix, OpenemsType openemsType) { + return this.generateTowerChannel(tower, channelIdSuffix, openemsType, null); + } + /** * Generates a Channel-ID for channels that are specific to a tower. * @@ -443,7 +490,6 @@ protected synchronized void updateNumberOfTowersAndModules() { || !tower2BmsSoftwareVersion.isDefined()) { return; } - int numberOfModulesPerTower = numberOfModulesPerTowerOpt.get(); // Evaluate the total number of towers by reading the software versions of // towers 2 and 3: they are '0' when the respective tower is not available. @@ -460,10 +506,15 @@ protected synchronized void updateNumberOfTowersAndModules() { Channel numberOfTowersChannel = this.channel(BatteryFeneconHome.ChannelId.NUMBER_OF_TOWERS); numberOfTowersChannel.setNextValue(numberOfTowers); + var moduleMaxVoltage = this.getBatteryHardwareType().moduleMaxVoltage; + var moduleMinVoltage = this.getBatteryHardwareType().moduleMinVoltage; + var capacityPerModule = this.getBatteryHardwareType().capacityPerModule; + int numberOfModulesPerTower = numberOfModulesPerTowerOpt.get(); + // Set Battery Channels - this._setChargeMaxVoltage(numberOfModulesPerTower * MODULE_MAX_VOLTAGE); - this._setDischargeMinVoltage(numberOfModulesPerTower * MODULE_MIN_VOLTAGE); - this._setCapacity(numberOfTowers * numberOfModulesPerTower * CAPACITY_PER_MODULE); + this._setChargeMaxVoltage(Math.round(numberOfModulesPerTower * moduleMaxVoltage)); + this._setDischargeMinVoltage(Math.round(numberOfModulesPerTower * moduleMinVoltage)); + this._setCapacity(numberOfTowers * numberOfModulesPerTower * capacityPerModule); // Initialize available Tower- and Module-Channels dynamically. try { @@ -711,7 +762,8 @@ private synchronized void initializeTowerModulesChannels(int numberOfTowers, int new UnsignedDoublewordElement(towerOffset + 47)), m(this.generateTowerChannel(tower, "ACC_DISCHARGE_ENERGY", OpenemsType.INTEGER), new UnsignedDoublewordElement(towerOffset + 49)), - m(this.generateTowerChannel(tower, "BMS_SERIAL_NUMBER", OpenemsType.STRING), + m(this.generateTowerChannel(tower, "BMS_SERIAL_NUMBER", OpenemsType.STRING, + doc -> doc.persistencePriority(PersistencePriority.HIGH)), new UnsignedDoublewordElement(towerOffset + 51), new ElementToChannelConverter(value -> { Integer intValue = TypeUtils.getAsType(OpenemsType.INTEGER, value); @@ -726,6 +778,9 @@ private synchronized void initializeTowerModulesChannels(int numberOfTowers, int moduleToUse = 0; } + final var cellsPerModule = this.getBatteryHardwareType().cellsPerModule; + final var tempSensorsPerModule = this.getBatteryHardwareType().tempSensorsPerModule; + for (var tower = towerToUse; tower < numberOfTowers; tower++) { final var towerOffset = tower * 2000 + 10000; final var moduleOffset = towerOffset + 100; @@ -734,50 +789,72 @@ private synchronized void initializeTowerModulesChannels(int numberOfTowers, int /* * Number Of Modules per Tower increased. * - * Dynamically generate Channels and Modbus mappings for Cell-Temperatures and - * for Cell-Voltages.Channel-IDs are like "TOWER_0_OFFSET_2_TEMPERATURE_003". - * Channel-IDs are like "TOWER_0_OFFSET_2_VOLTAGE_003". + * Dynamically generate Channels and Modbus mappings for Cell-Voltages. + * Channel-IDs are like "TOWER_0_MODULE_2_CELL_001_VOLTAGE". */ - var ameVolt = new ModbusElement[SENSORS_PER_MODULE]; - var ameTemp = new ModbusElement[SENSORS_PER_MODULE]; - for (var j = 0; j < SENSORS_PER_MODULE; j++) { - { - // Create Voltage Channel - var channelId = new ChannelIdImpl(// - getSingleCellPrefix(tower, module, j) + "_VOLTAGE", - Doc.of(OpenemsType.INTEGER).unit(Unit.VOLT)); - this.addChannel(channelId); - - // Create Modbus-Mapping for Voltages - var uwe = new UnsignedWordElement(moduleOffset + module * 100 + 2 + j); - ameVolt[j] = m(channelId, uwe); - } - { - // TODO only 8 temperatures - - // Create Temperature Channel - var channelId = new ChannelIdImpl(// - getSingleCellPrefix(tower, module, j) + "_TEMPERATURE", - Doc.of(OpenemsType.INTEGER).unit(Unit.DEZIDEGREE_CELSIUS)); - this.addChannel(channelId); - - // Create Modbus-Mapping for Temperatures - // Cell Temperatures Read Registers for Tower_1 starts from 10000, for Tower_2 - // 12000, for Tower_3 14000 - // (t-1)*2000+10000) calculates Tower Offset value - var uwe = new SignedWordElement(moduleOffset + module * 100 + 18 + j); - ameTemp[j] = m(channelId, uwe); - } + var ameVolt = new ModbusElement[cellsPerModule]; + for (var cell = 0; cell < cellsPerModule; cell++) { + + // Create Voltage Channel + var channelId = new ChannelIdImpl(// + generateSingleCellPrefix(tower, module, cell) + "_VOLTAGE", + Doc.of(OpenemsType.INTEGER).unit(Unit.VOLT)); + this.addChannel(channelId); + + // Create Modbus-Mapping for Voltages + var uwe = new UnsignedWordElement(moduleOffset + module * 100 + 2 + cell); + ameVolt[cell] = m(channelId, uwe); + } + + /* + * Dynamically generate Channels and Modbus mappings for temperature sensors. + * Channel-IDs are like "TOWER_0_MODULE_2_TEMPERATURE_SENSOR_1". + */ + var ameTemp = new ModbusElement[tempSensorsPerModule]; + for (var sensor = 0; sensor < tempSensorsPerModule; sensor++) { + + // Create Temperature Channel + var channelId = new ChannelIdImpl(// + generateTempSensorChannelName(tower, module, sensor + 1), + Doc.of(OpenemsType.INTEGER).unit(Unit.DEZIDEGREE_CELSIUS)); + this.addChannel(channelId); + + // Create Modbus-Mapping for Temperatures + // Cell Temperatures Read Registers for Tower_1 starts from 10000, for Tower_2 + // 12000, for Tower_3 14000 + // (t-1)*2000+10000) calculates Tower Offset value + var uwe = new SignedWordElement(moduleOffset + module * 100 + 18 + sensor); + ameTemp[sensor] = m(channelId, uwe); + } + + /* + * Temperature balancing sensors + */ + final var defaultBalancingTemperatures = 2; + var ameTempBalancing = new ModbusElement[defaultBalancingTemperatures]; + for (var j = 0; j < defaultBalancingTemperatures; j++) { + + // Create Temperature Channel + var channelId = new ChannelIdImpl(// + generateTempBalancingChannelName(tower, module, j + 1), + Doc.of(OpenemsType.INTEGER).unit(Unit.DEZIDEGREE_CELSIUS)); + this.addChannel(channelId); + + var uwe = new SignedWordElement(moduleOffset + module * 100 + 18 + tempSensorsPerModule + j); + ameTempBalancing[j] = m(channelId, uwe); } var channelId = new ChannelIdImpl(// "TOWER_" + tower + "_MODULE_" + module + "_SERIAL_NUMBER", // - Doc.of(OpenemsType.STRING)); + Doc.of(OpenemsType.STRING)// + .persistencePriority(PersistencePriority.HIGH)); this.addChannel(channelId); this.getModbusProtocol().addTasks(// new FC3ReadRegistersTask(moduleOffset + module * 100 + 2, Priority.LOW, ameVolt), new FC3ReadRegistersTask(moduleOffset + module * 100 + 18, Priority.LOW, ameTemp), + new FC3ReadRegistersTask(moduleOffset + module * 100 + 18 + tempSensorsPerModule, + Priority.LOW, ameTempBalancing), new FC3ReadRegistersTask(moduleOffset + module * 100 + 83, Priority.LOW, m(channelId, new UnsignedDoublewordElement(moduleOffset + module * 100 + 83), new ElementToChannelConverter(value -> { @@ -840,4 +917,69 @@ protected static String buildSerialNumber(String prefix, Integer value) { private static int extractNumber(int value, int length, int position) { return (1 << length) - 1 & value >> position - 1; } + + /** + * Generates prefix for Channel-IDs for Cell Temperature and Voltage channels. + * + * @param tower number to use + * @param module number to use + * @return a prefix e.g. "TOWER_1_MODULE_2" + */ + private static String generateModulePrefix(int tower, int module) { + return "TOWER_" + tower + "_MODULE_" + module; + } + + /** + * Generates Channel names for Cell Voltage Channel-IDs. + * + *

    + * "%03d" creates string number with leading zeros + * + * @param tower number to use + * @param module number to use + * @param cell number to user + * @return a Channel name e.g. "TOWER_1_MODULE_2_CELL_003_VOLTAGE" + */ + public static String generateCellVoltageChannelName(int tower, int module, int cell) { + return generateModulePrefix(tower, module) + "_CELL_" + String.format("%03d", cell) + "_VOLTAGE"; + } + + /** + * Generates Channel names for Temperature Sensor Channel-IDs. + * + * @param tower number to use + * @param module number to use + * @param sensor number to user + * @return a Channel name e.g. "TOWER_1_MODULE_2_TEMPERATURE_SENSOR_2" + */ + public static String generateTempSensorChannelName(int tower, int module, int sensor) { + return generateModulePrefix(tower, module) + "_TEMPERATURE_SENSOR_" + sensor; + } + + /** + * Generates Channel names for Temperature Balancing Channel-IDs. + * + * @param tower number to use + * @param module number to use + * @param value number to user + * @return a Channel name e.g. "TOWER_1_MODULE_2_TEMPERATURE_BALANCING_1" + */ + public static String generateTempBalancingChannelName(int tower, int module, int value) { + return generateModulePrefix(tower, module) + "_TEMPERATURE_BALANCING_" + value; + } + + /** + * Generates prefix for Channel-IDs for Cell Temperature and Voltage channels. + * + *

    + * "%03d" creates string number with leading zeros + * + * @param num number of the Cell + * @param module number of the Module + * @param tower number of the Tower + * @return a prefix e.g. "TOWER_1_MODULE_2_CELL_003" + */ + private static String generateSingleCellPrefix(int tower, int module, int num) { + return "TOWER_" + tower + "_MODULE_" + module + "_CELL_" + String.format("%03d", num); + } } diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/FeneconHomeBatteryProtection.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/FeneconHomeBatteryProtection52.java similarity index 93% rename from io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/FeneconHomeBatteryProtection.java rename to io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/FeneconHomeBatteryProtection52.java index dfd5f9a0224..23af1a9c6f5 100644 --- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/FeneconHomeBatteryProtection.java +++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/FeneconHomeBatteryProtection52.java @@ -5,7 +5,7 @@ import io.openems.edge.battery.protection.force.ForceDischarge; import io.openems.edge.common.linecharacteristic.PolyLine; -public class FeneconHomeBatteryProtection implements BatteryProtectionDefinition { +public class FeneconHomeBatteryProtection52 implements BatteryProtectionDefinition { @Override public int getInitialBmsMaxEverChargeCurrent() { diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/FeneconHomeBatteryProtection64.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/FeneconHomeBatteryProtection64.java new file mode 100644 index 00000000000..9fc51ca686b --- /dev/null +++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/FeneconHomeBatteryProtection64.java @@ -0,0 +1,68 @@ +package io.openems.edge.battery.fenecon.home; + +import io.openems.edge.battery.protection.BatteryProtectionDefinition; +import io.openems.edge.battery.protection.force.ForceCharge; +import io.openems.edge.battery.protection.force.ForceDischarge; +import io.openems.edge.common.linecharacteristic.PolyLine; + +public class FeneconHomeBatteryProtection64 implements BatteryProtectionDefinition { + + @Override + public int getInitialBmsMaxEverChargeCurrent() { + return 50; // [A] + } + + @Override + public int getInitialBmsMaxEverDischargeCurrent() { + return 50; // [A] + } + + @Override + public PolyLine getChargeVoltageToPercent() { + return PolyLine.create() // + .addPoint(2000, 0.1) // + .addPoint(3000, 0.1) // + .addPoint(Math.nextUp(3000), 1) // + .addPoint(3450, 1) // + .addPoint(3540, 0.08) // + .addPoint(Math.nextDown(3550), 0.08) // + .addPoint(3550, 0) // + .build(); + } + + @Override + public PolyLine getDischargeVoltageToPercent() { + return PolyLine.create() // + .addPoint(2000, 0) // + .addPoint(2900, 0) // + .addPoint(Math.nextUp(2900), 0.1) // + .addPoint(3100, 1) // + .build(); + } + + @Override + public PolyLine getChargeTemperatureToPercent() { + return PolyLine.empty(); + } + + @Override + public PolyLine getDischargeTemperatureToPercent() { + return PolyLine.empty(); + } + + @Override + public ForceDischarge.Params getForceDischargeParams() { + return new ForceDischarge.Params(3600, 3540, 3450); + } + + @Override + public ForceCharge.Params getForceChargeParams() { + return new ForceCharge.Params(2850, 2950, 3100); + } + + @Override + public Double getMaxIncreaseAmperePerSecond() { + return 0.1; // [A] per second + } + +} diff --git a/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImplTest.java b/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImplTest.java index 534f737bcf9..b3412a69147 100644 --- a/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImplTest.java +++ b/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImplTest.java @@ -1,5 +1,8 @@ package io.openems.edge.battery.fenecon.home; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + import java.time.Instant; import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; @@ -150,7 +153,7 @@ public void test2() throws Exception { } /** - * Battery start up when the relay is off and battery has already started, FEMS + * Battery start up when the relay is off and battery has already started, OpenEMS Edge * restarted. * * @throws Exception on error @@ -324,4 +327,21 @@ public void test6() throws Exception { .output(STATE_MACHINE, StateMachine.State.RUNNING)); } + @Test + public void testGetHardwareTypeFromRegisterValue() { + assertNull(BatteryFeneconHomeImpl.parseHardwareTypeFromRegisterValue(0)); + + assertNull(BatteryFeneconHomeImpl.parseHardwareTypeFromRegisterValue(123)); + + assertNull(BatteryFeneconHomeImpl.parseHardwareTypeFromRegisterValue(52)); + + assertNull(BatteryFeneconHomeImpl.parseHardwareTypeFromRegisterValue(64)); + + assertEquals(BatteryFeneconHomeHardwareType.BATTERY_52, + BatteryFeneconHomeImpl.parseHardwareTypeFromRegisterValue(520)); + + assertEquals(BatteryFeneconHomeHardwareType.BATTERY_64, + BatteryFeneconHomeImpl.parseHardwareTypeFromRegisterValue(640)); + + } } diff --git a/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/TowersAndModulesTest.java b/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/TowersAndModulesTest.java index 5aa82c8f4df..ff8b7353731 100644 --- a/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/TowersAndModulesTest.java +++ b/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/TowersAndModulesTest.java @@ -8,6 +8,7 @@ import io.openems.common.types.ChannelAddress; import io.openems.edge.battery.api.Battery; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; +import io.openems.edge.common.channel.ChannelId; import io.openems.edge.common.startstop.StartStopConfig; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; @@ -27,6 +28,7 @@ public class TowersAndModulesTest { "Tower1BmsSoftwareVersion"); private static final ChannelAddress TOWER_2_BMS_SOFTWARE_VERSION = new ChannelAddress(BATTERY_ID, "Tower2BmsSoftwareVersion"); + private static final ChannelAddress BATTERY_HARDWARE_TYPE = new ChannelAddress(BATTERY_ID, "BatteryHardwareType"); private static final int TOWERS = 1; private static final int MODULES = 5; @@ -52,18 +54,19 @@ public void testChannelsCreatedDynamically() throws Exception { .input(NUMBER_OF_MODULES_PER_TOWER, MODULES) // .input(TOWER_0_BMS_SOFTWARE_VERSION, 1) // .input(TOWER_1_BMS_SOFTWARE_VERSION, 0) // - .input(TOWER_2_BMS_SOFTWARE_VERSION, 0)); - checkDynamicChannels(battery, TOWERS, MODULES, CELLS); + .input(TOWER_2_BMS_SOFTWARE_VERSION, 0) // + .input(BATTERY_HARDWARE_TYPE, BatteryFeneconHomeHardwareType.BATTERY_52)); + checkDynamicChannels(battery, TOWERS, MODULES, CELLS, BatteryFeneconHomeHardwareType.BATTERY_52); // add new module (1 tower, each tower 6 modules) componentTest.next(new TestCase() // .input(NUMBER_OF_MODULES_PER_TOWER, MODULES + 1)); - checkDynamicChannels(battery, TOWERS, MODULES + 1, CELLS); + checkDynamicChannels(battery, TOWERS, MODULES + 1, CELLS, BatteryFeneconHomeHardwareType.BATTERY_52); // add new tower home (2 tower, each tower 6 modules) componentTest.next(new TestCase() // .input(TOWER_1_BMS_SOFTWARE_VERSION, 1)); - checkDynamicChannels(battery, TOWERS + 1, MODULES + 1, CELLS); + checkDynamicChannels(battery, TOWERS + 1, MODULES + 1, CELLS, BatteryFeneconHomeHardwareType.BATTERY_52); } @Test @@ -88,12 +91,14 @@ public void testSerialNumberFormatterForBattery() { * parameters are created. If channel not exists an exception will be thrown and * the test fails. * - * @param battery the {@link Battery} - * @param towers number of given towers - * @param modules number of given modules - * @param cells number of given cells + * @param battery the {@link Battery} + * @param towers number of given towers + * @param modules number of given modules + * @param cells number of given cells + * @param hardwareType hardware type */ - private static void checkDynamicChannels(Battery battery, int towers, int modules, int cells) { + private static void checkDynamicChannels(Battery battery, int towers, int modules, int cells, + BatteryFeneconHomeHardwareType hardwareType) { for (var tower = 0; tower < towers; tower++) { // check for each tower the serial number channel is existent battery.channel("Tower" + tower + "BmsSerialNumber"); @@ -102,26 +107,22 @@ private static void checkDynamicChannels(Battery battery, int towers, int module // check for each tower and module the serial number channel is existent battery.channel("Tower" + tower + "Module" + module + "SerialNumber"); - for (var cell = 0; cell < cells; cell++) { - // check for each tower, module and cell voltage and temperature channel are - // existent - battery.channel(getCellChannelName(tower, module, cell) + "Voltage"); - battery.channel(getCellChannelName(tower, module, cell) + "Temperature"); + // check for each tower, module and cell voltage + for (var cell = 0; cell < hardwareType.cellsPerModule; cell++) { + battery.channel(ChannelId.channelIdUpperToCamel( + BatteryFeneconHomeImpl.generateCellVoltageChannelName(tower, module, cell))); + } + // check for each tower, module and temperature sensor + for (var sensor = 0; sensor < hardwareType.tempSensorsPerModule; sensor++) { + battery.channel(ChannelId.channelIdUpperToCamel( + BatteryFeneconHomeImpl.generateTempSensorChannelName(tower, module, sensor + 1))); + } + // check for each tower, module and temperature balancing + for (var balancing = 0; balancing < 2; balancing++) { + battery.channel(ChannelId.channelIdUpperToCamel( + BatteryFeneconHomeImpl.generateTempBalancingChannelName(tower, module, balancing + 1))); } } } } - - /** - * Builds the cell channel name e.g. Tower0Module3Cell004. - * - * @param tower number to use - * @param module number to use - * @param cell number to user - * @return The cell channel name - */ - private static String getCellChannelName(int tower, int module, int cell) { - return "Tower" + tower + "Module" + module + "Cell" + String.format("%03d", cell); - } - } diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionC.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionC.java index 3a10f308e09..99f8fea4c9f 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionC.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionC.java @@ -7,6 +7,7 @@ import io.openems.common.channel.AccessMode; import io.openems.common.channel.Level; +import io.openems.common.channel.PersistencePriority; import io.openems.common.channel.Unit; import io.openems.common.types.OpenemsType; import io.openems.edge.battery.api.Battery; @@ -342,8 +343,10 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId MAX_STOP_ATTEMPTS(Doc.of(Level.FAULT) // .text("The maximum number of stop attempts failed")), // NUMBER_OF_MODULES_PER_TOWER(Doc.of(OpenemsType.INTEGER) // + .persistencePriority(PersistencePriority.HIGH) // .text("Number Modules per Tower")), // NUMBER_OF_TOWERS(Doc.of(OpenemsType.INTEGER) // + .persistencePriority(PersistencePriority.HIGH) // .text("Number of Towers")), // ; diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImpl.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImpl.java index 2e2fd7956cb..dc75bf1125f 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImpl.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImpl.java @@ -314,10 +314,8 @@ private void updateRackChannels(Integer numberOfModules, TreeSet racks) th ), // Level 1 Alarm: EMS Control to stop charge, discharge, charge&discharge m(new BitsWordElement(r.offset + 0x141, this) // - .bit(0, this.createChannelId(r, RackChannel.LEVEL1_CELL_VOLTAGE_HIGH)) // .bit(1, this.createChannelId(r, RackChannel.LEVEL1_TOTAL_VOLTAGE_HIGH)) // .bit(2, this.createChannelId(r, RackChannel.LEVEL1_CHARGE_CURRENT_HIGH)) // - .bit(3, this.createChannelId(r, RackChannel.LEVEL1_CELL_VOLTAGE_LOW)) // .bit(4, this.createChannelId(r, RackChannel.LEVEL1_TOTAL_VOLTAGE_LOW)) // .bit(5, this.createChannelId(r, RackChannel.LEVEL1_DISCHARGE_CURRENT_HIGH)) // .bit(6, this.createChannelId(r, RackChannel.LEVEL1_CHARGE_TEMP_HIGH)) // @@ -333,16 +331,11 @@ private void updateRackChannels(Integer numberOfModules, TreeSet racks) th ), // Pre-Alarm: Temperature Alarm will active current limication m(new BitsWordElement(r.offset + 0x142, this) // - .bit(0, this.createChannelId(r, RackChannel.PRE_ALARM_CELL_VOLTAGE_HIGH)) // - .bit(1, this.createChannelId(r, RackChannel.PRE_ALARM_TOTAL_VOLTAGE_HIGH)) // .bit(2, this.createChannelId(r, RackChannel.PRE_ALARM_CHARGE_CURRENT_HIGH)) // - .bit(3, this.createChannelId(r, RackChannel.PRE_ALARM_CELL_VOLTAGE_LOW)) // .bit(4, this.createChannelId(r, RackChannel.PRE_ALARM_TOTAL_VOLTAGE_LOW)) // .bit(5, this.createChannelId(r, RackChannel.PRE_ALARM_DISCHARGE_CURRENT_HIGH)) // .bit(6, this.createChannelId(r, RackChannel.PRE_ALARM_CHARGE_TEMP_HIGH)) // .bit(7, this.createChannelId(r, RackChannel.PRE_ALARM_CHARGE_TEMP_LOW)) // - .bit(8, this.createChannelId(r, RackChannel.PRE_ALARM_SOC_LOW)) // - .bit(9, this.createChannelId(r, RackChannel.PRE_ALARM_TEMP_DIFF_TOO_BIG)) // .bit(10, this.createChannelId(r, RackChannel.PRE_ALARM_POWER_POLE_HIGH))// .bit(11, this.createChannelId(r, RackChannel.PRE_ALARM_CELL_VOLTAGE_DIFF_TOO_BIG)) // @@ -906,16 +899,12 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException { // Pre-Alarm Summary: Temperature Alarm can be used for current limitation, // while all other alarms are just for alarm. Note: Alarm for all clusters m(new BitsWordElement(0x1093, this) // - .bit(0, BatterySoltaroClusterVersionC.ChannelId.PRE_ALARM_CELL_VOLTAGE_HIGH) // .bit(1, BatterySoltaroClusterVersionC.ChannelId.PRE_ALARM_TOTAL_VOLTAGE_HIGH) // .bit(2, BatterySoltaroClusterVersionC.ChannelId.PRE_ALARM_CHARGE_CURRENT_HIGH) // - .bit(3, BatterySoltaroClusterVersionC.ChannelId.PRE_ALARM_CELL_VOLTAGE_LOW) // .bit(4, BatterySoltaroClusterVersionC.ChannelId.PRE_ALARM_TOTAL_VOLTAGE_LOW) // .bit(5, BatterySoltaroClusterVersionC.ChannelId.PRE_ALARM_DISCHARGE_CURRENT_HIGH) // .bit(6, BatterySoltaroClusterVersionC.ChannelId.PRE_ALARM_CHARGE_TEMP_HIGH) // .bit(7, BatterySoltaroClusterVersionC.ChannelId.PRE_ALARM_CHARGE_TEMP_LOW) // - .bit(8, BatterySoltaroClusterVersionC.ChannelId.PRE_ALARM_SOC_LOW) // - .bit(9, BatterySoltaroClusterVersionC.ChannelId.PRE_ALARM_TEMP_DIFF_TOO_BIG) // .bit(10, BatterySoltaroClusterVersionC.ChannelId.PRE_ALARM_POWER_POLE_HIGH) // .bit(11, BatterySoltaroClusterVersionC.ChannelId.PRE_ALARM_CELL_VOLTAGE_DIFF_TOO_BIG) // .bit(12, BatterySoltaroClusterVersionC.ChannelId.PRE_ALARM_INSULATION_FAIL) // @@ -924,7 +913,6 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException { .bit(15, BatterySoltaroClusterVersionC.ChannelId.PRE_ALARM_DISCHARGE_TEMP_LOW)), // // Level 1 Alarm Summary m(new BitsWordElement(0x1094, this) // - .bit(0, BatterySoltaroClusterVersionC.ChannelId.LEVEL1_CELL_VOLTAGE_HIGH) // .bit(1, BatterySoltaroClusterVersionC.ChannelId.LEVEL1_TOTAL_VOLTAGE_HIGH) // .bit(2, BatterySoltaroClusterVersionC.ChannelId.LEVEL1_CHARGE_CURRENT_HIGH) // .bit(3, BatterySoltaroClusterVersionC.ChannelId.LEVEL1_CELL_VOLTAGE_LOW) // diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/RackChannel.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/RackChannel.java index beea695bfd4..ec13258ac7c 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/RackChannel.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/RackChannel.java @@ -411,14 +411,8 @@ public enum RackChannel { ALARM_SLAVE_BMS_HARDWARE(Doc.of(Level.WARNING) // .text("Slave BMS Hardware Failure")), // // Pre-Alarm - PRE_ALARM_CELL_VOLTAGE_HIGH(Doc.of(OpenemsType.BOOLEAN) // - .text("Cell Voltage High Pre-Alarm")), // - PRE_ALARM_TOTAL_VOLTAGE_HIGH(Doc.of(Level.INFO) // - .text("Total Voltage High Pre-Alarm")), // PRE_ALARM_CHARGE_CURRENT_HIGH(Doc.of(Level.INFO) // .text("Charge Current High Pre-Alarm")), // - PRE_ALARM_CELL_VOLTAGE_LOW(Doc.of(OpenemsType.BOOLEAN) // - .text("Cell Voltage Low Pre-Alarm")), // PRE_ALARM_TOTAL_VOLTAGE_LOW(Doc.of(Level.INFO) // .text("Total Voltage Low Pre-Alarm")), // PRE_ALARM_DISCHARGE_CURRENT_HIGH(Doc.of(Level.INFO) // @@ -427,10 +421,6 @@ public enum RackChannel { .text("Charge Temperature High Pre-Alarm")), // PRE_ALARM_CHARGE_TEMP_LOW(Doc.of(Level.INFO) // .text("Charge Temperature Low Pre-Alarm")), // - PRE_ALARM_SOC_LOW(Doc.of(OpenemsType.BOOLEAN) // - .text("State-Of-Charge Low Pre-Alarm")), // - PRE_ALARM_TEMP_DIFF_TOO_BIG(Doc.of(Level.INFO) // - .text("Temperature Difference Too Big Pre-Alarm")), // PRE_ALARM_POWER_POLE_HIGH(Doc.of(Level.INFO) // .text("Power Pole Temperature High Pre-Alarm")), // PRE_ALARM_CELL_VOLTAGE_DIFF_TOO_BIG(Doc.of(Level.INFO) // @@ -468,14 +458,10 @@ public enum RackChannel { .text("Discharge Current High Alarm Level 1")), // LEVEL1_TOTAL_VOLTAGE_LOW(Doc.of(Level.WARNING) // .text("Total Voltage Low Alarm Level 1")), // - LEVEL1_CELL_VOLTAGE_LOW(Doc.of(OpenemsType.BOOLEAN) // - .text("Cell Voltage Low Alarm Level 1")), // LEVEL1_CHARGE_CURRENT_HIGH(Doc.of(Level.WARNING) // .text("Charge Current High Alarm Level 1")), // LEVEL1_TOTAL_VOLTAGE_HIGH(Doc.of(Level.WARNING) // .text("Total Voltage High Alarm Level 1")), // - LEVEL1_CELL_VOLTAGE_HIGH(Doc.of(OpenemsType.BOOLEAN) // - .text("Cell Voltage High Alarm Level 1")), // // Alarm Level 2 LEVEL2_DISCHARGE_TEMP_LOW(Doc.of(Level.FAULT) // .text("Discharge Temperature Low Alarm Level 2")), // diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionC.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionC.java index f2bb9e746ad..28a54d5b9af 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionC.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionC.java @@ -578,8 +578,6 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId .text("Charge Temperature Low Pre-Alarm")), // PRE_ALARM_SOC_LOW(Doc.of(Level.INFO) // .text("State-Of-Charge Low Pre-Alarm")), // - PRE_ALARM_TEMP_DIFF_TOO_BIG(Doc.of(Level.INFO) // - .text("Temperature Difference Too Big Pre-Alarm")), // PRE_ALARM_POWER_POLE_HIGH(Doc.of(Level.INFO) // .text("Power Pole Temperature High Pre-Alarm")), // PRE_ALARM_CELL_VOLTAGE_DIFF_TOO_BIG(Doc.of(Level.INFO) // diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImpl.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImpl.java index 3e17ae11b05..3bc642bd4f4 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImpl.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImpl.java @@ -427,7 +427,6 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException { .bit(6, BatterySoltaroSingleRackVersionC.ChannelId.PRE_ALARM_CHARGE_TEMP_HIGH) // .bit(7, BatterySoltaroSingleRackVersionC.ChannelId.PRE_ALARM_CHARGE_TEMP_LOW) // .bit(8, BatterySoltaroSingleRackVersionC.ChannelId.PRE_ALARM_SOC_LOW) // - .bit(9, BatterySoltaroSingleRackVersionC.ChannelId.PRE_ALARM_TEMP_DIFF_TOO_BIG) // .bit(10, BatterySoltaroSingleRackVersionC.ChannelId.PRE_ALARM_POWER_POLE_HIGH) // .bit(11, BatterySoltaroSingleRackVersionC.ChannelId.PRE_ALARM_CELL_VOLTAGE_DIFF_TOO_BIG) // .bit(12, BatterySoltaroSingleRackVersionC.ChannelId.PRE_ALARM_INSULATION_FAIL) // diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/versionc/SoltaroBatteryVersionC.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/versionc/SoltaroBatteryVersionC.java new file mode 100644 index 00000000000..4267fb4f1a3 --- /dev/null +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/versionc/SoltaroBatteryVersionC.java @@ -0,0 +1,143 @@ +package io.openems.edge.battery.soltaro.versionc; + +import io.openems.common.channel.AccessMode; +import io.openems.common.channel.Level; +import io.openems.common.channel.Unit; +import io.openems.common.types.OpenemsType; +import io.openems.edge.battery.api.Battery; +import io.openems.edge.battery.soltaro.common.enums.EmsBaudrate; +import io.openems.edge.common.channel.Doc; + +public interface SoltaroBatteryVersionC extends Battery { + + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + /* + * EnumWriteChannels + */ + EMS_BAUDRATE(Doc.of(EmsBaudrate.values()) // + .accessMode(AccessMode.READ_WRITE)), // + + /* + * IntegerWriteChannels + */ + EMS_ADDRESS(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + EMS_COMMUNICATION_TIMEOUT(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.SECONDS) // + .accessMode(AccessMode.READ_WRITE)), // + + /* + * StateChannels + */ + // Other Alarm Info + ALARM_COMMUNICATION_TO_MASTER_BMS(Doc.of(Level.WARNING) // + .text("Communication Failure to Master BMS")), // + ALARM_COMMUNICATION_TO_SLAVE_BMS(Doc.of(Level.WARNING) // + .text("Communication Failure to Slave BMS")), // + ALARM_COMMUNICATION_SLAVE_BMS_TO_TEMP_SENSORS(Doc.of(Level.WARNING) // + .text("Communication Failure between Slave BMS and Temperature Sensors")), // + ALARM_SLAVE_BMS_HARDWARE(Doc.of(Level.WARNING) // + .text("Slave BMS Hardware Failure")), // + // Pre-Alarm + PRE_ALARM_TOTAL_VOLTAGE_HIGH(Doc.of(Level.INFO) // + .text("Total Voltage High Pre-Alarm")), // + PRE_ALARM_CHARGE_CURRENT_HIGH(Doc.of(Level.INFO) // + .text("Charge Current High Pre-Alarm")), // + PRE_ALARM_TOTAL_VOLTAGE_LOW(Doc.of(Level.INFO) // + .text("Total Voltage Low Pre-Alarm")), // + PRE_ALARM_DISCHARGE_CURRENT_HIGH(Doc.of(Level.INFO) // + .text("Discharge Current High Pre-Alarm")), // + PRE_ALARM_CHARGE_TEMP_HIGH(Doc.of(Level.INFO) // + .text("Charge Temperature High Pre-Alarm")), // + PRE_ALARM_CHARGE_TEMP_LOW(Doc.of(Level.INFO) // + .text("Charge Temperature Low Pre-Alarm")), // + PRE_ALARM_POWER_POLE_HIGH(Doc.of(Level.INFO) // + .text("Power Pole Temperature High Pre-Alarm")), // + PRE_ALARM_CELL_VOLTAGE_DIFF_TOO_BIG(Doc.of(Level.INFO) // + .text("Cell Voltage Difference Too Big Pre-Alarm")), // + PRE_ALARM_INSULATION_FAIL(Doc.of(Level.INFO) // + .text("Insulation Failure Pre-Alarm")), // + PRE_ALARM_TOTAL_VOLTAGE_DIFF_TOO_BIG(Doc.of(Level.INFO) // + .text("Total Voltage Difference Too Big Pre-Alarm")), // + PRE_ALARM_DISCHARGE_TEMP_HIGH(Doc.of(Level.INFO) // + .text("Discharge Temperature High Pre-Alarm")), // + PRE_ALARM_DISCHARGE_TEMP_LOW(Doc.of(Level.INFO) // + .text("Discharge Temperature Low Pre-Alarm")), // + // Alarm Level 1 + LEVEL1_DISCHARGE_TEMP_LOW(Doc.of(Level.WARNING) // + .text("Discharge Temperature Low Alarm Level 1")), // + LEVEL1_DISCHARGE_TEMP_HIGH(Doc.of(Level.WARNING) // + .text("Discharge Temperature High Alarm Level 1")), // + LEVEL1_TOTAL_VOLTAGE_DIFF_TOO_BIG(Doc.of(Level.WARNING) // + .text("Total Voltage Difference Too Big Alarm Level 1")), // + LEVEL1_INSULATION_VALUE(Doc.of(Level.WARNING) // + .text("Insulation Value Failure Alarm Level 1")), // + LEVEL1_CELL_VOLTAGE_DIFF_TOO_BIG(Doc.of(Level.WARNING) // + .text("Cell Voltage Difference Too Big Alarm Level 1")), // + LEVEL1_POWER_POLE_TEMP_HIGH(Doc.of(Level.WARNING) // + .text("Power Pole temperature too high Alarm Level 1")), // + LEVEL1_TEMP_DIFF_TOO_BIG(Doc.of(Level.WARNING) // + .text("Temperature Difference Too Big Alarm Level 1")), // + LEVEL1_CHARGE_TEMP_LOW(Doc.of(Level.WARNING) // + .text("Cell Charge Temperature Low Alarm Level 1")), // + LEVEL1_SOC_LOW(Doc.of(Level.WARNING) // + .text("Stage-Of-Charge Low Alarm Level 1")), // + LEVEL1_CHARGE_TEMP_HIGH(Doc.of(Level.WARNING) // + .text("Charge Temperature High Alarm Level 1")), // + LEVEL1_DISCHARGE_CURRENT_HIGH(Doc.of(Level.WARNING) // + .text("Discharge Current High Alarm Level 1")), // + LEVEL1_TOTAL_VOLTAGE_LOW(Doc.of(Level.WARNING) // + .text("Total Voltage Low Alarm Level 1")), // + LEVEL1_CELL_VOLTAGE_LOW(Doc.of(Level.WARNING) // + .text("Cell Voltage Low Alarm Level 1")), // + LEVEL1_CHARGE_CURRENT_HIGH(Doc.of(Level.WARNING) // + .text("Charge Current High Alarm Level 1")), // + LEVEL1_TOTAL_VOLTAGE_HIGH(Doc.of(Level.WARNING) // + .text("Total Voltage High Alarm Level 1")), // + // Alarm Level 2 + LEVEL2_DISCHARGE_TEMP_LOW(Doc.of(Level.WARNING) // + .text("Discharge Temperature Low Alarm Level 2")), // + LEVEL2_DISCHARGE_TEMP_HIGH(Doc.of(Level.WARNING) // + .text("Discharge Temperature High Alarm Level 2")), // + LEVEL2_TOTAL_VOLTAGE_DIFF_TOO_BIG(Doc.of(Level.WARNING) // + .text("Total Voltage Difference Too Big Alarm Level 2")), // + LEVEL2_INSULATION_VALUE(Doc.of(Level.WARNING) // + .text("Insulation Value Failure Alarm Level 2")), // + LEVEL2_CELL_VOLTAGE_DIFF_TOO_BIG(Doc.of(Level.WARNING) // + .text("Cell Voltage Difference Too Big Alarm Level 2")), // + LEVEL2_POWER_POLE_TEMP_HIGH(Doc.of(Level.WARNING) // + .text("Power Pole temperature too high Alarm Level 2")), // + LEVEL2_TEMP_DIFF_TOO_BIG(Doc.of(Level.WARNING) // + .text("Temperature Difference Too Big Alarm Level 2")), // + LEVEL2_CHARGE_TEMP_LOW(Doc.of(Level.WARNING) // + .text("Cell Charge Temperature Low Alarm Level 2")), // + LEVEL2_SOC_LOW(Doc.of(Level.WARNING) // + .text("Stage-Of-Charge Low Alarm Level 2")), // + LEVEL2_CHARGE_TEMP_HIGH(Doc.of(Level.WARNING) // + .text("Charge Temperature High Alarm Level 2")), // + LEVEL2_DISCHARGE_CURRENT_HIGH(Doc.of(Level.WARNING) // + .text("Discharge Current High Alarm Level 2")), // + LEVEL2_TOTAL_VOLTAGE_LOW(Doc.of(Level.WARNING) // + .text("Total Voltage Low Alarm Level 2")), // + LEVEL2_CELL_VOLTAGE_LOW(Doc.of(Level.WARNING) // + .text("Cell Voltage Low Alarm Level 2")), // + LEVEL2_CHARGE_CURRENT_HIGH(Doc.of(Level.WARNING) // + .text("Charge Current High Alarm Level 2")), // + LEVEL2_TOTAL_VOLTAGE_HIGH(Doc.of(Level.WARNING) // + .text("Total Voltage High Alarm Level 2")), // + LEVEL2_CELL_VOLTAGE_HIGH(Doc.of(Level.INFO) // + .text("Cell Voltage High Alarm Level 2")), // + ; + + private final Doc doc; + + private ChannelId(Doc doc) { + this.doc = doc; + } + + @Override + public Doc doc() { + return this.doc; + } + } +} diff --git a/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcel.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcel.java index 84130b97a91..daa20ac5029 100644 --- a/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcel.java +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcel.java @@ -3,6 +3,7 @@ import io.openems.common.channel.AccessMode; import io.openems.common.channel.Debounce; import io.openems.common.channel.Level; +import io.openems.common.channel.PersistencePriority; import io.openems.common.channel.Unit; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.types.OpenemsType; @@ -93,6 +94,7 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { MANUFACTURER_AND_MODEL_NUMBER(Doc.of(OpenemsType.STRING) // .accessMode(AccessMode.READ_ONLY)), // SERIAL_NUMBER(Doc.of(OpenemsType.STRING) // + .persistencePriority(PersistencePriority.HIGH) // .accessMode(AccessMode.READ_ONLY)), // FAULT_STATUS(Doc.of(Level.FAULT) // .accessMode(AccessMode.READ_ONLY)), // diff --git a/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcelImpl.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcelImpl.java index 2cae3abcf59..b41b9e77a2c 100644 --- a/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcelImpl.java +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcelImpl.java @@ -522,7 +522,7 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException { m(BatteryInverterSinexcel.ChannelId.REACTIVE_POWER_L1, new SignedWordElement(113), SCALE_FACTOR_1), // m(BatteryInverterSinexcel.ChannelId.REACTIVE_POWER_L2, new SignedWordElement(114), - SCALE_FACTOR_1), // fems + SCALE_FACTOR_1), // m(BatteryInverterSinexcel.ChannelId.REACTIVE_POWER_L3, new SignedWordElement(115), SCALE_FACTOR_1), // m(BatteryInverterSinexcel.ChannelId.APPERENT_POWER_L1, new SignedWordElement(116), diff --git a/io.openems.edge.common/src/io/openems/edge/common/channel/Channel.java b/io.openems.edge.common/src/io/openems/edge/common/channel/Channel.java index da7a6cb76e1..3a8a17d6fa1 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/channel/Channel.java +++ b/io.openems.edge.common/src/io/openems/edge/common/channel/Channel.java @@ -1,6 +1,9 @@ package io.openems.edge.common.channel; +import java.time.Duration; import java.time.LocalDateTime; +import java.time.temporal.TemporalAmount; +import java.util.TreeMap; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -9,7 +12,6 @@ import io.openems.edge.common.channel.internal.AbstractReadChannel; import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.component.OpenemsComponent; -import io.openems.edge.common.type.CircularTreeMap; import io.openems.edge.common.type.TypeUtils; /** @@ -48,10 +50,10 @@ public interface Channel { /** - * Holds the number of past values for this Channel that are kept in the - * 'pastValues' variable. + * Holds the maximum allowed age of past values based on the latest value for + * this Channel that are kept in the 'pastValues' variable. */ - public static final int NO_OF_PAST_VALUES = 300; + public static final TemporalAmount MAX_AGE_OF_PAST_VALUES = Duration.ofMinutes(5).plusSeconds(10); /** * Gets the ChannelId of this Channel. @@ -170,7 +172,7 @@ public default void setNextValue(Object value) { * @return a map of recording time and historic value at that time */ // TODO this should be a ZonedDateTime - public CircularTreeMap> getPastValues(); + public TreeMap> getPastValues(); /** * Add an onUpdate callback. It is called, after the active value was updated by diff --git a/io.openems.edge.common/src/io/openems/edge/common/channel/StateChannelDoc.java b/io.openems.edge.common/src/io/openems/edge/common/channel/StateChannelDoc.java index 431e5a1c63f..6a83379456f 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/channel/StateChannelDoc.java +++ b/io.openems.edge.common/src/io/openems/edge/common/channel/StateChannelDoc.java @@ -2,6 +2,7 @@ import io.openems.common.channel.ChannelCategory; import io.openems.common.channel.Level; +import io.openems.common.channel.PersistencePriority; import io.openems.edge.common.component.OpenemsComponent; public class StateChannelDoc extends BooleanDoc { @@ -12,6 +13,7 @@ public StateChannelDoc(Level level) { super(); this.level = level; this.initialValue(false); + this.persistencePriority(PersistencePriority.HIGH); } @Override diff --git a/io.openems.edge.common/src/io/openems/edge/common/channel/internal/AbstractDoc.java b/io.openems.edge.common/src/io/openems/edge/common/channel/internal/AbstractDoc.java index af4850548b8..1fe4388ba83 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/channel/internal/AbstractDoc.java +++ b/io.openems.edge.common/src/io/openems/edge/common/channel/internal/AbstractDoc.java @@ -70,7 +70,7 @@ public AccessMode getAccessMode() { /** * PersistencePriority for this Channel. */ - private PersistencePriority persistencePriority = PersistencePriority.VERY_LOW; + private PersistencePriority persistencePriority = PersistencePriority.LOW; /** * Sets the {@link PersistencePriority}. Defaults to diff --git a/io.openems.edge.common/src/io/openems/edge/common/channel/internal/AbstractReadChannel.java b/io.openems.edge.common/src/io/openems/edge/common/channel/internal/AbstractReadChannel.java index af0a3fe2c0e..0a82e5539a6 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/channel/internal/AbstractReadChannel.java +++ b/io.openems.edge.common/src/io/openems/edge/common/channel/internal/AbstractReadChannel.java @@ -3,6 +3,7 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Objects; +import java.util.TreeMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -20,7 +21,6 @@ import io.openems.edge.common.channel.WriteChannel; import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.component.OpenemsComponent; -import io.openems.edge.common.type.CircularTreeMap; public abstract class AbstractReadChannel, T> implements Channel { @@ -34,7 +34,7 @@ public abstract class AbstractReadChannel, T> implement private final List>> onUpdateCallbacks = new CopyOnWriteArrayList<>(); private final List>> onSetNextValueCallbacks = new CopyOnWriteArrayList<>(); private final List, Value>> onChangeCallbacks = new CopyOnWriteArrayList<>(); - private final CircularTreeMap> pastValues = new CircularTreeMap<>(NO_OF_PAST_VALUES); + private final TreeMap> pastValues = new TreeMap<>(); /** * The 'next' value of the Channel. Copied to 'active' in @@ -120,8 +120,8 @@ public void nextProcessImage() { this.onChangeCallbacks.forEach(callback -> callback.accept(oldValue, newValue)); } - // Additionally store value in 'pastValues' - this.pastValues.put(this.activeValue.getTimestamp(), newValue); + // Additionally append to 'pastValues' + this.appendPastValue(newValue); } catch (RuntimeException e) { this.log.error("Error while updating process image for [" + this.address() + "]: " + e.getMessage()); @@ -129,6 +129,19 @@ public void nextProcessImage() { } } + /** + * Appends a value to `pastValues` and deletes entries that are elder than + * {@link Channel#MAX_AGE_OF_PAST_VALUES}. + * + * @param value a new {@link Value} + */ + private void appendPastValue(Value value) { + final var compareTime = value.getTimestamp().minus(Channel.MAX_AGE_OF_PAST_VALUES); + this.pastValues.put(value.getTimestamp(), value); + // changes to sub map are also applied to the backed map + this.pastValues.headMap(compareTime).clear(); + } + @Override public ChannelAddress address() { return new ChannelAddress(this.parent.id(), this.channelId().id()); @@ -285,7 +298,7 @@ private boolean validateType(OpenemsType expected, OpenemsType actual) { * @return a map of recording time and historic value at that time */ @Override - public CircularTreeMap> getPastValues() { + public TreeMap> getPastValues() { return this.pastValues; } diff --git a/io.openems.edge.common/src/io/openems/edge/common/component/AbstractOpenemsComponent.java b/io.openems.edge.common/src/io/openems/edge/common/component/AbstractOpenemsComponent.java index b9cf60d9e16..b4fd7cbc2cc 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/component/AbstractOpenemsComponent.java +++ b/io.openems.edge.common/src/io/openems/edge/common/component/AbstractOpenemsComponent.java @@ -27,7 +27,6 @@ import io.openems.edge.common.channel.Channel; import io.openems.edge.common.channel.Doc; import io.openems.edge.common.channel.StateChannel; -import io.openems.edge.common.channel.internal.AbstractDoc; import io.openems.edge.common.type.TypeUtils; /** @@ -304,8 +303,8 @@ private void addChannelsForProperties(ObjectClassDefinition ocd, Dictionary create new Channel - AbstractDoc doc = Doc.of(channelType); - doc.persistencePriority(PersistencePriority.MEDIUM); + var doc = Doc.of(channelType) // + .persistencePriority(PersistencePriority.HIGH); io.openems.edge.common.channel.ChannelId channelId = new io.openems.edge.common.channel.ChannelId() { @Override diff --git a/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java b/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java index dd7c72b89fb..6aa652bf83d 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java +++ b/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java @@ -2,6 +2,7 @@ import io.openems.common.OpenemsConstants; import io.openems.common.channel.AccessMode; +import io.openems.common.channel.PersistencePriority; import io.openems.common.types.OpenemsType; import io.openems.edge.common.channel.Doc; import io.openems.edge.common.channel.EnumReadChannel; @@ -25,7 +26,8 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { *

  • Type: String * */ - VERSION(Doc.of(OpenemsType.STRING)), + VERSION(Doc.of(OpenemsType.STRING) // + .persistencePriority(PersistencePriority.HIGH)), /** * Edge currency. @@ -35,7 +37,8 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { *
  • Type: Currency * */ - CURRENCY(Doc.of(Currency.values())); + CURRENCY(Doc.of(Currency.values()) // + .persistencePriority(PersistencePriority.HIGH)); private final Doc doc; diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordChannel.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordChannel.java index 931fb9326f9..826959f66b1 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordChannel.java +++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordChannel.java @@ -113,11 +113,15 @@ public String toString() { @Override public byte[] getValue(OpenemsComponent component) { - final Object value; + Object value; if (component != null) { Channel channel = component.channel(this.channelId); if (channel != null) { - value = channel.value().get(); + try { + value = channel.value().get(); + } catch (IllegalArgumentException e) { + value = null; + } } else { this.log.warn("Channel [" + component.id() + "/" + this.channelId.id() + "] is not available for " + this.toString()); diff --git a/io.openems.edge.common/src/io/openems/edge/common/type/CircularTreeMap.java b/io.openems.edge.common/src/io/openems/edge/common/type/CircularTreeMap.java deleted file mode 100644 index ad1b613a668..00000000000 --- a/io.openems.edge.common/src/io/openems/edge/common/type/CircularTreeMap.java +++ /dev/null @@ -1,41 +0,0 @@ -package io.openems.edge.common.type; - -import java.util.TreeMap; - -/** - * Implements a circular buffer with a TreeMap. - * - *

    - * Be aware that not the eldest entry is removed when the buffer is full, but - * the entry with the lowest key is removed! - * - * @param the type of the Key - * @param the type of the Value - */ -public class CircularTreeMap extends TreeMap { - - private static final long serialVersionUID = 1L; - - private final int limit; - - public CircularTreeMap(int limit) { - this.limit = limit; - } - - @Override - public V put(K key, V value) { - var result = super.put(key, value); - if (super.size() > this.limit) { - this.removeLowest(); - } - return result; - } - - private void removeLowest() { - var iterator = this.keySet().iterator(); - if (iterator.hasNext()) { - this.remove(iterator.next()); - } - } - -} diff --git a/io.openems.edge.common/src/io/openems/edge/common/user/User.java b/io.openems.edge.common/src/io/openems/edge/common/user/User.java index 374a9d6f3e0..f05e25c8ef4 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/user/User.java +++ b/io.openems.edge.common/src/io/openems/edge/common/user/User.java @@ -84,4 +84,9 @@ public String toString() { return "User [id=" + this.getId() + ", name=" + this.getName() + "]"; } + @Override + public boolean hasMultipleEdges() { + return false; + } + } diff --git a/io.openems.edge.common/test/io/openems/edge/common/type/CircularTreeMapTest.java b/io.openems.edge.common/test/io/openems/edge/common/type/CircularTreeMapTest.java deleted file mode 100644 index d498fc25faf..00000000000 --- a/io.openems.edge.common/test/io/openems/edge/common/type/CircularTreeMapTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.openems.edge.common.type; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; - -public class CircularTreeMapTest { - - @Test - public void test() { - var m = new CircularTreeMap(3); - m.put("1", "one"); - m.put("2", "two"); - m.put("3", "three"); - m.put("4", "four"); - - var ks = m.keySet(); - var i = ks.iterator(); - assertEquals("2", i.next()); - assertEquals("3", i.next()); - assertEquals("4", i.next()); - } - - @Test - public void testAnotherOrder() { - var m = new CircularTreeMap(3); - m.put("4", "four"); - m.put("3", "three"); - m.put("2", "two"); - m.put("1", "one"); - - var ks = m.keySet(); - var i = ks.iterator(); - assertEquals("2", i.next()); - assertEquals("3", i.next()); - assertEquals("4", i.next()); - } -} diff --git a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/Config.java b/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/Config.java index 6e7826fe89a..797e115933b 100644 --- a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/Config.java +++ b/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/Config.java @@ -41,8 +41,14 @@ @AttributeDefinition(name = "Api-Timeout", description = "Sets the timeout in seconds for updates on Channels set by this Api.") int apiTimeout() default 60; - @AttributeDefinition(name = "Persistence Priority", description = "Send only Channels with a Persistence Priority greater-or-equals this.") - PersistencePriority persistencePriority() default PersistencePriority.VERY_LOW; + @AttributeDefinition(name = "Persistence Priority", description = "Send only Channels with a Persistence Priority greater-or-equals this on every Cycle.") + PersistencePriority persistencePriority() default PersistencePriority.HIGH; + + @AttributeDefinition(name = "Aggregated values Persistence Priority", description = "Send only Channels as aggregated values with a Persistence Priority greater-or-equals this.") + PersistencePriority aggregationPriority() default PersistencePriority.LOW; + + @AttributeDefinition(name = "Resend values Persistence Priority", description = "Resend only Channels with a Persistence Priority greater-or-equals this. Should match with the persistence priority configured in your timedata.") + PersistencePriority resendPriority() default PersistencePriority.HIGH; @AttributeDefinition(name = "Debug Mode", description = "Activates the debug mode") boolean debugMode() default false; diff --git a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/ControllerApiBackend.java b/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/ControllerApiBackend.java index 402784b2090..555420f97c8 100644 --- a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/ControllerApiBackend.java +++ b/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/ControllerApiBackend.java @@ -5,8 +5,10 @@ import io.openems.common.channel.Level; import io.openems.common.channel.PersistencePriority; +import io.openems.common.channel.Unit; import io.openems.common.types.OpenemsType; import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.channel.LongReadChannel; import io.openems.edge.common.channel.StateChannel; import io.openems.edge.common.channel.StringReadChannel; import io.openems.edge.common.component.OpenemsComponent; @@ -22,10 +24,10 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { // Make sure this is always persisted, as it is required for resending .persistencePriority(PersistencePriority.VERY_HIGH)), // LAST_SUCCESSFUL_RESEND(Doc.of(OpenemsType.LONG) // + .unit(Unit.CUMULATED_SECONDS) // Make sure this is always persisted, as it is required for resending .persistencePriority(PersistencePriority.VERY_HIGH) // - .text("Latest timestamp of successfully resent data")) // - // TODO: resend algorithm still needs to be implemented + .text("Latest timestamp of successfully resent data")), // ; private final Doc doc; @@ -58,10 +60,20 @@ public default StateChannel getUnableToSendChannel() { return this.channel(ChannelId.UNABLE_TO_SEND); } + /** + * Gets the Channel for {@link ChannelId#LAST_SUCCESSFUL_RESEND}. + * + * @return the Channel + */ + public default LongReadChannel getLastSuccessFulResendChannel() { + return this.channel(ChannelId.LAST_SUCCESSFUL_RESEND); + } + /** * Gets if the edge is currently connected to the backend. * * @return true if it is connected */ public boolean isConnected(); + } diff --git a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/ControllerApiBackendImpl.java b/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/ControllerApiBackendImpl.java index 94799a2e0fd..72e43df6b8d 100644 --- a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/ControllerApiBackendImpl.java +++ b/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/ControllerApiBackendImpl.java @@ -21,9 +21,7 @@ import org.osgi.service.component.annotations.ConfigurationPolicy; import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Reference; -import org.osgi.service.component.annotations.ReferenceCardinality; -import org.osgi.service.component.annotations.ReferencePolicy; -import org.osgi.service.component.annotations.ReferencePolicyOption; +import org.osgi.service.component.annotations.ReferenceScope; import org.osgi.service.event.Event; import org.osgi.service.event.EventHandler; import org.osgi.service.event.propertytypes.EventTopics; @@ -50,7 +48,6 @@ import io.openems.edge.common.user.User; import io.openems.edge.controller.api.Controller; import io.openems.edge.controller.api.common.ApiWorker; -import io.openems.edge.timedata.api.Timedata; @Designate(ocd = Config.class, factory = true) @Component(// @@ -75,8 +72,8 @@ public class ControllerApiBackendImpl extends AbstractOpenemsComponent private final Logger log = LoggerFactory.getLogger(ControllerApiBackendImpl.class); - @Reference(policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.OPTIONAL) - private volatile Timedata timedata = null; + @Reference(scope = ReferenceScope.PROTOTYPE_REQUIRED) + protected ResendHistoricDataWorker resendHistoricDataWorker; @Reference protected ComponentManager componentManager; @@ -99,6 +96,12 @@ public ControllerApiBackendImpl() { this.apiWorker.setLogChannel(this.getApiWorkerLogChannel()); } + /** + * Activation method. + * + * @param context the {@link ComponentContext} + * @param config the {@link Config} + */ @Activate private void activate(ComponentContext context, Config config) { this.config = config; @@ -140,6 +143,15 @@ private void activate(ComponentContext context, Config config) { // Create Websocket instance this.websocket = new WebsocketClient(this, name, uri, httpHeaders, proxy); this.websocket.start(); + + this.resendHistoricDataWorker.setConfig(new ResendHistoricDataWorker.Config(// + this.getUnableToSendChannel().address(), // + this.getLastSuccessFulResendChannel().address(), // + config.resendPriority(), // + t -> this.getLastSuccessFulResendChannel().setNextValue(t), // + t -> this.websocket.sendMessage(t) // + )); + this.resendHistoricDataWorker.activate(this.id(), false); } @Override diff --git a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/OnOpen.java b/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/OnOpen.java index 14d64f96650..58bea7049ac 100644 --- a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/OnOpen.java +++ b/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/OnOpen.java @@ -28,6 +28,9 @@ public void run(WebSocket ws, JsonObject handshake) { // Send all Channel values this.parent.sendChannelValuesWorker.sendValuesOfAllChannelsOnce(); + + // Trigger resending data + this.parent.resendHistoricDataWorker.triggerNextRun(); } } diff --git a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/ResendHistoricDataWorker.java b/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/ResendHistoricDataWorker.java new file mode 100644 index 00000000000..1e6b78029bf --- /dev/null +++ b/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/ResendHistoricDataWorker.java @@ -0,0 +1,252 @@ +package io.openems.edge.controller.api.backend; + +import static io.openems.common.utils.CollectorUtils.toTreeBasedTable; +import static java.util.stream.Collectors.toSet; + +import java.time.Instant; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Random; +import java.util.Set; +import java.util.SortedMap; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.component.annotations.ReferencePolicyOption; +import org.osgi.service.component.annotations.ServiceScope; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.TreeBasedTable; +import com.google.gson.JsonElement; + +import io.openems.common.channel.AccessMode; +import io.openems.common.channel.PersistencePriority; +import io.openems.common.jsonrpc.base.JsonrpcMessage; +import io.openems.common.jsonrpc.notification.ResendDataNotification; +import io.openems.common.types.ChannelAddress; +import io.openems.common.types.OpenemsType; +import io.openems.common.worker.AbstractWorker; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.common.type.TypeUtils; +import io.openems.edge.timedata.api.Timedata; + +@Component(// + scope = ServiceScope.PROTOTYPE, // + service = { ResendHistoricDataWorker.class } // +) +public final class ResendHistoricDataWorker extends AbstractWorker { + + public record Config(// + ChannelAddress addressForSuccessfulSend, // + ChannelAddress addressForSuccessfulResend, // + PersistencePriority resendPriority, // + Consumer onLastSuccessfulResendUpdated, // + Function onSendData // + ) { + + } + + protected enum TriggerState { + INIT, // + AFTER_TRIGGER, // + SKIP_FIRST_FOREVER, // + WAITING_FOR_TIMEDATA, // + WAITING_FOR_CONFIG, // + } + + /** + * This worker only starts resending data, after it got explicit triggered with + * {@link AbstractWorker#triggerNextRun()} and also after this delay time. + */ + protected static final int DELAY_TRIGGER_TIME = 300_000; // [milliseconds] 5 min + private static final int MAX_RANDOM_DELAY = 3_600_000; // [milliseconds] 1 h + private static final int BUFFER_SECONDS = 300; // [seconds] 5 min + private static final int MAX_RESEND_TIMESPAN_SECONDS = 300; // [seconds] 5 min + + private final Logger log = LoggerFactory.getLogger(ResendHistoricDataWorker.class); + + @Reference + private ComponentManager componentManager; + private volatile Timedata timedata; + + private Config config; + + /** + * Trigger helper variable to delay execution of the forever method by + * DELAY_TRIGGER_TIME. If during the forever method timedata or the + * configuration is not set the state changes into either + * {@link TriggerState#WAITING_FOR_TIMEDATA} or + * {@link TriggerState#WAITING_FOR_CONFIG} and continues with the execution when + * the one missing got set. + * + *

    +	 * triggerState = INIT
    +	 * tiggerNextRun();
    +	 * -> triggerState = AFTER_TRIGGER; 
    +	 * -> forever();
    +	 * -> if(triggerState == 1) triggerState = SKIP_FIRST_FOREVER;
    +	 * -> getCycleTime() = DELAY_TRIGGER_TIME; triggerState = INIT;
    +	 * -> forever();
    +	 * 
    + */ + protected final AtomicReference triggerState = new AtomicReference<>(TriggerState.INIT); + + @Activate + public ResendHistoricDataWorker() { + } + + @Override + @Deactivate + public void deactivate() { + super.deactivate(); + } + + @Override + protected void forever() throws Throwable { + if (this.triggerState.compareAndSet(TriggerState.AFTER_TRIGGER, TriggerState.SKIP_FIRST_FOREVER)) { + return; + } + + Timedata timedata; + final Config config; + synchronized (this.triggerState) { + config = this.config; + if (config == null) { + this.triggerState.set(TriggerState.WAITING_FOR_CONFIG); + this.log.warn("ResendHistoricDataWorker configuration is not set!"); + return; + } + + timedata = this.timedata; + if (timedata == null) { + this.triggerState.set(TriggerState.WAITING_FOR_TIMEDATA); + this.log.info("Missing timedata reference!"); + return; + } + } + + final var latestResendTimestamp = timedata.getLatestValue(config.addressForSuccessfulResend()).get() // + .map(t -> TypeUtils.getAsType(OpenemsType.LONG, t)) // + .orElse(-1L); + + final var now = ZonedDateTime.now(this.componentManager.getClock()) // + .minus(DELAY_TRIGGER_TIME, ChronoUnit.MILLIS); + final var timeranges = timedata.getResendTimeranges(config.addressForSuccessfulSend(), latestResendTimestamp) // + .withBuffer(BUFFER_SECONDS, BUFFER_SECONDS); + + final var channelsToResend = this.getChannelsToResend(config.resendPriority()); + + // maximum of 5 minutes range of resend data + for (var timerange : timeranges.maxDataInTime(MAX_RESEND_TIMESPAN_SECONDS)) { + final var from = Instant.ofEpochSecond(timerange.getMinTimestamp()).atZone(now.getZone()); + final var to = Instant.ofEpochSecond(timerange.getMaxTimestamp()).atZone(now.getZone()); + + timedata = this.timedata; + if (timedata == null) { + synchronized (this.triggerState) { + timedata = this.timedata; + if (timedata == null) { + this.triggerState.set(TriggerState.WAITING_FOR_TIMEDATA); + this.log.info("Missing timedata reference!"); + return; + } + } + } + + final var data = timedata.queryResendData(from, to, channelsToResend); + + final var successful = config.onSendData().apply(new ResendDataNotification(mapResendData(data))); + + if (successful) { + config.onLastSuccessfulResendUpdated().accept(timerange.getMaxTimestamp()); + } else { + // if data can not be send wait for next trigger + this.log.warn("Unable to resend data!"); + return; + } + } + + } + + @Reference(// + policy = ReferencePolicy.DYNAMIC, // + policyOption = ReferencePolicyOption.GREEDY, // + cardinality = ReferenceCardinality.OPTIONAL, // + bind = "bindTimedata", unbind = "unbindTimedata" // + ) + protected void bindTimedata(Timedata timedata) { + synchronized (this.triggerState) { + this.timedata = timedata; + if (this.triggerState.get() == TriggerState.WAITING_FOR_TIMEDATA) { + this.triggerNextRun(); + } + } + } + + protected void unbindTimedata(Timedata timedata) { + this.timedata = null; + } + + private Set getChannelsToResend(PersistencePriority resendPriority) { + return this.componentManager.getEnabledComponents().stream() // + .flatMap(component -> component.channels().stream()) // + .filter(channel -> // + channel.channelDoc().getAccessMode() != AccessMode.WRITE_ONLY // + && channel.channelDoc().getPersistencePriority() // + .isAtLeast(resendPriority)) + .map(t -> t.address()) // + .collect(toSet()); + } + + @Override + protected int getCycleTime() { + if (this.triggerState.compareAndSet(TriggerState.SKIP_FIRST_FOREVER, TriggerState.INIT)) { + return DELAY_TRIGGER_TIME + new Random().nextInt(MAX_RANDOM_DELAY); + } + return AbstractWorker.ALWAYS_WAIT_FOR_TRIGGER_NEXT_RUN; + } + + @Override + public void triggerNextRun() { + this.triggerState.set(TriggerState.AFTER_TRIGGER); + super.triggerNextRun(); + } + + public void setConfig(Config config) { + if (Objects.equals(config, this.config)) { + return; + } + synchronized (this.triggerState) { + this.config = config; + if (this.config == null) { + return; + } + if (this.triggerState.get() == TriggerState.WAITING_FOR_CONFIG) { + this.triggerNextRun(); + } + } + } + + protected static TreeBasedTable mapResendData(// + final SortedMap> resendData // + ) { + return resendData.entrySet().stream() // + .collect(Collectors.toMap(Entry::getKey, + entry -> entry.getValue().entrySet().stream() + .collect(Collectors., String, JsonElement>toMap( + t -> t.getKey().toString(), Entry::getValue)))) // + .entrySet().stream().collect(toTreeBasedTable()); + } + +} diff --git a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/SendChannelValuesWorker.java b/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/SendChannelValuesWorker.java index 0397f20fd51..bbc68f01a3d 100644 --- a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/SendChannelValuesWorker.java +++ b/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/SendChannelValuesWorker.java @@ -2,25 +2,39 @@ import java.time.Duration; import java.time.Instant; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.TreeBasedTable; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonPrimitive; import io.openems.common.channel.AccessMode; +import io.openems.common.jsonrpc.notification.AggregatedDataNotification; import io.openems.common.jsonrpc.notification.TimestampedDataNotification; +import io.openems.common.timedata.DurationUnit; +import io.openems.common.types.OpenemsType; import io.openems.common.utils.ThreadPoolUtils; import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.type.TypeUtils; /** * Method {@link #collectData()} is called Synchronously with the Core.Cycle to @@ -33,6 +47,7 @@ */ public class SendChannelValuesWorker { + private static final int AGGREGATION_MINUTES = 5; private static final int SEND_VALUES_OF_ALL_CHANNELS_AFTER_SECONDS = 300; /* 5 minutes */ private final Logger log = LoggerFactory.getLogger(SendChannelValuesWorker.class); @@ -48,6 +63,7 @@ public class SendChannelValuesWorker { * If true: next 'send' sends all channel values. */ private final AtomicBoolean sendValuesOfAllChannels = new AtomicBoolean(true); + private final AtomicBoolean sendValuesOfAllChannelsAggregated = new AtomicBoolean(true); /** * Keeps the last timestamp when all channel values were sent. @@ -57,7 +73,9 @@ public class SendChannelValuesWorker { /** * Keeps the values of last successful send. */ - private ImmutableMap lastAllValues = ImmutableMap.of(); + private Map lastAllValues = ImmutableMap.of(); + + private Instant lastSendAggregatedDataTimestamp; protected SendChannelValuesWorker(ControllerApiBackendImpl parent) { this.parent = parent; @@ -68,6 +86,7 @@ protected SendChannelValuesWorker(ControllerApiBackendImpl parent) { */ public synchronized void sendValuesOfAllChannelsOnce() { this.sendValuesOfAllChannels.set(true); + this.sendValuesOfAllChannelsAggregated.set(true); } /** @@ -88,9 +107,15 @@ public synchronized void collectData() { // Update the values of all channels final var enabledComponents = this.parent.componentManager.getEnabledComponents(); final var allValues = this.collectData(enabledComponents); + final var aggregatedValues = this.collectAggregatedData(enabledComponents); // Add to send Queue this.executor.execute(new SendTask(this, now, allValues)); + if (aggregatedValues != null && !aggregatedValues.isEmpty()) { + aggregatedValues.rowMap().forEach((timestamp, data) -> { + this.executor.execute(new SendAggregatedDataTask(this, Instant.ofEpochMilli(timestamp), data)); + }); + } } /** @@ -126,18 +151,130 @@ private ImmutableMap collectData(List ena } } + private TreeBasedTable collectAggregatedData(List enabledComponents) { + final var now = LocalDateTime.now(this.parent.componentManager.getClock()); + final var endTime = now.truncatedTo(DurationUnit.ofMinutes(AGGREGATION_MINUTES)); + final var startTime = endTime.minusMinutes(AGGREGATION_MINUTES); + + final var timestamp = Instant.now().truncatedTo(DurationUnit.ofMinutes(AGGREGATION_MINUTES)) // + .minus(AGGREGATION_MINUTES, ChronoUnit.MINUTES); + if (this.lastSendAggregatedDataTimestamp == null) { + this.lastSendAggregatedDataTimestamp = timestamp; + return null; + } + if (timestamp.equals(this.lastSendAggregatedDataTimestamp)) { + return null; + } + this.lastSendAggregatedDataTimestamp = timestamp; + final var timestampMillis = timestamp.toEpochMilli(); + + final var sendAllChannels = this.sendValuesOfAllChannelsAggregated.getAndSet(false); + + final var table = TreeBasedTable.create(); + enabledComponents.stream() // + .flatMap(component -> component.channels().stream()) // + .filter(channel -> // Ignore WRITE_ONLY Channels + channel.channelDoc().getAccessMode() != AccessMode.WRITE_ONLY // + // Ignore Low-Priority Channels + && channel.channelDoc().getPersistencePriority() + .isAtLeast(this.parent.config.aggregationPriority())) + .forEach(channel -> { + try { + // This is the highest timestamp before `startTime`. If existing it is used for + // the tailMap to make sure we get a Value even for Channels where the value has + // not changed within the last 5 minutes. + var channelStartTime = Optional.ofNullable(channel.getPastValues().floorKey(startTime)) + .orElse(startTime); + + var value = channel.getPastValues() // + .tailMap(channelStartTime, true) // + .entrySet() // + .stream() // + .filter(e -> e.getKey().isBefore(endTime)) // + .filter(e -> e.getValue().isDefined()).map(e -> e.getValue().get()) // + .collect(aggregateCollector(channel.channelDoc().getUnit().isCumulated(), // + channel.getType())); + + if (!sendAllChannels && value.isJsonNull()) { + return; + } + table.put(timestampMillis, channel.address().toString(), value); + } catch (IllegalArgumentException e) { + // unable to collect data because types are not matching the expected one + e.printStackTrace(); + } + }); + return table; + } + + protected static Collector aggregateCollector(// + final boolean isCumulated, // + final OpenemsType type // + ) { + return Collector.of(ArrayList::new, // + (a, b) -> a.add(b), // + (a, b) -> { + b.addAll(a); + return b; + }, // + t -> aggregate(isCumulated, type, t) // + ); + } + + protected static JsonElement aggregate(boolean isCumulated, OpenemsType type, Collection values) + throws IllegalArgumentException { + switch (type) { + case DOUBLE, FLOAT -> { + final var stream = values.stream() // + .mapToDouble(item -> TypeUtils.getAsType(OpenemsType.DOUBLE, item)); + if (isCumulated) { + final var maxOpt = stream.max(); + if (maxOpt.isPresent()) { + return new JsonPrimitive(maxOpt.getAsDouble()); + } + } else { + final var avgOpt = stream.average(); + if (avgOpt.isPresent()) { + return new JsonPrimitive(avgOpt.getAsDouble()); + } + } + } + // round averages to their type + case BOOLEAN, LONG, INTEGER, SHORT -> { + final var stream = values.stream() // + .mapToLong(item -> TypeUtils.getAsType(OpenemsType.LONG, item)); + if (isCumulated) { + final var maxOpt = stream.max(); + if (maxOpt.isPresent()) { + return new JsonPrimitive(maxOpt.getAsLong()); + } + } else { + final var avgOpt = stream.average(); + if (avgOpt.isPresent()) { + return new JsonPrimitive(Math.round(avgOpt.getAsDouble())); + } + } + } + case STRING -> { + // return first string for now + for (var item : values) { + return new JsonPrimitive(TypeUtils.getAsType(type, item)); + } + } + } + return JsonNull.INSTANCE; + } + /* * From here things run asynchronously. */ - private static class SendTask implements Runnable { private final SendChannelValuesWorker parent; private final Instant timestamp; - private final ImmutableMap allValues; + private final Map allValues; - public SendTask(SendChannelValuesWorker parent, Instant timestamp, - ImmutableMap allValues) { + public SendTask(SendChannelValuesWorker parent, Instant timestamp, Map allValues) { this.parent = parent; this.timestamp = timestamp; this.allValues = allValues; @@ -147,7 +284,7 @@ public SendTask(SendChannelValuesWorker parent, Instant timestamp, public void run() { // Holds the data of the last successful send. If the table is empty, it is also // used as a marker to send all data. - final ImmutableMap lastAllValues; + final Map lastAllValues; if (this.parent.sendValuesOfAllChannels.getAndSet(false)) { // Send values of all Channels once in a while @@ -194,9 +331,6 @@ public void run() { // Try to send var wasSent = this.parent.parent.websocket.sendMessage(message); - // Set the UNABLE_TO_SEND channel - this.parent.parent.getUnableToSendChannel().setNextValue(!wasSent); - if (wasSent) { // Successfully sent: update information for next runs this.parent.lastAllValues = this.allValues; @@ -210,4 +344,31 @@ public void run() { } + private static final class SendAggregatedDataTask implements Runnable { + + private final SendChannelValuesWorker parent; + private final Instant timestamp; + private final Map allValues; + + public SendAggregatedDataTask(SendChannelValuesWorker parent, Instant timestamp, + Map allValues) { + super(); + this.parent = parent; + this.timestamp = timestamp; + this.allValues = allValues; + } + + @Override + public void run() { + final var message = new AggregatedDataNotification(); + message.add(this.timestamp.toEpochMilli(), this.allValues); + + final var wasSent = this.parent.parent.websocket.sendMessage(message); + + // Set the UNABLE_TO_SEND channel + this.parent.parent.getUnableToSendChannel().setNextValue(!wasSent); + } + + } + } \ No newline at end of file diff --git a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/WebsocketClient.java b/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/WebsocketClient.java index 6b69408d4cd..45a9800a44b 100644 --- a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/WebsocketClient.java +++ b/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/WebsocketClient.java @@ -35,6 +35,7 @@ protected WebsocketClient(ControllerApiBackendImpl parent, String name, URI serv this.onClose = (ws, code, reason, remote) -> { this.log.error("Disconnected from OpenEMS Backend [" + serverUri.toString() // + (proxy != AbstractWebsocketClient.NO_PROXY ? " via Proxy" : "") + "]"); + this.parent.getUnableToSendChannel().setNextValue(true); }; } diff --git a/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/ControllerApiBackendImplTest.java b/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/ControllerApiBackendImplTest.java index 9a203820bcf..3e532abad03 100644 --- a/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/ControllerApiBackendImplTest.java +++ b/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/ControllerApiBackendImplTest.java @@ -38,6 +38,7 @@ public void test() throws Exception { new ComponentTest(sut) // .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("cycle", new DummyCycle(1000)) // + .addReference("resendHistoricDataWorker", new ResendHistoricDataWorker()) // .addComponent(new DummySum()) // .activate(MyConfig.create() // .setId(CTRL_ID) // @@ -45,7 +46,9 @@ public void test() throws Exception { .setApikey("12345") // .setProxyType(Type.DIRECT) // .setProxyAddress("") // - .setPersistencePriority(PersistencePriority.VERY_LOW) // + .setPersistencePriority(PersistencePriority.HIGH) // + .setAggregationPriority(PersistencePriority.VERY_LOW) // + .setResendPriority(PersistencePriority.MEDIUM) // .build()); // Stop connection diff --git a/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/MyConfig.java b/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/MyConfig.java index 40a6ed13614..ed16098a4e3 100644 --- a/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/MyConfig.java +++ b/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/MyConfig.java @@ -17,6 +17,8 @@ protected static class Builder { private Type proxyType; private int apiTimeout; private PersistencePriority persistencePriority; + private PersistencePriority aggregationPriority; + private PersistencePriority resendPriority; private boolean debugMode; private Builder() { @@ -62,6 +64,16 @@ public Builder setPersistencePriority(PersistencePriority persistencePriority) { return this; } + public Builder setAggregationPriority(PersistencePriority aggregationPriority) { + this.aggregationPriority = aggregationPriority; + return this; + } + + public Builder setResendPriority(PersistencePriority resendPriority) { + this.resendPriority = resendPriority; + return this; + } + public Builder setDebugMode(boolean debugMode) { this.debugMode = debugMode; return this; @@ -123,6 +135,16 @@ public PersistencePriority persistencePriority() { return this.builder.persistencePriority; } + @Override + public PersistencePriority aggregationPriority() { + return this.builder.aggregationPriority; + } + + @Override + public PersistencePriority resendPriority() { + return this.builder.resendPriority; + } + @Override public boolean debugMode() { return this.builder.debugMode; diff --git a/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/ResendHistoricDataWorkerTest.java b/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/ResendHistoricDataWorkerTest.java new file mode 100644 index 00000000000..a06739a005b --- /dev/null +++ b/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/ResendHistoricDataWorkerTest.java @@ -0,0 +1,43 @@ +package io.openems.edge.controller.api.backend; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.SortedMap; +import java.util.TreeMap; + +import org.junit.Test; + +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; + +import io.openems.common.types.ChannelAddress; +import io.openems.edge.controller.api.backend.ResendHistoricDataWorker.TriggerState; + +public class ResendHistoricDataWorkerTest { + + @Test + public void testState() throws Throwable { + final var worker = new ResendHistoricDataWorker(); + + assertEquals(TriggerState.INIT, worker.triggerState.get()); + worker.triggerNextRun(); + assertEquals(TriggerState.AFTER_TRIGGER, worker.triggerState.get()); + worker.forever(); + assertEquals(TriggerState.SKIP_FIRST_FOREVER, worker.triggerState.get()); + assertTrue(worker.getCycleTime() >= ResendHistoricDataWorker.DELAY_TRIGGER_TIME); + } + + @Test + public void testMapResendData() { + final var testTimestamp = 1L; + final var testChannel = new ChannelAddress("c1", "test"); + final var testValue = new JsonPrimitive("value"); + SortedMap> data = new TreeMap<>(); + data.computeIfAbsent(testTimestamp, a -> new TreeMap<>()) // + .put(testChannel, testValue); + final var mapped = ResendHistoricDataWorker.mapResendData(data); + assertEquals(testValue, mapped.get(testTimestamp, testChannel.toString())); + } + +} diff --git a/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/SendChannelValuesWorkerTest.java b/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/SendChannelValuesWorkerTest.java new file mode 100644 index 00000000000..db9ad453f2d --- /dev/null +++ b/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/SendChannelValuesWorkerTest.java @@ -0,0 +1,55 @@ +package io.openems.edge.controller.api.backend; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import com.google.common.collect.Lists; + +import io.openems.common.types.OpenemsType; + +public class SendChannelValuesWorkerTest { + + @Test + public void testAggregateNaturalCumulated() { + final var value = SendChannelValuesWorker.aggregate(true, OpenemsType.LONG, // + Lists.newArrayList(2, 4)); + assertEquals(4, value.getAsLong()); + } + + @Test + public void testAggregateNaturalNotCumulated() { + final var value = SendChannelValuesWorker.aggregate(false, OpenemsType.LONG, // + Lists.newArrayList(2, 7)); + assertEquals(5, value.getAsLong()); + } + + @Test + public void testAggregateFloatingCumulated() { + final var value = SendChannelValuesWorker.aggregate(true, OpenemsType.DOUBLE, // + Lists.newArrayList(2.23, 4.75)); + assertEquals(4.75, value.getAsDouble(), 0); + } + + @Test + public void testAggregateFloatingNotCumulated() { + final var value = SendChannelValuesWorker.aggregate(false, OpenemsType.DOUBLE, // + Lists.newArrayList(2.9, 7.1)); + assertEquals(5, value.getAsDouble(), 0); + } + + @Test + public void testAggregateStringCumulated() { + final var value = SendChannelValuesWorker.aggregate(true, OpenemsType.STRING, // + Lists.newArrayList("a", "b", "c", "d", "e")); + assertEquals("a", value.getAsString()); + } + + @Test + public void testAggregateStringNotCumulated() { + final var value = SendChannelValuesWorker.aggregate(false, OpenemsType.STRING, // + Lists.newArrayList("a", "b", "c", "d", "e")); + assertEquals("a", value.getAsString()); + } + +} diff --git a/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/ControllerEssTimeOfUseTariffDischarge.java b/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/ControllerEssTimeOfUseTariffDischarge.java index f33cbc04078..92ab6a67512 100644 --- a/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/ControllerEssTimeOfUseTariffDischarge.java +++ b/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/ControllerEssTimeOfUseTariffDischarge.java @@ -17,6 +17,7 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { * Current state of the Time of use tariff discharge controller. */ STATE_MACHINE(Doc.of(StateMachine.values()) // + .persistencePriority(PersistencePriority.HIGH) // .text("Current state of the Controller")), /** @@ -38,6 +39,7 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { .text("Total consmption for the night")), QUARTERLY_PRICES(Doc.of(OpenemsType.FLOAT) // .unit(Unit.EUROS_PER_MEGAWATT_HOUR) // + .persistencePriority(PersistencePriority.HIGH) // .text("Price of the electricity for the current Hour")), REMAINING_CONSUMPTION(Doc.of(OpenemsType.DOUBLE) // .text("remaining consmption to charge from grid")), diff --git a/io.openems.edge.controller.evcs.fixactivepower/src/io/openems/edge/controller/evcs/fixactivepower/Config.java b/io.openems.edge.controller.evcs.fixactivepower/src/io/openems/edge/controller/evcs/fixactivepower/Config.java index 21e551c4c97..bee75cbf074 100644 --- a/io.openems.edge.controller.evcs.fixactivepower/src/io/openems/edge/controller/evcs/fixactivepower/Config.java +++ b/io.openems.edge.controller.evcs.fixactivepower/src/io/openems/edge/controller/evcs/fixactivepower/Config.java @@ -23,6 +23,9 @@ @AttributeDefinition(name = "Charge power [W]", description = "Fix value that should be charged") int power(); + @AttributeDefinition(name = "Update Frequency [s]", description = "Timeout to write the value to the EVCS. Recommended minimum: 60s or more for optimal settings.") + int updateFrequency() default 60; + String webconsole_configurationFactory_nameHint() default "Controller Electric Vehicle Charging Station: Fix Active Power [{id}]"; } diff --git a/io.openems.edge.controller.evcs.fixactivepower/src/io/openems/edge/controller/evcs/fixactivepower/ControllerEvcsFixActivePowerImpl.java b/io.openems.edge.controller.evcs.fixactivepower/src/io/openems/edge/controller/evcs/fixactivepower/ControllerEvcsFixActivePowerImpl.java index 23f815826df..8831b2c7262 100644 --- a/io.openems.edge.controller.evcs.fixactivepower/src/io/openems/edge/controller/evcs/fixactivepower/ControllerEvcsFixActivePowerImpl.java +++ b/io.openems.edge.controller.evcs.fixactivepower/src/io/openems/edge/controller/evcs/fixactivepower/ControllerEvcsFixActivePowerImpl.java @@ -26,8 +26,6 @@ public class ControllerEvcsFixActivePowerImpl extends AbstractOpenemsComponent implements ControllerEvcsFixActivePower, Controller, OpenemsComponent { - private static final int RUN_EVERY_MINUTES = 1; - @Reference private ComponentManager componentManager; @@ -56,18 +54,21 @@ protected void deactivate() { @Override public void run() throws OpenemsNamedException { - var now = LocalDateTime.now(this.componentManager.getClock()); + if (this.updateTimerExpired()) { + ManagedEvcs evcs = this.componentManager.getComponent(this.config.evcs_id()); + var powerLimit = this.config.power(); - // Execute only every ... minutes - if (this.lastRun.plusMinutes(RUN_EVERY_MINUTES).isAfter(now)) { - return; + evcs.setChargePowerLimit(powerLimit); } + } - ManagedEvcs evcs = this.componentManager.getComponent(this.config.evcs_id()); - - // set charge power - evcs.setChargePowerLimit(this.config.power()); - this.lastRun = now; + private boolean updateTimerExpired() { + var now = LocalDateTime.now(this.componentManager.getClock()); + var result = !this.lastRun.plusSeconds(this.config.updateFrequency()).isAfter(now); + if (result) { + this.lastRun = now; + } + return result; } } diff --git a/io.openems.edge.controller.evcs.fixactivepower/test/io/openems/edge/controller/evcs/fixactivepower/ControllerEvcsFixActivePowerImplTest.java b/io.openems.edge.controller.evcs.fixactivepower/test/io/openems/edge/controller/evcs/fixactivepower/ControllerEvcsFixActivePowerImplTest.java index 6f26c60b6e4..e3f8ed55368 100644 --- a/io.openems.edge.controller.evcs.fixactivepower/test/io/openems/edge/controller/evcs/fixactivepower/ControllerEvcsFixActivePowerImplTest.java +++ b/io.openems.edge.controller.evcs.fixactivepower/test/io/openems/edge/controller/evcs/fixactivepower/ControllerEvcsFixActivePowerImplTest.java @@ -18,6 +18,7 @@ public void test() throws Exception { .setId(CTRL_ID) // .setEvcsId(EVCS_ID) // .setPower(0) // + .setUpdateFrequency(1) // .build()); // ; } diff --git a/io.openems.edge.controller.evcs.fixactivepower/test/io/openems/edge/controller/evcs/fixactivepower/MyConfig.java b/io.openems.edge.controller.evcs.fixactivepower/test/io/openems/edge/controller/evcs/fixactivepower/MyConfig.java index 49ce93bebac..7714363efee 100644 --- a/io.openems.edge.controller.evcs.fixactivepower/test/io/openems/edge/controller/evcs/fixactivepower/MyConfig.java +++ b/io.openems.edge.controller.evcs.fixactivepower/test/io/openems/edge/controller/evcs/fixactivepower/MyConfig.java @@ -6,9 +6,10 @@ public class MyConfig extends AbstractComponentConfig implements Config { protected static class Builder { - private String id = null; + private String id; private String evcsId; private int power; + private int updateFrequency; private Builder() { } @@ -28,6 +29,11 @@ public Builder setPower(int power) { return this; } + public Builder setUpdateFrequency(int updateFrequency) { + this.updateFrequency = updateFrequency; + return this; + } + public MyConfig build() { return new MyConfig(this); } @@ -58,4 +64,9 @@ public String evcs_id() { public int power() { return this.builder.power; } + + @Override + public int updateFrequency() { + return this.builder.updateFrequency; + } } \ No newline at end of file diff --git a/io.openems.edge.controller.evcs/test/io/openems/edge/controller/evcs/ControllerEvcsImplTest.java b/io.openems.edge.controller.evcs/test/io/openems/edge/controller/evcs/ControllerEvcsImplTest.java index e6c09bc3007..d2ae71d58ca 100644 --- a/io.openems.edge.controller.evcs/test/io/openems/edge/controller/evcs/ControllerEvcsImplTest.java +++ b/io.openems.edge.controller.evcs/test/io/openems/edge/controller/evcs/ControllerEvcsImplTest.java @@ -170,7 +170,6 @@ public void wrongConfigParametersTest() throws Exception { @Test public void clusterTest() throws Exception { - final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, ZoneOffset.UTC); diff --git a/io.openems.edge.controller.io.heatingelement/src/io/openems/edge/controller/io/heatingelement/ControllerIoHeatingElement.java b/io.openems.edge.controller.io.heatingelement/src/io/openems/edge/controller/io/heatingelement/ControllerIoHeatingElement.java index 030045364f4..f9dc5e6e7d3 100644 --- a/io.openems.edge.controller.io.heatingelement/src/io/openems/edge/controller/io/heatingelement/ControllerIoHeatingElement.java +++ b/io.openems.edge.controller.io.heatingelement/src/io/openems/edge/controller/io/heatingelement/ControllerIoHeatingElement.java @@ -6,6 +6,7 @@ import static io.openems.common.types.OpenemsType.INTEGER; import static io.openems.common.types.OpenemsType.LONG; +import io.openems.common.channel.PersistencePriority; import io.openems.edge.common.channel.Doc; import io.openems.edge.controller.io.heatingelement.enums.Level; import io.openems.edge.controller.io.heatingelement.enums.Status; @@ -48,8 +49,10 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { TOTAL_PHASE_TIME(Doc.of(INTEGER)// .unit(SECONDS)), // FORCE_START_AT_SECONDS_OF_DAY(Doc.of(INTEGER)// - .unit(SECONDS)), - STATUS(Doc.of(Status.values())); // + .unit(SECONDS) // + .persistencePriority(PersistencePriority.HIGH)), + STATUS(Doc.of(Status.values()) // + .persistencePriority(PersistencePriority.HIGH)); // private final Doc doc; diff --git a/io.openems.edge.controller.io.heatpump.sgready/src/io/openems/edge/controller/io/heatpump/sgready/ControllerIoHeatPumpSgReady.java b/io.openems.edge.controller.io.heatpump.sgready/src/io/openems/edge/controller/io/heatpump/sgready/ControllerIoHeatPumpSgReady.java index 649581a1879..ef104cecbfa 100644 --- a/io.openems.edge.controller.io.heatpump.sgready/src/io/openems/edge/controller/io/heatpump/sgready/ControllerIoHeatPumpSgReady.java +++ b/io.openems.edge.controller.io.heatpump.sgready/src/io/openems/edge/controller/io/heatpump/sgready/ControllerIoHeatPumpSgReady.java @@ -15,8 +15,9 @@ public interface ControllerIoHeatPumpSgReady extends OpenemsComponent { public enum ChannelId implements io.openems.edge.common.channel.ChannelId { - STATUS(Doc.of(Status.values()). // - text("Current State")), // + STATUS(Doc.of(Status.values()) // + .persistencePriority(PersistencePriority.HIGH) // + .text("Current State")), // AWAITING_HYSTERESIS(Doc.of(OpenemsType.BOOLEAN)), // REGULAR_STATE_TIME(Doc.of(OpenemsType.LONG) // .unit(Unit.CUMULATED_SECONDS) // diff --git a/io.openems.edge.controller.io.heatpump.sgready/src/io/openems/edge/controller/io/heatpump/sgready/ControllerIoHeatPumpSgReadyImpl.java b/io.openems.edge.controller.io.heatpump.sgready/src/io/openems/edge/controller/io/heatpump/sgready/ControllerIoHeatPumpSgReadyImpl.java index 841f73b532d..0c43cc30560 100644 --- a/io.openems.edge.controller.io.heatpump.sgready/src/io/openems/edge/controller/io/heatpump/sgready/ControllerIoHeatPumpSgReadyImpl.java +++ b/io.openems.edge.controller.io.heatpump.sgready/src/io/openems/edge/controller/io/heatpump/sgready/ControllerIoHeatPumpSgReadyImpl.java @@ -21,9 +21,9 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.types.ChannelAddress; -import io.openems.edge.common.channel.IntegerReadChannel; import io.openems.edge.common.channel.StateChannel; import io.openems.edge.common.channel.WriteChannel; +import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.ComponentManager; import io.openems.edge.common.component.OpenemsComponent; @@ -92,6 +92,10 @@ private void activate(ComponentContext context, Config config) { private void modified(ComponentContext context, Config config) throws OpenemsNamedException { super.modified(context, config.id(), config.alias(), config.enabled()); this.config = config; + // reset channels + this._setGridActivePowerNotPresent(false); + this._setEssDischargePowerNotPresent(false); + this._setStateOfChargeNotPresent(false); } @Override @@ -141,6 +145,11 @@ public void run() throws OpenemsNamedException { */ private void modeAutomatic() throws IllegalArgumentException, OpenemsNamedException { + // Values to calculate the surplus/grid-buy power + final var gridActivePower = this.getGridActivePowerOrZero(); + final var soc = this.getEssSocOrZero(); + var essDischargePower = this.getEssDischargePowerOrZero(); + // Detect if hysteresis is active, depending on the minimum switching time if (this.lastStateChange.plusSeconds(this.config.minimumSwitchingTime()) .isAfter(Instant.now(this.componentManager.getClock()))) { @@ -149,11 +158,6 @@ private void modeAutomatic() throws IllegalArgumentException, OpenemsNamedExcept } this._setAwaitingHysteresis(false); - // Values to calculate the surplus/grid-buy power - var gridActivePower = this.getGridActivePowerOrZero(); - var soc = this.getEssSocOrZero(); - var essDischargePower = this.getEssDischargePowerOrZero(); - // We are only interested in discharging, not charging essDischargePower = essDischargePower < 0 ? 0 : essDischargePower; @@ -222,35 +226,33 @@ private void modeManual() throws IllegalArgumentException, OpenemsNamedException } private int getEssDischargePowerOrZero() { - return this.getChannelValueOrZeroAndSetStateChannel(this.sum.getEssDischargePowerChannel(), + return this.getChannelValueOrZeroAndSetStateChannel(this.sum.getEssDischargePower(), this.getEssDischargePowerNotPresentChannel()); } private int getEssSocOrZero() { - return this.getChannelValueOrZeroAndSetStateChannel(this.sum.getEssSocChannel(), + return this.getChannelValueOrZeroAndSetStateChannel(this.sum.getEssSoc(), this.getStateOfChargeNotPresentChannel()); } private int getGridActivePowerOrZero() { - return this.getChannelValueOrZeroAndSetStateChannel(this.sum.getGridActivePowerChannel(), + return this.getChannelValueOrZeroAndSetStateChannel(this.sum.getGridActivePower(), this.getGridActivePowerNotPresentChannel()); } /** - * Get the IntegerReadChannel value or 0 if not present - Sets also the - * according state channel depending on the channel. + * Get the Channel value or 0 if not present - Sets also the according state + * channel depending on the channel. * - * @param channel Channel that value should be read. + * @param value Channel value * @param stateChannel Referring StateChannel that will be set if the value is * not present. * @return Current channel value as int. */ - private int getChannelValueOrZeroAndSetStateChannel(IntegerReadChannel channel, StateChannel stateChannel) { - var channelOptional = channel.getNextValue().asOptional(); - - if (channelOptional.isPresent()) { + private int getChannelValueOrZeroAndSetStateChannel(Value value, StateChannel stateChannel) { + if (value.isDefined()) { stateChannel.setNextValue(false); - return channelOptional.get(); + return value.get(); } stateChannel.setNextValue(true); return 0; diff --git a/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadWrite.java b/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadWrite.java index 0511fd27ee7..bd569577c21 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadWrite.java +++ b/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadWrite.java @@ -1,5 +1,9 @@ package io.openems.edge.app.api; +import static io.openems.edge.core.appmanager.formly.builder.SelectBuilder.DEFAULT_COMPONENT_2_LABEL; +import static io.openems.edge.core.appmanager.formly.builder.SelectBuilder.DEFAULT_COMPONENT_2_VALUE; +import static io.openems.edge.core.appmanager.formly.enums.InputType.NUMBER; + import java.util.EnumMap; import org.osgi.service.cm.ConfigurationAdmin; @@ -26,14 +30,13 @@ import io.openems.edge.core.appmanager.AppDescriptor; import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; -import io.openems.edge.core.appmanager.JsonFormlyUtil; -import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder.Type; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; import io.openems.edge.core.appmanager.TranslationUtil; import io.openems.edge.core.appmanager.dependency.DependencyDeclaration; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; /** * Describes a App for ReadWrite Modbus/TCP Api. @@ -90,9 +93,8 @@ public AppAssistant getAppAssistant(Language language) { TranslationUtil.getTranslation(bundle, "App.Api.apiTimeout.description")) // .setDefaultValue(60) // .isRequired(true) // - .setInputType(Type.NUMBER) // - .setMin(30) // - .setMax(120) // + .setInputType(NUMBER) // + .setMin(0) // .build()) .add(JsonFormlyUtil.buildSelect(Property.COMPONENT_IDS) // .isMulti(true) // @@ -102,8 +104,7 @@ public AppAssistant getAppAssistant(Language language) { .setDescription(TranslationUtil.getTranslation(bundle, this.getAppId() + ".componentIds.description")) // .setOptions(this.componentManager.getEnabledComponentsOfType(ModbusSlave.class), // - JsonFormlyUtil.SelectBuilder.DEFAULT_COMPONENT_2_LABEL, // - JsonFormlyUtil.SelectBuilder.DEFAULT_COMPONENT_2_VALUE) + DEFAULT_COMPONENT_2_LABEL, DEFAULT_COMPONENT_2_VALUE) .setDefaultValue(JsonUtils.buildJsonArray() // .add("_sum") // .build()) // diff --git a/io.openems.edge.core/src/io/openems/edge/app/api/MqttApi.java b/io.openems.edge.core/src/io/openems/edge/app/api/MqttApi.java index feb65f5cd8a..54abb880741 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/api/MqttApi.java +++ b/io.openems.edge.core/src/io/openems/edge/app/api/MqttApi.java @@ -1,5 +1,7 @@ package io.openems.edge.app.api; +import static io.openems.edge.core.appmanager.formly.enums.InputType.PASSWORD; + import java.util.EnumMap; import org.osgi.service.cm.ConfigurationAdmin; @@ -25,13 +27,12 @@ import io.openems.edge.core.appmanager.AppDescriptor; import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; -import io.openems.edge.core.appmanager.JsonFormlyUtil; -import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder.Type; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; import io.openems.edge.core.appmanager.TranslationUtil; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; /** * Describes a App for MQTT Api. @@ -92,7 +93,7 @@ public AppAssistant getAppAssistant(Language language) { .setDescription(TranslationUtil.getTranslation(bundle, this.getAppId() + ".Password.description")) // .isRequired(true) // - .setInputType(Type.PASSWORD) // + .setInputType(PASSWORD) // .build()) // .add(JsonFormlyUtil.buildInput(Property.CLIENT_ID) // .setLabel(TranslationUtil.getTranslation(bundle, this.getAppId() + ".EdgeId.label")) // diff --git a/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadOnly.java b/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadOnly.java index c2c0862e6c4..9b1658bac6c 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadOnly.java +++ b/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadOnly.java @@ -28,13 +28,13 @@ import io.openems.edge.core.appmanager.AppDescriptor; import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; -import io.openems.edge.core.appmanager.JsonFormlyUtil; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; import io.openems.edge.core.appmanager.Type; import io.openems.edge.core.appmanager.Type.Parameter.BundleParameter; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; /** * Describes a App for ReadOnly Rest JSON Api. diff --git a/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadWrite.java b/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadWrite.java index 79fbb333e88..28571454f88 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadWrite.java +++ b/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadWrite.java @@ -1,5 +1,7 @@ package io.openems.edge.app.api; +import static io.openems.edge.core.appmanager.formly.enums.InputType.NUMBER; + import java.util.EnumMap; import org.osgi.service.cm.ConfigurationAdmin; @@ -25,14 +27,13 @@ import io.openems.edge.core.appmanager.AppDescriptor; import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; -import io.openems.edge.core.appmanager.JsonFormlyUtil; -import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder.Type; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; import io.openems.edge.core.appmanager.TranslationUtil; import io.openems.edge.core.appmanager.dependency.DependencyDeclaration; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; /** * Describes a App for ReadWrite Rest JSON Api. @@ -85,9 +86,9 @@ public AppAssistant getAppAssistant(Language language) { .setLabel(TranslationUtil.getTranslation(bundle, "App.Api.apiTimeout.label")) // .setDescription( TranslationUtil.getTranslation(bundle, "App.Api.apiTimeout.description")) // - .setInputType(Type.NUMBER) // + .setInputType(NUMBER) // .setDefaultValue(60) // - .setMin(30) // + .setMin(0) // .isRequired(true) // .build()) .build()) diff --git a/io.openems.edge.core/src/io/openems/edge/app/common/props/CommonProps.java b/io.openems.edge.core/src/io/openems/edge/app/common/props/CommonProps.java index 5803ef0d56d..ba4c29b5d52 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/common/props/CommonProps.java +++ b/io.openems.edge.core/src/io/openems/edge/app/common/props/CommonProps.java @@ -1,10 +1,19 @@ package io.openems.edge.app.common.props; +import java.util.stream.Stream; + +import io.openems.common.OpenemsConstants; +import io.openems.common.utils.JsonUtils; import io.openems.edge.core.appmanager.AppDef; -import io.openems.edge.core.appmanager.JsonFormlyUtil; +import io.openems.edge.core.appmanager.AppDef.FieldValuesSupplier; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; +import io.openems.edge.core.appmanager.TranslationUtil; +import io.openems.edge.core.appmanager.Type.Parameter; import io.openems.edge.core.appmanager.Type.Parameter.BundleParameter; +import io.openems.edge.core.appmanager.Type.Parameter.BundleProvider; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; +import io.openems.edge.core.appmanager.formly.enums.DisplayType; public final class CommonProps { @@ -17,9 +26,9 @@ private CommonProps() { * * @return the {@link AppDef} */ - public static final AppDef defaultDef() { - return AppDef.of() // - .setTranslationBundleSupplier(BundleParameter::getBundle); + public static final AppDef defaultDef() { + return AppDef.of() // + .setTranslationBundleSupplier(BundleProvider::bundle); } /** @@ -27,11 +36,80 @@ public static final AppDef defaultDef() { * * @return the {@link AppDef} */ - public static final AppDef alias() { - return CommonProps.defaultDef() // + public static final AppDef alias() { + return AppDef.copyOfGeneric(defaultDef(), def -> def // .setTranslatedLabel("alias") // .setDefaultValueToAppName() // - .setField(JsonFormlyUtil::buildInputFromNameable); + .setField(JsonFormlyUtil::buildInputFromNameable)); + } + + /** + * Creates a {@link AppDef} for a installation hint. Only displays the text of + * the supplier with a checkbox to accept these conditions. Also does not safe + * the value. + * + * @param the type of the {@link OpenemsApp} + * @param the type of the {@link Nameable} + * @param the type of the {@link Parameter} + * @param firstText the first text to ensure that there is at least one element + * @param otherTexts the additional texts of the conditions to accept + * @return the {@link AppDef} + */ + @SafeVarargs + public static final AppDef installationHint(// + final FieldValuesSupplier firstText, // + final FieldValuesSupplier... otherTexts // + ) { + return AppDef.copyOfGeneric(CommonProps.defaultDef(), def -> { + def.setTranslatedLabel("installationHint.label") // + .setField(JsonFormlyUtil::buildFieldGroupFromNameable, (app, property, l, parameter, field) -> { + field.setPopupInput(property, DisplayType.BOOLEAN); + final var fields = JsonUtils.buildJsonArray(); + final var stream = Stream.>builder() // + .add(firstText); + + for (int i = 0; i < otherTexts.length; i++) { + stream.add(otherTexts[i]); + } + stream.build().forEach(t -> { + fields.add(JsonFormlyUtil.buildText() // + .setText(t.get(app, property, l, parameter)) // + .build()); + }); + fields.add(JsonFormlyUtil.buildCheckboxFromNameable(property) // + .isRequired(true) // + .requireTrue(l) // + .setLabel(TranslationUtil.getTranslation(parameter.bundle, "acceptCondition.label")) // + .build()); + field.setFieldGroup(fields.build()); + }); + def.setAllowedToSave(false); + }); + } + + /** + * Creates a installation hint to warn the user that the current app is not an + * official app from the company of this edge. This can be used for apps which + * are in a early beta testing stage. + * + * @param the type of the {@link OpenemsApp} + * @param the type of the {@link Nameable} + * @param the type of the {@link Parameter} + * @return the {@link AppDef} + */ + public static final AppDef installationHintOfUnofficialApp() { + return AppDef.copyOfGeneric(installationHint(// + (app, property, l, parameter) -> TranslationUtil.getTranslation(parameter.bundle, + "unofficialAppWarning.text1"), // + (app, property, l, parameter) -> TranslationUtil.getTranslation(parameter.bundle, + "unofficialAppWarning.text2")), + def -> def.setAutoGenerateField(!OpenemsConstants.MANUFACTURER.equals("OpenEMS Association e.V."))); } } diff --git a/io.openems.edge.core/src/io/openems/edge/app/common/props/CommunicationProps.java b/io.openems.edge.core/src/io/openems/edge/app/common/props/CommunicationProps.java index bc950a321a0..72697b13be4 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/common/props/CommunicationProps.java +++ b/io.openems.edge.core/src/io/openems/edge/app/common/props/CommunicationProps.java @@ -1,27 +1,61 @@ package io.openems.edge.app.common.props; +import static io.openems.edge.app.common.props.CommonProps.defaultDef; +import static io.openems.edge.core.appmanager.formly.enums.InputType.NUMBER; +import static io.openems.edge.core.appmanager.formly.enums.Validation.IP; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Optional; + +import com.google.gson.JsonPrimitive; + +import io.openems.common.utils.JsonUtils; +import io.openems.edge.app.enums.ModbusType; +import io.openems.edge.app.enums.OptionsFactory; import io.openems.edge.core.appmanager.AppDef; -import io.openems.edge.core.appmanager.JsonFormlyUtil; +import io.openems.edge.core.appmanager.ComponentManagerSupplier; +import io.openems.edge.core.appmanager.ComponentUtilSupplier; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; -import io.openems.edge.core.appmanager.Type.Parameter.BundleParameter; +import io.openems.edge.core.appmanager.TranslationUtil; +import io.openems.edge.core.appmanager.Type.Parameter.BundleProvider; +import io.openems.edge.core.appmanager.formly.Case; +import io.openems.edge.core.appmanager.formly.DefaultValueOptions; +import io.openems.edge.core.appmanager.formly.Exp; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; +import io.openems.edge.core.appmanager.formly.expression.StringExpression; +import io.openems.edge.core.appmanager.formly.expression.Variable; public final class CommunicationProps { private CommunicationProps() { } + /** + * Creates a {@link AppDef} for a {@link ModbusType}. + * + * @return the {@link AppDef} + */ + public static final AppDef modbusType() { + return AppDef.copyOfGeneric(defaultDef(), def -> def // + .setTranslatedLabel("communication.modbusIntegrationType") // + .setDefaultValue(ModbusType.TCP) // + .setField(JsonFormlyUtil::buildSelectFromNameable, (app, property, l, parameter, field) -> field + .setOptions(OptionsFactory.of(ModbusType.class), l))); + } + /** * Creates a {@link AppDef} for a ip-address. * * @return the {@link AppDef} */ - public static final AppDef ip() { - return CommonProps.defaultDef() // - .setTranslatedLabel("ipAddress") // + public static final AppDef ip() { + return AppDef.copyOfGeneric(defaultDef(), def -> def // + .setTranslatedLabel("communication.ipAddress") // .setDefaultValue("192.168.178.85") // .setField(JsonFormlyUtil::buildInputFromNameable, (app, prop, l, param, f) -> // - f.setValidation(JsonFormlyUtil.InputBuilder.Validation.IP)); + f.setValidation(IP))); } /** @@ -29,14 +63,14 @@ public static final AppDef ip() { * * @return the {@link AppDef} */ - public static final AppDef port() { - return CommonProps.defaultDef() // - .setTranslatedLabel("port") // - .setTranslatedDescription("port.description") // + public static final AppDef port() { + return AppDef.copyOfGeneric(defaultDef(), def -> def // + .setTranslatedLabel("communication.port") // + .setTranslatedDescription("communication.port.description") // .setDefaultValue(502) // .setField(JsonFormlyUtil::buildInputFromNameable, (app, prop, l, param, f) -> // - f.setInputType(JsonFormlyUtil.InputBuilder.Type.NUMBER) // - .setMin(0)); + f.setInputType(NUMBER) // + .setMin(0))); } /** @@ -44,13 +78,158 @@ public static final AppDef port() { * * @return the {@link AppDef} */ - public static final AppDef modbusUnitId() { - return CommonProps.defaultDef() // - .setTranslatedLabel("modbusUnitId") // - .setTranslatedDescription("modbusUnitId.description") // + public static final AppDef modbusUnitId() { + return AppDef.copyOfGeneric(defaultDef(), def -> def // + .setTranslatedLabel("communication.modbusUnitId") // + .setTranslatedDescription("communication.modbusUnitId.description") // + .setDefaultValue(0) // .setField(JsonFormlyUtil::buildInputFromNameable, (app, prop, l, param, f) -> // - f.setInputType(JsonFormlyUtil.InputBuilder.Type.NUMBER) // - .setMin(0)); + f.setInputType(NUMBER) // + .setMin(0) // + .onlyPositiveNumbers())); + } + + /** + * Creates a {@link AppDef} group of a {@link ComponentProps#pickModbusId()} and + * a {@link CommunicationProps#modbusUnitId()} to check if the current selected + * modbus unit id already got selected. + * + * @param the type of the app + * @param the type of the properties + * @param the type of the parameters + * @param modbusId the {@link Nameable} of the modbus id + * @param modbusIdDef the {@link AppDef} of the modbus id + * @param modbusUnitId the {@link Nameable} of the modbus unit id + * @param modbusUnitIdDef the {@link AppDef} of the modbus unit id + * @return the {@link AppDef} + */ + public static final // + AppDef modbusGroup(// + PROP modbusId, // + AppDef modbusIdDef, // + PROP modbusUnitId, // + AppDef modbusUnitIdDef // + ) { + return modbusGroup(modbusId, modbusIdDef, modbusUnitId, modbusUnitIdDef, null); + } + + /** + * Creates a {@link AppDef} group of a {@link ComponentProps#pickModbusId()} and + * a {@link CommunicationProps#modbusUnitId()} to check if the current selected + * modbus unit id already got selected. + * + * @param the type of the app + * @param the type of the properties + * @param the type of the parameters + * @param modbusId the {@link Nameable} of the modbus id + * @param modbusIdDef the {@link AppDef} of the modbus id + * @param modbusUnitId the {@link Nameable} of the modbus unit id + * @param modbusUnitIdDef the {@link AppDef} of the modbus unit id + * @param connectionModubsType if set add a default value of 1 if + * {@link ModbusType#TCP} is selected + * @return the {@link AppDef} + */ + public static final // + AppDef modbusGroup(// + PROP modbusId, // + AppDef modbusIdDef, // + PROP modbusUnitId, // + AppDef modbusUnitIdDef, // + PROP connectionModubsType // + ) { + return AppDef.copyOfGeneric(CommonProps.defaultDef(), def -> { + def.setField(JsonFormlyUtil::buildFieldGroupFromNameable, (app, property, l, parameter, field) -> { + final var componentManager = app.getComponentManager(); + final var componentUtil = app.getComponentUtil(); + + final var components = componentManager.getEdgeConfig() + .getComponentIdsByFactory("Bridge.Modbus.Serial"); + + final var cases = new ArrayList(); + + final var defaultValue = Optional.ofNullable(modbusUnitIdDef.getDefaultValue()) // + .map(f -> f.get(app, modbusUnitId, l, parameter)) // + .flatMap(JsonUtils::getAsOptionalInt) // + .orElse(0); + + for (var componentId : components) { + final var alreadyUsedIds = componentUtil.getUsedModbusUnitIds(componentId); + if (alreadyUsedIds.length == 0) { + continue; + } + + final var usedIdStrings = Arrays.stream(alreadyUsedIds) // + .distinct() // + .sorted() // + .mapToObj(Exp::staticValue) // + .toArray(Variable[]::new); + + // checks if the current modbus component is selected + final var expression = Exp.currentModelValue(modbusId).notEqual(Exp.staticValue(componentId)) // + // checks if the current selected unit id is not the initial value + .or(Exp.initialModelValue(modbusUnitId) // + .equal(Exp.currentValue(modbusUnitId))) // + // checks if the current selected unit id is not already used + .or(Exp.array(usedIdStrings).every(v -> v.notEqual(Exp.currentModelValue(modbusUnitId)))); + + final var filteredArray = Exp.array(usedIdStrings) // + .filter(v -> v.notEqual(Exp.initialModelValue(modbusUnitId)) // + .or(Exp.initialModelValue(modbusId) // + .notEqual(Exp.currentModelValue(modbusId)))); + + final var message = Exp.ifElse(filteredArray.length().equal(Exp.staticValue(1)), // + StringExpression.of(TranslationUtil.getTranslation(parameter.bundle(), + "communication.modbusUnitId.alreadTaken.singular", + filteredArray.join(", ").insideTranslation(), componentId)), // + StringExpression.of(TranslationUtil.getTranslation(parameter.bundle(), + "communication.modbusUnitId.alreadTaken.plural", + filteredArray.join(", ").insideTranslation(), componentId))); + + field.setCustomValidation(componentId, expression, message, modbusUnitId); + + cases.add(new Case(new JsonPrimitive(componentId), + new JsonPrimitive(getFirstPossibleValue(defaultValue, alreadyUsedIds)))); + } + + final var modbusIdFieldBuilder = modbusIdDef.getField().get(app, modbusId, l, parameter); + final var overridenDefaultForModbusId = cases.stream() // + .filter(c -> c.value().equals(modbusIdFieldBuilder.getDefaultValue())) // + .findFirst() // + .map(Case::defaultValue) // + .orElse(new JsonPrimitive(defaultValue)); + + field.setFieldGroup(JsonUtils.buildJsonArray() // + .add(modbusIdFieldBuilder.build()) + .add(modbusUnitIdDef.getField().get(app, modbusUnitId, l, parameter) // + .onlyIf(!cases.isEmpty(), b -> { + if (connectionModubsType != null) { + b.setDefaultValueCases( + new DefaultValueOptions(modbusId, cases.toArray(Case[]::new)), + new DefaultValueOptions(connectionModubsType, + new Case(new JsonPrimitive(ModbusType.TCP.name()), + new JsonPrimitive(1)))); + } else { + b.setDefaultValueCases( + new DefaultValueOptions(modbusId, cases.toArray(Case[]::new))); + } + }) // + .setDefaultValue(overridenDefaultForModbusId) // + .build()) + .build()) // + .hideKey(); + }); + }); + } + + private static int getFirstPossibleValue(int start, int[] skipValues) { + for (int j = 0; j < skipValues.length; j++) { + if (start == skipValues[j]) { + return getFirstPossibleValue(start + 1, skipValues); + } + } + return start; } } diff --git a/io.openems.edge.core/src/io/openems/edge/app/common/props/ComponentProps.java b/io.openems.edge.core/src/io/openems/edge/app/common/props/ComponentProps.java new file mode 100644 index 00000000000..efad9816f02 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/app/common/props/ComponentProps.java @@ -0,0 +1,255 @@ +package io.openems.edge.app.common.props; + +import static io.openems.edge.core.appmanager.formly.builder.SelectBuilder.DEFAULT_COMPONENT_2_LABEL; +import static io.openems.edge.core.appmanager.formly.builder.SelectBuilder.DEFAULT_COMPONENT_2_VALUE; + +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; + +import com.google.gson.JsonNull; +import com.google.gson.JsonPrimitive; + +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.core.appmanager.AppDef; +import io.openems.edge.core.appmanager.AppManagerUtilSupplier; +import io.openems.edge.core.appmanager.ComponentManagerSupplier; +import io.openems.edge.core.appmanager.ComponentUtilSupplier; +import io.openems.edge.core.appmanager.Nameable; +import io.openems.edge.core.appmanager.OpenemsApp; +import io.openems.edge.core.appmanager.Type.Parameter.BundleProvider; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; +import io.openems.edge.ess.api.ManagedSymmetricEss; +import io.openems.edge.meter.api.ElectricityMeter; +import io.openems.edge.meter.api.MeterType; + +/** + * Static method collection for {@link AppDef AppDefs} for selecting different + * kinds of {@link OpenemsComponent OpenemsComponents}. + */ +public final class ComponentProps { + + /** + * Creates a {@link AppDef} for a input to select a enabled + * {@link OpenemsComponent}. + * + * @param the type of the {@link OpenemsApp} + * @return the {@link AppDef} + */ + public static // + AppDef pickComponentId() { + return pickComponentId(app -> { + final var componentManager = app.getComponentManager(); + return componentManager.getEnabledComponents(); + }); + } + + /** + * Creates a {@link AppDef} for a input to select a enabled + * {@link OpenemsComponent} of the given type. + * + * @param the type of the {@link OpenemsApp} + * @param the type of the component + * @param type the type of the {@link OpenemsComponent OpenemsComponents} + * @return the {@link AppDef} + */ + public static // + AppDef pickComponentId(// + final Class type // + ) { + return pickComponentId(type, null); + } + + /** + * Creates a {@link AppDef} for a input to select a enabled + * {@link OpenemsComponent} of the given type and filtered by the given filter. + * + * @param the type of the {@link OpenemsApp} + * @param the type of the component + * @param type the type of the {@link OpenemsComponent OpenemsComponents} + * @param filter the filter of the components + * @return the {@link AppDef} + */ + public static // + AppDef pickComponentId(// + final Class type, // + final Predicate filter // + ) { + return pickComponentId(app -> { + final var componentUtil = app.getComponentUtil(); + var components = componentUtil.getEnabledComponentsOfType(type).stream(); + if (filter != null) { + components = components.filter(filter); + } + return components.toList(); + }); + } + + private static AppDef pickComponentId(// + final Function> supplyComponents // + ) { + return AppDef.copyOfGeneric(CommonProps.defaultDef(), def -> def // + .setLabel("Component-ID") // + .setField(JsonFormlyUtil::buildSelectFromNameable, (app, property, l, parameter, field) -> { + field.setOptions(supplyComponents.apply(app), // + DEFAULT_COMPONENT_2_LABEL, DEFAULT_COMPONENT_2_VALUE); + }).setDefaultValue((app, property, l, parameter) -> { + final var components = supplyComponents.apply(app); + if (components.isEmpty()) { + return JsonNull.INSTANCE; + } + return new JsonPrimitive(components.get(0).id()); + })); + } + + /** + * Creates a {@link AppDef} for a input to select a enabled + * {@link OpenemsComponent} with the given starting id. + * + * @param the type of the {@link OpenemsApp} + * @param startingId the starting id of the components e. g. evcs for all evcss: + * evcs0, evcs1, ... + * @param filter the filter to apply on the component list + * @return the {@link AppDef} + */ + public static // + AppDef pickComponentId(// + String startingId, // + final Predicate filter // + ) { + return pickComponentId(app -> { + final var componentUtil = app.getComponentUtil(); + final var components = componentUtil.getEnabledComponentsOfStartingId(startingId); + if (filter == null) { + return components; + } + return components.stream() // + .filter(filter) // + .toList(); + }); + } + + /** + * Creates a {@link AppDef} for a input to select a enabled + * {@link OpenemsComponent} with the given starting id. + * + * @param the type of the {@link OpenemsApp} + * @param startingId the starting id of the components e. g. evcs for all evcss: + * evcs0, evcs1, ... + * @return the {@link AppDef} + */ + public static // + AppDef pickComponentId(// + String startingId // + ) { + return pickComponentId(startingId, null); + } + + /** + * Creates a {@link AppDef} for a input to select a {@link ManagedSymmetricEss}. + * + * @param the type of the {@link OpenemsApp} + * @return the {@link AppDef} + */ + public static // + AppDef pickManagedSymmetricEssId() { + return ComponentProps.pickComponentId(ManagedSymmetricEss.class) // + .setTranslatedLabel("essId.label") // + .setTranslatedDescription("essId.description"); + } + + /** + * Creates a {@link AppDef} for a input to select an {@link ElectricityMeter}. + * + * @param the type of the {@link OpenemsApp} + * @return the {@link AppDef} + */ + public static // + AppDef pickElectricityMeterId() { + return ComponentProps.pickComponentId(ElectricityMeter.class) // + .setTranslatedLabel("meterId.label") // + .setTranslatedDescription("meterId.description"); + } + + /** + * Creates a {@link AppDef} for a input to select an {@link ElectricityMeter} + * with the {@link MeterType} {@link MeterType#GRID}. + * + * @param the type of the {@link OpenemsApp} + * @return the {@link AppDef} + */ + public static // + AppDef pickElectricityGridMeterId() { + return ComponentProps + .pickComponentId(ElectricityMeter.class, + meter -> meter.getMeterType() == MeterType.GRID) // + .setTranslatedLabel("gridMeterId.label") // + .setTranslatedDescription("gridMeterId.description"); + } + + /** + * Creates a {@link AppDef} for a input to select a {@link OpenemsComponent} + * with the starting id 'modbus'. + * + * @param the type of the {@link OpenemsApp} + * @param filter the filter to apply on the component list + * @return the {@link AppDef} + */ + public static // + AppDef pickModbusId(// + final Predicate filter // + ) { + return AppDef.copyOfGeneric(ComponentProps.pickComponentId("modbus", filter), def -> { + def.setTranslatedLabel("communication.modbusId") // + .setTranslatedDescription("communication.modbusId.description"); + final var oldDefaultValue = def.getDefaultValue(); + def.setDefaultValue((app, property, l, parameter) -> { + if (PropsUtil.isHomeInstalled(app.getAppManagerUtil())) { + return new JsonPrimitive("modbus1"); + } + + return oldDefaultValue.get(app, property, l, parameter); + }); + }); + } + + /** + * Creates a {@link AppDef} for a input to select a {@link OpenemsComponent} + * with the starting id 'modbus'. + * + * @param the type of the {@link OpenemsApp} + * @return the {@link AppDef} + */ + public static // + AppDef pickModbusId() { + return pickModbusId(null); + } + + /** + * Creates a {@link AppDef} for a input to select a {@link OpenemsComponent} + * with the starting id 'modbus' and the factoryId 'Bridge.Modbus.Serial'. + * + * @param the type of the {@link OpenemsApp} + * @return the {@link AppDef} + */ + public static // + AppDef pickSerialModbusId() { + return pickModbusId(c -> c.serviceFactoryPid().equals("Bridge.Modbus.Serial")); + } + + /** + * Creates a {@link AppDef} for a input to select a {@link OpenemsComponent} + * with the starting id 'modbus' and the factoryId 'Bridge.Modbus.Tcp'. + * + * @param the type of the {@link OpenemsApp} + * @return the {@link AppDef} + */ + public static // + AppDef pickTcpModbusId() { + return pickModbusId(c -> c.serviceFactoryPid().equals("Bridge.Modbus.Tcp")); + } + + private ComponentProps() { + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/app/common/props/PropsUtil.java b/io.openems.edge.core/src/io/openems/edge/app/common/props/PropsUtil.java new file mode 100644 index 00000000000..26093b1fe77 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/app/common/props/PropsUtil.java @@ -0,0 +1,21 @@ +package io.openems.edge.app.common.props; + +import io.openems.edge.app.integratedsystem.FeneconHome; +import io.openems.edge.core.appmanager.AppManagerUtil; + +public final class PropsUtil { + + private PropsUtil() { + } + + /** + * Checks if a {@link FeneconHome} is installed. + * + * @param util the {@link AppManagerUtil} to get the installed instances + * @return true if a {@link FeneconHome} is installed otherwise false + */ + public static boolean isHomeInstalled(AppManagerUtil util) { + return !util.getInstantiatedAppsOfApp("App.FENECON.Home").isEmpty(); + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/app/enums/MeterType.java b/io.openems.edge.core/src/io/openems/edge/app/enums/MeterType.java new file mode 100644 index 00000000000..5f19565f139 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/app/enums/MeterType.java @@ -0,0 +1,28 @@ +package io.openems.edge.app.enums; + +import io.openems.common.session.Language; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; +import io.openems.edge.core.appmanager.TranslationUtil; + +/** + * Copy of {@link io.openems.edge.meter.api.MeterType}. + */ +public enum MeterType implements TranslatableEnum { + PRODUCTION("App.Meter.production"), // + GRID("App.Meter.gridMeter"), // + CONSUMPTION_METERED("App.Meter.consumtionMeter"), // + ; + + private final String translationKey; + + private MeterType(String translationKey) { + this.translationKey = translationKey; + } + + @Override + public String getTranslation(Language l) { + final var bundle = AbstractOpenemsApp.getTranslationBundle(l); + return TranslationUtil.getTranslation(bundle, this.translationKey); + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/app/enums/ModbusType.java b/io.openems.edge.core/src/io/openems/edge/app/enums/ModbusType.java new file mode 100644 index 00000000000..72c441dc9b3 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/app/enums/ModbusType.java @@ -0,0 +1,24 @@ +package io.openems.edge.app.enums; + +import io.openems.common.session.Language; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; +import io.openems.edge.core.appmanager.TranslationUtil; + +public enum ModbusType implements TranslatableEnum { + TCP("communication.modbusIntegrationType.tcp"), // + RTU("communication.modbusIntegrationType.rtu"), // + ; + + private final String translationKey; + + private ModbusType(String translation) { + this.translationKey = translation; + } + + @Override + public String getTranslation(Language l) { + final var bundle = AbstractOpenemsApp.getTranslationBundle(l); + return TranslationUtil.getTranslation(bundle, this.translationKey); + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/app/enums/OptionsFactory.java b/io.openems.edge.core/src/io/openems/edge/app/enums/OptionsFactory.java index 988069f1494..0830a219621 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/enums/OptionsFactory.java +++ b/io.openems.edge.core/src/io/openems/edge/app/enums/OptionsFactory.java @@ -32,7 +32,7 @@ public interface OptionsFactory { */ public static OptionsFactory of(TranslatableEnum[] values) { return l -> Arrays.stream(values) // - .map(e -> Map.entry(e.getTranslation(l), e.name())) // + .map(e -> Map.entry(e.getTranslation(l), e.getValue())) // .collect(Collectors.toSet()); } @@ -41,10 +41,14 @@ public static OptionsFactory of(TranslatableEnum[] values) { * * @param the type of the enum {@link Class} * @param enumClass the {@link Class EnumClass} to get the values from. + * @param exclude the constants to exclude * @return the {@link OptionsFactory} */ - public static & TranslatableEnum> OptionsFactory of(Class enumClass) { - return of(enumClass.getEnumConstants()); + @SafeVarargs + public static & TranslatableEnum> OptionsFactory of(Class enumClass, T... exclude) { + return of(Arrays.stream(enumClass.getEnumConstants()) // + .filter(t -> !Arrays.stream(exclude).anyMatch(o -> t == o)) // + .toArray(TranslatableEnum[]::new)); } /** diff --git a/io.openems.edge.core/src/io/openems/edge/app/enums/TranslatableEnum.java b/io.openems.edge.core/src/io/openems/edge/app/enums/TranslatableEnum.java index a4272b68943..71f19037dd1 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/enums/TranslatableEnum.java +++ b/io.openems.edge.core/src/io/openems/edge/app/enums/TranslatableEnum.java @@ -13,4 +13,15 @@ public interface TranslatableEnum extends Nameable { */ public String getTranslation(Language language); + /** + * Gets the value which is being selected in the {@link OptionsFactory}. If you + * choose to override this be careful when calling a method which gets the enum + * value of a string name and not this value. + * + * @return the value of this {@link TranslatableEnum} + */ + public default String getValue() { + return this.name(); + } + } \ No newline at end of file diff --git a/io.openems.edge.core/src/io/openems/edge/app/ess/PrepareBatteryExtension.java b/io.openems.edge.core/src/io/openems/edge/app/ess/PrepareBatteryExtension.java index 2d14266a630..ea29fcdebb4 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/ess/PrepareBatteryExtension.java +++ b/io.openems.edge.core/src/io/openems/edge/app/ess/PrepareBatteryExtension.java @@ -27,7 +27,6 @@ import io.openems.edge.core.appmanager.AppDescriptor; import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; -import io.openems.edge.core.appmanager.JsonFormlyUtil; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; @@ -36,6 +35,7 @@ import io.openems.edge.core.appmanager.Type; import io.openems.edge.core.appmanager.Type.Parameter; import io.openems.edge.core.appmanager.Type.Parameter.BundleParameter; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; /** * Describes a prepare battery extension app. diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/AbstractEvcsApp.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/AbstractEvcsApp.java deleted file mode 100644 index bc27dc6616d..00000000000 --- a/io.openems.edge.core/src/io/openems/edge/app/evcs/AbstractEvcsApp.java +++ /dev/null @@ -1,43 +0,0 @@ -package io.openems.edge.app.evcs; - -import java.util.List; - -import org.osgi.service.cm.ConfigurationAdmin; -import org.osgi.service.component.ComponentContext; - -import com.google.common.collect.Lists; - -import io.openems.common.types.EdgeConfig; -import io.openems.common.utils.JsonUtils; -import io.openems.edge.common.component.ComponentManager; -import io.openems.edge.core.appmanager.AbstractEnumOpenemsApp; -import io.openems.edge.core.appmanager.ComponentUtil; -import io.openems.edge.core.appmanager.Nameable; -import io.openems.edge.core.appmanager.OpenemsAppCategory; - -public abstract class AbstractEvcsApp & Nameable> - extends AbstractEnumOpenemsApp { - - protected AbstractEvcsApp(ComponentManager componentManager, ComponentContext componentContext, - ConfigurationAdmin cm, ComponentUtil componentUtil) { - super(componentManager, componentContext, cm, componentUtil); - } - - @Override - public final OpenemsAppCategory[] getCategories() { - return new OpenemsAppCategory[] { OpenemsAppCategory.EVCS }; - } - - protected final List getComponents(String evcsId, String alias, String factorieId, String ip, - String ctrlEvcsId) { - return Lists.newArrayList(// - new EdgeConfig.Component(evcsId, alias, factorieId, JsonUtils.buildJsonObject() // - .addPropertyIfNotNull("ip", ip) // - .build()), // - new EdgeConfig.Component(ctrlEvcsId, "Data", "Controller.Evcs", JsonUtils.buildJsonObject() // - .addProperty("evcs.id", evcsId) // - .build())// - ); - } - -} diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/AlpitronicEvcs.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/AlpitronicEvcs.java new file mode 100644 index 00000000000..d4711ee495b --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/AlpitronicEvcs.java @@ -0,0 +1,330 @@ +package io.openems.edge.app.evcs; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Map; +import java.util.OptionalInt; +import java.util.ResourceBundle; +import java.util.TreeMap; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.osgi.service.cm.ConfigurationAdmin; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; +import io.openems.common.types.EdgeConfig; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.app.common.props.CommonProps; +import io.openems.edge.app.common.props.CommunicationProps; +import io.openems.edge.app.evcs.AlpitronicEvcs.ParentProperty; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; +import io.openems.edge.core.appmanager.AbstractOpenemsAppWithProps; +import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.AppDef; +import io.openems.edge.core.appmanager.AppDescriptor; +import io.openems.edge.core.appmanager.ComponentUtil; +import io.openems.edge.core.appmanager.ConfigurationTarget; +import io.openems.edge.core.appmanager.InterfaceConfiguration; +import io.openems.edge.core.appmanager.Nameable; +import io.openems.edge.core.appmanager.OpenemsApp; +import io.openems.edge.core.appmanager.OpenemsAppCardinality; +import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.TranslationUtil; +import io.openems.edge.core.appmanager.Type; +import io.openems.edge.core.appmanager.Type.Parameter; +import io.openems.edge.core.appmanager.Type.Parameter.BundleParameter; +import io.openems.edge.core.appmanager.formly.Case; +import io.openems.edge.core.appmanager.formly.DefaultValueOptions; +import io.openems.edge.core.appmanager.formly.Exp; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; +import io.openems.edge.core.appmanager.formly.enums.Wrappers; +import io.openems.edge.core.appmanager.formly.expression.StringExpression; + +/** + * Describes a Alpitronic evcs app. + * + *
    +  {
    +    "appId":"App.Evcs.Alpitronic",
    +    "alias":"Alpitronic Ladestation",
    +    "instanceId": UUID,
    +    "image": base64,
    +    "properties":{
    +      "MODBUS_ID": "modbus0",
    +      "NUMBER_OF_CONNECTORS": 4,
    +      "IP":"192.168.25.11",
    +      "CP_ALIAS_[1-3]": "Alpitronic Ladestation - Ladepunkt [1-3]",
    +      "EVCS_ID_[0-3]": "evcs[0-3]",
    +      "CTRL_EVCS_ID[0-3]": "ctrlEvcs[0-3]"
    +    },
    +    "appDescriptor": {
    +    	"websiteUrl": {@link AppDescriptor#getWebsiteUrl()}
    +    }
    +  }
    + * 
    + */ +@Component(name = "App.Evcs.Alpitronic") +public class AlpitronicEvcs extends + AbstractOpenemsAppWithProps implements OpenemsApp { + + public static interface ParentProperty extends Type { + + } + + private static final class ParentPropertyImpl extends + Type.AbstractType implements ParentProperty { + + public ParentPropertyImpl(String name, + AppDef def) { + super(name, def, Parameter.functionOf(AbstractOpenemsApp::getTranslationBundle)); + } + + } + + public enum Property implements ParentProperty { + // Component-IDs + MODBUS_ID(AppDef.componentId("modbus0")), // + // Properties + NUMBER_OF_CONNECTORS(AppDef.copyOfGeneric(EvcsProps.numberOfChargePoints(4))), + IP(AppDef.copyOfGeneric(CommunicationProps.ip()) // + .setDefaultValue("192.168.1.100")), // + MAX_HARDWARE_POWER_ACCEPT_PROPERTY(AppDef.of() // + .setAllowedToSave(false)), // + MAX_HARDWARE_POWER(AppDef.copyOfGeneric(// + EvcsProps.clusterMaxHardwarePower(MAX_HARDWARE_POWER_ACCEPT_PROPERTY), def -> { + def.wrapField((app, property, l, parameter, field) -> { + final var existingEvcs = EvcsProps.getEvcsComponents(app.getComponentUtil()); + + if (existingEvcs.isEmpty()) { + field.onlyShowIf(Exp.currentModelValue(NUMBER_OF_CONNECTORS) // + .greaterThanEqual(Exp.staticValue(2))); + return; + } + final var expressionForSingleUpdate = existingEvcs.stream().map(OpenemsComponent::id) // + .map(Exp::staticValue) // + .collect(Exp.toArrayExpression()) + .every(v -> v.notEqual(Exp.currentModelValue(Nameable.of(EVCS_ID.apply(0))))); + + field.onlyShowIf(Exp.currentModelValue(NUMBER_OF_CONNECTORS) // + .greaterThanEqual(Exp.staticValue(2)) // + .or(expressionForSingleUpdate)); + }); // + })), // + ; + + private static AppDef chargePointAlias(int number) { + return AppDef.of(AlpitronicEvcs.class) // + .setTranslatedLabel("App.Evcs.chargingStation.label", number) // + .setDefaultValue((app, property, l, parameter) -> { + return new JsonPrimitive(TranslationUtil.getTranslation(parameter.bundle(), + "App.Evcs.Alpitronic.chargingStation.label", number)); + }) // + .setField(JsonFormlyUtil::buildFieldGroupFromNameable, (app, property, l, parameter, field) -> { + field.addWrapper(Wrappers.PANEL) // + .hideKey() // + .onlyIf(number == 1, + b -> b.setLabelExpression(Exp.ifElse( + Exp.currentModelValue(Property.NUMBER_OF_CONNECTORS) // + .equal(Exp.staticValue(1)), + StringExpression.of(""), + StringExpression.of(TranslationUtil.getTranslation(parameter.bundle(), + "App.Evcs.chargingStation.label", number))))) + .onlyShowIf(Exp.currentModelValue(Property.NUMBER_OF_CONNECTORS) + .greaterThanEqual(Exp.staticValue(number))) // + .setFieldGroup(JsonUtils.buildJsonArray() // + .add(CommonProps.alias().getField().get(app, property, l, parameter) // + .setDefaultValue(TranslationUtil.getTranslation(parameter.bundle(), + "App.Evcs.Alpitronic.chargingStation.label", number)) // + .onlyIf(number == 1, b -> { + b.setDefaultValueCases(new DefaultValueOptions( + Property.NUMBER_OF_CONNECTORS, // + buildFirstAliasDefaultCases(app, l, parameter.bundle()))); + }).build()) // + .build()); + }); + } + + private static Case[] buildFirstAliasDefaultCases(AlpitronicEvcs app, Language l, ResourceBundle bundle) { + return IntStream.rangeClosed(1, MAX_NUMBER_OF_CHARGEPOINTS) // + .mapToObj(value -> { + if (value == 1) { + return new Case(1, app.getName(l)); + } else { + return new Case(value, TranslationUtil.getTranslation(bundle, + "App.Evcs.Alpitronic.chargingStation.label", 1)); + } + }).toArray(Case[]::new); + } + + private final AppDef def; + + private Property(AppDef def) { + this.def = def; + } + + @Override + public Type self() { + return this; + } + + @Override + public AppDef def() { + return this.def; + } + + @Override + public Function, BundleParameter> getParamter() { + return Parameter.functionOf(AbstractOpenemsApp::getTranslationBundle); + } + + } + + private static final int MAX_NUMBER_OF_CHARGEPOINTS = 4; + private static final IntFunction EVCS_ALIAS = value -> "CP_ALIAS_" + value; + private static final IntFunction EVCS_ID = value -> "EVCS_ID_" + value; + private static final IntFunction CTRL_EVCS_ID = value -> "CTRL_EVCS_ID_" + value; + + private final Map chargePointsDef = new TreeMap<>(); + + @Activate + public AlpitronicEvcs(// + @Reference ComponentManager componentManager, // + ComponentContext componentContext, // + @Reference ConfigurationAdmin cm, // + @Reference ComponentUtil componentUtil // + ) { + super(componentManager, componentContext, cm, componentUtil); + for (int i = 0; i < MAX_NUMBER_OF_CHARGEPOINTS; i++) { + final var name = EVCS_ALIAS.apply(i); + this.chargePointsDef.put(name, + new ParentPropertyImpl(i == 0 ? "ALIAS" : name, Property.chargePointAlias(i + 1))); + final var evcsComponentId = EVCS_ID.apply(i); + this.chargePointsDef.put(evcsComponentId, + new ParentPropertyImpl(evcsComponentId, AppDef.componentId("evcs0"))); + final var evcsControllerComponentId = CTRL_EVCS_ID.apply(i); + this.chargePointsDef.put(evcsControllerComponentId, + new ParentPropertyImpl(evcsControllerComponentId, AppDef.componentId("ctrlEvcs0"))); + } + } + + @Override + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appPropertyConfigurationFactory() { + return (t, p, l) -> { + final var translationBundle = AbstractOpenemsApp.getTranslationBundle(l); + final var controllerAlias = TranslationUtil.getTranslation(translationBundle, "App.Evcs.controller.alias"); + + var maxHardwarePowerPerPhase = OptionalInt.empty(); + if (p.containsKey(Property.MAX_HARDWARE_POWER)) { + maxHardwarePowerPerPhase = OptionalInt.of(this.getInt(p, Property.MAX_HARDWARE_POWER)); + } + + final var components = new ArrayList(); + final var schedulerIds = new ArrayList(); + final var addedEvcsIds = new ArrayList(); + + final var ip = this.getString(p, l, Property.IP); + final var numberOfConnectors = this.getInt(p, Property.NUMBER_OF_CONNECTORS); + final var modbusId = this.getId(t, p, Property.MODBUS_ID); + + for (int i = 0; i < numberOfConnectors; i++) { + final var aliasDef = this.chargePointsDef.get(EVCS_ALIAS.apply(i)); + final var evcsIdDef = this.chargePointsDef.get(EVCS_ID.apply(i)); + final var evcsId = this.getId(t, p, evcsIdDef); + final var evcsCtrlIdDef = this.chargePointsDef.get(CTRL_EVCS_ID.apply(i)); + final var ctrlEvcsId = this.getId(t, p, evcsCtrlIdDef); + + schedulerIds.add(ctrlEvcsId); + addedEvcsIds.add(evcsId); + + components.add(new EdgeConfig.Component(evcsId, this.getString(p, l, aliasDef), + "Evcs.AlpitronicHypercharger", JsonUtils.buildJsonObject() // + .addProperty("connector", "SLOT_" + i) // + .addProperty("modbus.id", modbusId) // + .build())); + components.add(new EdgeConfig.Component(ctrlEvcsId, controllerAlias, "Controller.Evcs", + JsonUtils.buildJsonObject() // + .addProperty("evcs.id", evcsId) // + .build())); + } + + components.add(new EdgeConfig.Component(modbusId, + TranslationUtil.getTranslation(translationBundle, "App.Evcs.Alpitronic.modbus.alias"), + "Bridge.Modbus.Tcp", JsonUtils.buildJsonObject() // + .addProperty("ip", ip) // + .build())); + + schedulerIds.add("ctrlBalancing0"); + + var ips = Lists.newArrayList(// + new InterfaceConfiguration("eth0") // + // range from 192.168.1.96 - 192.168.1.111 + .addIp("Evcs", "192.168.1.97/28") // + ); + + return new AppConfiguration(// + components, // + schedulerIds, // + ip.startsWith("192.168.1.") ? ips : null, // + EvcsCluster.dependency(t, this.componentManager, this.componentUtil, maxHardwarePowerPerPhase, + addedEvcsIds.stream().toArray(String[]::new)) // + ); + }; + } + + @Override + public AppDescriptor getAppDescriptor() { + return AppDescriptor.create() // + .build(); + } + + @Override + public OpenemsAppCardinality getCardinality() { + return OpenemsAppCardinality.MULTIPLE; + } + + @Override + public OpenemsAppCategory[] getCategories() { + return new OpenemsAppCategory[] { OpenemsAppCategory.EVCS }; + } + + @Override + protected AlpitronicEvcs getApp() { + return this; + } + + @Override + protected ParentProperty[] propertyValues() { + final var builder = ImmutableList.builder() // + .addAll(Arrays.stream(Property.values()).filter(p -> Stream.of(// + Property.MAX_HARDWARE_POWER_ACCEPT_PROPERTY, // + Property.MAX_HARDWARE_POWER // + ).allMatch(t -> p != t)).toList()); + + builder.addAll(this.chargePointsDef.values()); + + builder.add(Property.MAX_HARDWARE_POWER_ACCEPT_PROPERTY); + builder.add(Property.MAX_HARDWARE_POWER); + + return builder.build().toArray(ParentProperty[]::new); + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/DezonyEvcs.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/DezonyEvcs.java new file mode 100644 index 00000000000..b30a495d6a2 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/DezonyEvcs.java @@ -0,0 +1,185 @@ +package io.openems.edge.app.evcs; + +import java.util.Map; +import java.util.OptionalInt; +import java.util.function.Function; + +import org.osgi.service.cm.ConfigurationAdmin; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +import com.google.common.collect.Lists; +import com.google.gson.JsonElement; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; +import io.openems.common.types.EdgeConfig; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.app.common.props.CommonProps; +import io.openems.edge.app.common.props.CommunicationProps; +import io.openems.edge.app.evcs.DezonyEvcs.Property; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; +import io.openems.edge.core.appmanager.AbstractOpenemsAppWithProps; +import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.AppDef; +import io.openems.edge.core.appmanager.AppDescriptor; +import io.openems.edge.core.appmanager.ComponentUtil; +import io.openems.edge.core.appmanager.ConfigurationTarget; +import io.openems.edge.core.appmanager.Nameable; +import io.openems.edge.core.appmanager.OpenemsApp; +import io.openems.edge.core.appmanager.OpenemsAppCardinality; +import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.OpenemsAppStatus; +import io.openems.edge.core.appmanager.TranslationUtil; +import io.openems.edge.core.appmanager.Type; +import io.openems.edge.core.appmanager.Type.Parameter; +import io.openems.edge.core.appmanager.Type.Parameter.BundleParameter; + +/** + * Describes a dezony IQ evcs App. + * + *
    +  {
    +    "appId":"App.Evcs.Dezony",
    +    "alias":"dezoney IQ Ladestation",
    +    "instanceId": UUID,
    +    "image": base64,
    +    "properties":{
    +      "EVCS_ID": "evcs0",
    +      "CTRL_EVCS_ID": "ctrlEvcs0",
    +      "IP":"192.168.25.11",
    +      "PORT":"5000"
    +    },
    +    "appDescriptor": {
    +    	"websiteUrl": {@link AppDescriptor#getWebsiteUrl()}
    +    }
    +  }
    + * 
    + */ +@Component(name = "App.Evcs.Dezony") +public class DezonyEvcs extends AbstractOpenemsAppWithProps + implements OpenemsApp { + + public enum Property implements Type, Nameable { + // Component-IDs + EVCS_ID(AppDef.componentId("evcs0")), // + CTRL_EVCS_ID(AppDef.componentId("ctrlEvcs0")), // + // Properties + ALIAS(CommonProps.alias()), // + IP(AppDef.copyOfGeneric(CommunicationProps.ip(), // + def -> def.setDefaultValue("192.168.50.88") // + .wrapField((app, property, l, parameter, field) -> field.isRequired(true)))), // + PORT(AppDef.copyOfGeneric(CommunicationProps.port(), // + def -> def.setDefaultValue(5000) // + .wrapField((app, property, l, parameter, field) -> field.isRequired(true)))), // + MAX_HARDWARE_POWER_ACCEPT_PROPERTY(AppDef.of() // + .setAllowedToSave(false)), // + MAX_HARDWARE_POWER(EvcsProps.clusterMaxHardwarePowerSingleCp(MAX_HARDWARE_POWER_ACCEPT_PROPERTY, EVCS_ID)), // + UNOFFICIAL_APP_WARNING(CommonProps.installationHintOfUnofficialApp()), // + ; + + private final AppDef def; + + private Property(AppDef def) { + this.def = def; + } + + @Override + public Type self() { + return this; + } + + @Override + public AppDef def() { + return this.def; + } + + @Override + public Function, BundleParameter> getParamter() { + return Parameter.functionOf(AbstractOpenemsApp::getTranslationBundle); + } + + } + + @Activate + public DezonyEvcs(@Reference ComponentManager componentManager, ComponentContext componentContext, + @Reference ConfigurationAdmin cm, @Reference ComponentUtil componentUtil) { + super(componentManager, componentContext, cm, componentUtil); + } + + @Override + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appPropertyConfigurationFactory() { + return (t, p, l) -> { + final var controllerAlias = TranslationUtil.getTranslation(AbstractOpenemsApp.getTranslationBundle(l), + "App.Evcs.controller.alias"); + + // values the user enters + final var alias = this.getString(p, l, Property.ALIAS); + final var ip = this.getString(p, Property.IP); + final var port = this.getInt(p, Property.PORT); + + // values which are being auto generated by the appmanager + final var evcsId = this.getId(t, p, Property.EVCS_ID); + final var ctrlEvcsId = this.getId(t, p, Property.CTRL_EVCS_ID); + + var maxHardwarePowerPerPhase = OptionalInt.empty(); + if (p.containsKey(Property.MAX_HARDWARE_POWER)) { + maxHardwarePowerPerPhase = OptionalInt.of(this.getInt(p, Property.MAX_HARDWARE_POWER)); + } + + final var components = Lists.newArrayList(// + new EdgeConfig.Component(evcsId, alias, "Evcs.Dezony", JsonUtils.buildJsonObject() // + .addProperty("ip", ip) // + .addProperty("port", port) // + .build()), // + new EdgeConfig.Component(ctrlEvcsId, controllerAlias, "Controller.Evcs", JsonUtils.buildJsonObject() // + .addProperty("evcs.id", evcsId) // + .build())// + ); + + return new AppConfiguration(// + components, // + Lists.newArrayList(ctrlEvcsId, "ctrlBalancing0"), // + null, // + EvcsCluster.dependency(t, this.componentManager, this.componentUtil, maxHardwarePowerPerPhase, + evcsId) // + ); + }; + } + + @Override + public AppDescriptor getAppDescriptor() { + return AppDescriptor.create() // + .build(); + } + + @Override + public OpenemsAppCardinality getCardinality() { + return OpenemsAppCardinality.MULTIPLE; + } + + @Override + public OpenemsAppCategory[] getCategories() { + return new OpenemsAppCategory[] { OpenemsAppCategory.EVCS }; + } + + @Override + protected OpenemsAppStatus getStatus() { + return OpenemsAppStatus.BETA; + } + + @Override + protected DezonyEvcs getApp() { + return this; + } + + @Override + protected Property[] propertyValues() { + return Property.values(); + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsCluster.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsCluster.java index 3b07dd3e685..0fa0b6db699 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsCluster.java +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsCluster.java @@ -1,7 +1,19 @@ package io.openems.edge.app.evcs; +import static io.openems.edge.core.appmanager.formly.builder.SelectBuilder.DEFAULT_COMPONENT_2_LABEL; +import static io.openems.edge.core.appmanager.formly.builder.SelectBuilder.DEFAULT_COMPONENT_2_VALUE; +import static io.openems.edge.core.appmanager.formly.enums.InputType.NUMBER; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.OptionalInt; +import java.util.TreeSet; import java.util.function.Function; +import java.util.stream.Collectors; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; @@ -9,29 +21,40 @@ import org.osgi.service.component.annotations.Reference; import com.google.common.collect.Lists; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import io.openems.common.channel.Unit; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.function.ThrowingTriFunction; import io.openems.common.session.Language; +import io.openems.common.session.Role; import io.openems.common.types.EdgeConfig; import io.openems.common.utils.JsonUtils; +import io.openems.edge.app.common.props.CommonProps; import io.openems.edge.app.evcs.EvcsCluster.Property; import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.core.appmanager.AbstractOpenemsApp; import io.openems.edge.core.appmanager.AbstractOpenemsAppWithProps; import io.openems.edge.core.appmanager.AppConfiguration; import io.openems.edge.core.appmanager.AppDef; import io.openems.edge.core.appmanager.AppDescriptor; +import io.openems.edge.core.appmanager.ComponentManagerSupplier; import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; -import io.openems.edge.core.appmanager.JsonFormlyUtil; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; import io.openems.edge.core.appmanager.Type; import io.openems.edge.core.appmanager.Type.Parameter.BundleParameter; +import io.openems.edge.core.appmanager.dependency.DependencyDeclaration; +import io.openems.edge.core.appmanager.dependency.DependencyDeclaration.AppDependencyConfig; +import io.openems.edge.core.appmanager.dependency.DependencyUtil; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; /** * Describes a evcs cluster. @@ -64,23 +87,34 @@ public static enum Property implements Type { f.setOptions( - app.componentUtil.getEnabledComponentsOfStartingId("evcs").stream() + app.getComponentUtil().getEnabledComponentsOfStartingId("evcs").stream() .filter(t -> !t.id().startsWith("evcsCluster")).toList(), - JsonFormlyUtil.SelectBuilder.DEFAULT_COMPONENT_2_LABEL, - JsonFormlyUtil.SelectBuilder.DEFAULT_COMPONENT_2_VALUE) // + DEFAULT_COMPONENT_2_LABEL, DEFAULT_COMPONENT_2_VALUE) // .isRequired(true) // .isMulti(true); }) // - .bidirectional(EVCS_CLUSTER_ID, "evcs.ids", a -> a.componentManager)) // + .setDefaultValue((app, property, l, parameter) -> new JsonArray()) // + .bidirectional(EVCS_CLUSTER_ID, "evcs.ids", a -> a.componentManager)), // + MAX_HARDWARE_POWER_LIMIT_PER_PHASE(AppDef.copyOfGeneric(CommonProps.defaultDef(), def -> def // + .setTranslatedLabelWithAppPrefix(".maxChargeFromGrid.short.label") // + .setTranslatedDescriptionWithAppPrefix(".maxChargeFromGrid.description") // + .setDefaultValue(7000) // + .appendIsAllowedToEdit(AppDef.ofLeastRole(Role.INSTALLER)) // + .setField(JsonFormlyUtil::buildInput, (app, property, l, parameter, field) -> field.setInputType(NUMBER) // + .setMin(0) // + .isRequired(true) // + .setUnit(Unit.WATT, l)) // + .bidirectional(EVCS_CLUSTER_ID, "hardwarePowerLimitPerPhase", + ComponentManagerSupplier::getComponentManager, AppDef.multiplyWith(3)))), // ; - private final AppDef def; + private final AppDef def; - private Property(AppDef def) { + private Property(AppDef def) { this.def = def; } @@ -90,7 +124,7 @@ public Property self() { } @Override - public AppDef def() { + public AppDef def() { return this.def; } @@ -109,16 +143,18 @@ public EvcsCluster(@Reference ComponentManager componentManager, ComponentContex @Override protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appPropertyConfigurationFactory() { return (t, p, l) -> { + final var evcsClusterId = this.getId(t, p, Property.EVCS_CLUSTER_ID); - var evcsClusterId = this.getId(t, p, Property.EVCS_CLUSTER_ID); - - var alias = this.getString(p, l, Property.ALIAS); - var ids = this.getJsonArray(p, Property.EVCS_IDS); + final var alias = this.getString(p, l, Property.ALIAS); + final var ids = this.getJsonArray(p, Property.EVCS_IDS); + final var hardwarePowerLimitPerPhase = this.getInt(p, Property.MAX_HARDWARE_POWER_LIMIT_PER_PHASE); var components = Lists.newArrayList(// new EdgeConfig.Component(evcsClusterId, alias, "Evcs.Cluster.PeakShaving", JsonUtils.buildJsonObject() // .add("evcs.ids", ids) // + .addProperty("hardwarePowerLimitPerPhase", + hardwarePowerLimitPerPhase / EvcsProps.NUMBER_OF_PHASES) // .build()) // ); @@ -152,4 +188,152 @@ protected EvcsCluster getApp() { return this; } + /** + * Creates a dependency for a {@link EvcsCluster}. + * + * @param target the target of the configuration + * @param cm the {@link ComponentManager} + * @param componentUtil the {@link ComponentUtil} + * @param hardwarePowerLimitPerPhase the + * {@link Property#MAX_HARDWARE_POWER_LIMIT_PER_PHASE} + * @param addedEvcsIdsArray the ids of the evcss which got added + * @return a {@link List} of the {@link DependencyDeclaration} + * @throws OpenemsNamedException on error + */ + public static List dependency(// + final ConfigurationTarget target, // + final ComponentManager cm, // + final ComponentUtil componentUtil, // + final OptionalInt hardwarePowerLimitPerPhase, // + final String... addedEvcsIdsArray // + ) throws OpenemsNamedException { + return dependency(target, cm, componentUtil, hardwarePowerLimitPerPhase, Collections.emptyList(), + addedEvcsIdsArray); + } + + /** + * Creates a dependency for a {@link EvcsCluster}. + * + * @param target the target of the configuration + * @param cm the {@link ComponentManager} + * @param componentUtil the {@link ComponentUtil} + * @param hardwarePowerLimitPerPhase the + * {@link Property#MAX_HARDWARE_POWER_LIMIT_PER_PHASE} + * @param removeEvcsIds the evcs ids which got removed + * @param addedEvcsIdsArray the ids of the evcss which got added + * @return a {@link List} of the {@link DependencyDeclaration} + * @throws OpenemsNamedException on error + */ + public static List dependency(// + final ConfigurationTarget target, // + final ComponentManager cm, // + final ComponentUtil componentUtil, // + final OptionalInt hardwarePowerLimitPerPhase, // + final List removeEvcsIds, // + final String... addedEvcsIdsArray // + ) throws OpenemsNamedException { + final var clusterComponents = new ArrayList(); + // this is currently not type safe so for example any component can have the id + // evcs% but in order to get the type in a test we would have to instantiate a + // evcs component and because the interface is in a different package it would + // be a bad practice to have from the core package a dependency to the interface + // package and there is currently no way to set the natures of a + // OpenemsComponent for a test component. + final var evcsComponents = componentUtil.getEnabledComponentsOfStartingId("evcs") // + .stream().filter(t -> { + final var isCluster = t.id().startsWith("evcsCluster"); + if (isCluster) { + clusterComponents.add(t); + } + return !isCluster; + }).collect(Collectors.toList()); + + final var potentialIdsInCluster = new TreeSet(); + potentialIdsInCluster.addAll(evcsComponents.stream() // + .map(OpenemsComponent::id).toList()); + if (target == ConfigurationTarget.DELETE) { + potentialIdsInCluster.removeAll(Arrays.asList(addedEvcsIdsArray)); + } else { + potentialIdsInCluster.addAll(Arrays.asList(addedEvcsIdsArray)); + } + potentialIdsInCluster.removeAll(removeEvcsIds); + + final var shouldDependencyExist = potentialIdsInCluster.size() > 1; + + // still add dependency when deleting the app so that it will also be deleted + if (target != ConfigurationTarget.DELETE && !shouldDependencyExist) { + return Collections.emptyList(); + } + + // if there is exactly one cluster component overwrite its configuration + final var clusterId = clusterComponents.size() == 1 ? clusterComponents.get(0).id() : "evcsCluster0"; + + final var existingEvcsIds = cm.getEdgeConfig().getComponent(clusterId) // + .flatMap(t -> t.getProperty("evcs.ids")) // + .flatMap(JsonUtils::getAsOptionalJsonArray) // + .orElse(new JsonArray()); + + // create dependency + final var existingCluster = DependencyUtil.getAppWhichHasComponent(cm, clusterId); + + final var dependencyBuilder = AppDependencyConfig.create(); + if (existingCluster != null) { + dependencyBuilder.setSpecificInstanceId(existingCluster.instanceId); + } else { + dependencyBuilder.setAppId("App.Evcs.Cluster"); + } + final var newEvcsIds = JsonUtils.stream(existingEvcsIds) // + .map(t -> { + if (!t.isJsonPrimitive()) { + return null; + } + if (!t.getAsJsonPrimitive().isString()) { + return null; + } + return t.getAsString(); + }) // + .filter(Objects::nonNull) // + .collect(Collectors.toList()); + + final var evcsIds = evcsComponents.stream() // + .map(OpenemsComponent::id) // + .collect(Collectors.toList()); + final var addedEvcsIds = Arrays.stream(addedEvcsIdsArray) // + .collect(Collectors.toList()); + if (target != ConfigurationTarget.DELETE) { + evcsIds.addAll(addedEvcsIds); + } + for (var id : evcsIds) { + if (newEvcsIds.stream().anyMatch(t -> t.equals(id))) { + continue; + } + newEvcsIds.add(id); + } + if (target == ConfigurationTarget.DELETE) { + newEvcsIds.removeAll(addedEvcsIds); + } + + newEvcsIds.removeAll(removeEvcsIds); + + dependencyBuilder.setProperties(JsonUtils + .buildJsonObject(existingCluster != null ? existingCluster.properties.deepCopy() : new JsonObject()) // + .add(Property.EVCS_IDS.name(), newEvcsIds.stream() // + .map(JsonPrimitive::new) // + .collect(JsonUtils.toJsonArray())) // + .addProperty(Property.EVCS_CLUSTER_ID.name(), clusterId) // + .onlyIf(hardwarePowerLimitPerPhase.isPresent(), + c -> c.addProperty(Property.MAX_HARDWARE_POWER_LIMIT_PER_PHASE.name(), + hardwarePowerLimitPerPhase.getAsInt())) // + .build()); + + return Lists.newArrayList(new DependencyDeclaration("CLUSTER", // + DependencyDeclaration.CreatePolicy.IF_NOT_EXISTING, // + DependencyDeclaration.UpdatePolicy.ALWAYS, // + shouldDependencyExist ? DependencyDeclaration.DeletePolicy.IF_MINE + : DependencyDeclaration.DeletePolicy.ALWAYS, // + DependencyDeclaration.DependencyUpdatePolicy.ALLOW_ALL, // + DependencyDeclaration.DependencyDeletePolicy.NOT_ALLOWED, // + dependencyBuilder.build())); + } + } diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsProps.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsProps.java new file mode 100644 index 00000000000..258c66c960f --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsProps.java @@ -0,0 +1,185 @@ +package io.openems.edge.app.evcs; + +import static io.openems.edge.core.appmanager.formly.enums.InputType.NUMBER; + +import java.util.List; +import java.util.stream.IntStream; + +import com.google.gson.JsonPrimitive; + +import io.openems.common.channel.Unit; +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.session.Language; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.app.common.props.CommonProps; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.core.appmanager.AppDef; +import io.openems.edge.core.appmanager.AppManager; +import io.openems.edge.core.appmanager.AppManagerImpl; +import io.openems.edge.core.appmanager.ComponentManagerSupplier; +import io.openems.edge.core.appmanager.ComponentUtil; +import io.openems.edge.core.appmanager.ComponentUtilSupplier; +import io.openems.edge.core.appmanager.Nameable; +import io.openems.edge.core.appmanager.OpenemsApp; +import io.openems.edge.core.appmanager.TranslationUtil; +import io.openems.edge.core.appmanager.Type.Parameter.BundleProvider; +import io.openems.edge.core.appmanager.formly.Exp; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; +import io.openems.edge.core.appmanager.formly.builder.FieldGroupBuilder; +import io.openems.edge.core.appmanager.formly.enums.DisplayType; + +public final class EvcsProps { + + public static final int NUMBER_OF_PHASES = 3; + + private EvcsProps() { + } + + /** + * Creates a {@link AppDef} for selecting the number of charge points. + * + * @param maxValue the max number of charge points + * @return the {@link AppDef} + */ + public static AppDef numberOfChargePoints(// + final int maxValue // + ) { + return AppDef.copyOfGeneric(CommonProps.defaultDef(), def -> def // + .setTranslatedLabel("App.Evcs.numberOfChargingStations.label") // + .setDefaultValue(1) // + .setField(JsonFormlyUtil::buildSelectFromNameable, (app, property, l, parameter, field) -> // + field.setOptions(IntStream.rangeClosed(1, maxValue) // + .mapToObj(value -> value) // + .toList(), JsonPrimitive::new, JsonPrimitive::new))); + } + + private static void field(// + OpenemsApp app, // + Nameable property, // + Nameable acceptProperty, // + Language language, // + BundleProvider parameter, // + FieldGroupBuilder field // + ) { + field.hideKey(); + field.setPopupInput(property, DisplayType.NUMBER); + field.setFieldGroup(JsonUtils.buildJsonArray() // + .add(JsonFormlyUtil.buildText() // + .setText(TranslationUtil.getTranslation(parameter.bundle(), // + "App.Evcs.Cluster.maxGrid.text1")) + .build()) + .add(JsonFormlyUtil.buildText() // + .setText(TranslationUtil.getTranslation(parameter.bundle(), // + "App.Evcs.Cluster.maxGrid.text2")) + .build()) + .add(JsonFormlyUtil.buildInputFromNameable(property) // + .setLabel(TranslationUtil.getTranslation(parameter.bundle(), + "App.Evcs.Cluster.maxChargeFromGrid.short.label")) + .setInputType(NUMBER) // + .setMin(0) // + .isRequired(true) // + .setUnit(Unit.WATT, language) // + .build()) + .add(JsonFormlyUtil.buildText() // + .setText(TranslationUtil.getTranslation(parameter.bundle(), // + "App.Evcs.Cluster.maxGrid.text3")) + .build()) + .add(JsonFormlyUtil.buildCheckboxFromNameable(acceptProperty) // + .isRequired(true) // + .requireTrue(language) // + .setLabel(TranslationUtil.getTranslation(parameter.bundle(), "acceptCondition.label")) // + .build()) + .build()); + } + + /** + * Creates a {@link AppDef} for the + * {@link EvcsCluster.Property#MAX_HARDWARE_POWER_LIMIT_PER_PHASE}. + * + * @param the type of the {@link OpenemsApp} + * @param acceptProperty the property of the accept field + * @return the {@link AppDef} + */ + public static AppDef clusterMaxHardwarePower( + Nameable acceptProperty) { + return AppDef.copyOfGeneric(CommonProps.defaultDef(), def -> def // + .setTranslatedLabel("App.Evcs.Cluster.maxChargeFromGrid.label") // + .setAllowedToSave(false) // + .setIsAllowedToSee((app, property, l, parameter, user) -> { + final var componentManager = app.getComponentManager(); + if (isClusterInstalled(componentManager)) { + return false; + } + return true; + }) // + .setField(JsonFormlyUtil::buildFieldGroupFromNameable, (app, property, l, parameter, + field) -> field(app, property, acceptProperty, l, parameter, field))); + } + + /** + * Creates a {@link AppDef} for the + * {@link EvcsCluster.Property#MAX_HARDWARE_POWER_LIMIT_PER_PHASE} for a single + * charge point. + * + * @param the type of the {@link OpenemsApp} + * @param acceptProperty the property of the accept field + * @param evcsIdProperty the property of the evcs id + * @return the {@link AppDef} + */ + public static AppDef clusterMaxHardwarePowerSingleCp( + Nameable acceptProperty, // + Nameable evcsIdProperty // + ) { + return EvcsProps.clusterMaxHardwarePower(acceptProperty) // + .setIsAllowedToSee((app, property, l, parameter, user) -> { + final var componentManager = app.getComponentManager(); + if (isClusterInstalled(componentManager)) { + return false; + } + final var existingEvcs = getEvcsComponents(app.getComponentUtil()); + return !existingEvcs.isEmpty(); + }).wrapField((app, property, l, parameter, field) -> { + final var existingEvcs = EvcsProps.getEvcsComponents(app.getComponentUtil()); + if (existingEvcs.isEmpty()) { + return; + } + + final var expression = existingEvcs.stream().map(OpenemsComponent::id) // + .map(Exp::staticValue) // + .collect(Exp.toArrayExpression()) // + .every(v -> v.notEqual(Exp.currentModelValue(evcsIdProperty))); + + field.onlyShowIf(expression); + }); + } + + /** + * Gets the currently installed evcs components. + * + *

    + * Note: only checks if the component id starts with evcs it does not check the + * type of the component. + * + * @param componentUtil the {@link ComponentUtil} + * @return a list of the components + */ + public static List getEvcsComponents(ComponentUtil componentUtil) { + return componentUtil.getEnabledComponentsOfStartingId("evcs") // + .stream().filter(t -> !t.id().startsWith("evcsCluster")).toList(); + } + + private static final boolean isClusterInstalled(ComponentManager componentManager) { + try { + AppManagerImpl appManager = componentManager.getComponent(AppManager.SINGLETON_COMPONENT_ID); + if (appManager.getInstantiatedApps().stream() // + .anyMatch(t -> t.appId.equals("App.Evcs.Cluster"))) { + return true; + } + } catch (OpenemsNamedException e) { + e.printStackTrace(); + } + return false; + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/HardyBarthEvcs.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/HardyBarthEvcs.java index c9eeecedfb3..9fdba3b83d1 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/evcs/HardyBarthEvcs.java +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/HardyBarthEvcs.java @@ -1,8 +1,13 @@ package io.openems.edge.app.evcs; +import static io.openems.edge.core.appmanager.formly.enums.Wrappers.PANEL; + import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Map; +import java.util.OptionalInt; import java.util.function.Function; import org.osgi.service.cm.ConfigurationAdmin; @@ -27,6 +32,7 @@ import io.openems.edge.app.common.props.CommunicationProps; import io.openems.edge.app.evcs.HardyBarthEvcs.PropertyParent; import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.core.appmanager.AbstractOpenemsApp; import io.openems.edge.core.appmanager.AbstractOpenemsAppWithProps; import io.openems.edge.core.appmanager.AppConfiguration; @@ -35,13 +41,6 @@ import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; import io.openems.edge.core.appmanager.InterfaceConfiguration; -import io.openems.edge.core.appmanager.JsonFormlyUtil; -import io.openems.edge.core.appmanager.JsonFormlyUtil.Case; -import io.openems.edge.core.appmanager.JsonFormlyUtil.DefaultValueOptions; -import io.openems.edge.core.appmanager.JsonFormlyUtil.ExpressionBuilder; -import io.openems.edge.core.appmanager.JsonFormlyUtil.ExpressionBuilder.Operator; -import io.openems.edge.core.appmanager.JsonFormlyUtil.FormlyBuilder; -import io.openems.edge.core.appmanager.JsonFormlyUtil.Wrappers; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; @@ -50,6 +49,13 @@ import io.openems.edge.core.appmanager.Type; import io.openems.edge.core.appmanager.Type.Parameter; import io.openems.edge.core.appmanager.Type.Parameter.BundleParameter; +import io.openems.edge.core.appmanager.dependency.DependencyDeclaration; +import io.openems.edge.core.appmanager.formly.Case; +import io.openems.edge.core.appmanager.formly.DefaultValueOptions; +import io.openems.edge.core.appmanager.formly.Exp; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; +import io.openems.edge.core.appmanager.formly.builder.FormlyBuilder; +import io.openems.edge.core.appmanager.formly.expression.StringExpression; /** * Describes a Hardy Barth evcs App. @@ -91,35 +97,55 @@ public static enum Property implements PropertyParent { EVCS_ID_CP_2(AppDef.componentId("evcs0")), // CTRL_EVCS_ID_CP_2(AppDef.componentId("ctrlEvcs0")), // // Properties - NUMBER_OF_CHARGING_STATIONS(AppDef.of(HardyBarthEvcs.class) // - .setTranslatedLabelWithAppPrefix(".numberOfChargingStations.label") // - .setDefaultValue(1) // - .setField(JsonFormlyUtil::buildSelectFromNameable, (app, property, l, parameter, field) -> // - field.setOptions(Lists.newArrayList(1, 2), JsonPrimitive::new, JsonPrimitive::new))), // + // TODO maybe make this immutable after first installation? + NUMBER_OF_CHARGING_STATIONS(AppDef.copyOfGeneric(EvcsProps.numberOfChargePoints(2))), // WRAPPER_FIRST_CHARGE_POINT(AppDef.of(HardyBarthEvcs.class) // .setTranslatedLabel("App.Evcs.chargingStation.label", 1) .setField(JsonFormlyUtil::buildFieldGroupFromNameable, (app, property, l, parameter, field) -> { - field.addWrapper(Wrappers.PANEL) // + field.addWrapper(PANEL) // .setFieldGroup(SubPropertyFirstChargepoint.fields(app, l, parameter)) // - .setLabelExpression( - ExpressionBuilder.of(Property.NUMBER_OF_CHARGING_STATIONS, Operator.EQ, "1"), // - "", TranslationUtil.getTranslation(parameter.bundle, - "App.Evcs.chargingStation.label", 1)) + .setLabelExpression(Exp.ifElse( + Exp.currentModelValue(Property.NUMBER_OF_CHARGING_STATIONS) + .equal(Exp.staticValue(1)), + StringExpression.of(""), // + StringExpression.of(TranslationUtil.getTranslation(parameter.bundle, + "App.Evcs.chargingStation.label", 1)))) .hideKey(); // })), // WRAPPER_SECOND_CHARGE_POINT(AppDef.of(HardyBarthEvcs.class) // .setTranslatedLabel("App.Evcs.chargingStation.label", 2) .setField(JsonFormlyUtil::buildFieldGroupFromNameable, (app, property, l, parameter, field) -> { - field.addWrapper(Wrappers.PANEL) // + field.addWrapper(PANEL) // .setFieldGroup(SubPropertySecondChargepoint.fields(app, l, parameter)) // - .onlyShowIfValueEquals(NUMBER_OF_CHARGING_STATIONS, "2") // + .onlyShowIf(Exp.currentModelValue(NUMBER_OF_CHARGING_STATIONS) // + .equal(Exp.staticValue(2))) .hideKey(); })), // + MAX_HARDWARE_POWER_ACCEPT_PROPERTY(AppDef.of() // + .setAllowedToSave(false)), // + MAX_HARDWARE_POWER(AppDef.copyOfGeneric(EvcsProps.clusterMaxHardwarePower(MAX_HARDWARE_POWER_ACCEPT_PROPERTY), + def -> def // + .setDefaultValue(0) // + .wrapField((app, property, l, parameter, field) -> { + final var existingEvcs = EvcsProps.getEvcsComponents(app.componentUtil); + + if (existingEvcs.isEmpty()) { + field.onlyShowIf(Exp.currentModelValue(NUMBER_OF_CHARGING_STATIONS) // + .equal(Exp.staticValue(2))); + return; + } + field.onlyShowIf(Exp.currentModelValue(NUMBER_OF_CHARGING_STATIONS) // + .equal(Exp.staticValue(2)) // + .or(existingEvcs.stream().map(OpenemsComponent::id) // + .map(Exp::staticValue) // + .collect(Exp.toArrayExpression()) // + .every(i -> Exp.currentModelValue(EVCS_ID).notEqual(i)))); + }))), // ; - private final AppDef def; + private final AppDef def; - private Property(AppDef def) { + private Property(AppDef def) { this.def = def; } @@ -129,7 +155,7 @@ public Type self() { } @Override - public AppDef def() { + public AppDef def() { return this.def; } @@ -144,23 +170,23 @@ public enum SubPropertyFirstChargepoint implements PropertyParent { ALIAS(AppDef.copyOfGeneric(CommonProps.alias()) // .setAutoGenerateField(false) // .setDefaultValue((app, property, l, parameter) -> // - new JsonPrimitive(TranslationUtil.getTranslation(parameter.bundle, "App.Evcs.HardyBarth.alias.value", // - TranslationUtil.getTranslation(parameter.bundle, "right")))) // + new JsonPrimitive(TranslationUtil.getTranslation(parameter.bundle(), "App.Evcs.HardyBarth.alias.value", // + TranslationUtil.getTranslation(parameter.bundle(), "right")))) // .wrapField((app, property, l, parameter, field) -> field.isRequired(true) // .setDefaultValueCases(new DefaultValueOptions(Property.NUMBER_OF_CHARGING_STATIONS, // new Case(1, app.getName(l)), // - new Case(2, TranslationUtil.getTranslation(parameter.bundle, // + new Case(2, TranslationUtil.getTranslation(parameter.bundle(), // "App.Evcs.HardyBarth.alias.value", // - TranslationUtil.getTranslation(parameter.bundle, "right"))))))), // + TranslationUtil.getTranslation(parameter.bundle(), "right"))))))), // IP(AppDef.copyOfGeneric(CommunicationProps.ip()) // .setDefaultValue("192.168.25.30") // .setAutoGenerateField(false) // .wrapField((app, property, l, parameter, field) -> field.isRequired(true))), // ; - private final AppDef def; + private final AppDef def; - private SubPropertyFirstChargepoint(AppDef def) { + private SubPropertyFirstChargepoint(AppDef def) { this.def = def; } @@ -169,7 +195,7 @@ private SubPropertyFirstChargepoint(AppDef def() { + public AppDef def() { return this.def; } @@ -204,8 +230,8 @@ public enum SubPropertySecondChargepoint implements PropertyParent { ALIAS_CP_2(AppDef.copyOfGeneric(CommonProps.alias()) // .setAutoGenerateField(false) // .setDefaultValue((app, property, l, parameter) -> // - new JsonPrimitive(TranslationUtil.getTranslation(parameter.bundle, "App.Evcs.HardyBarth.alias.value", // - TranslationUtil.getTranslation(parameter.bundle, "left")))) // + new JsonPrimitive(TranslationUtil.getTranslation(parameter.bundle(), "App.Evcs.HardyBarth.alias.value", // + TranslationUtil.getTranslation(parameter.bundle(), "left")))) // .wrapField((app, property, l, parameter, field) -> field.isRequired(true))), // IP_CP_2(AppDef.copyOfGeneric(CommunicationProps.ip()) // .setDefaultValue("192.168.25.31") // @@ -213,9 +239,10 @@ public enum SubPropertySecondChargepoint implements PropertyParent { .wrapField((app, property, l, parameter, field) -> field.isRequired(true))), // ; - private final AppDef def; + private final AppDef def; - private SubPropertySecondChargepoint(AppDef def) { + private SubPropertySecondChargepoint( + AppDef def) { this.def = def; } @@ -224,7 +251,7 @@ private SubPropertySecondChargepoint(AppDef def() { + public AppDef def() { return this.def; } @@ -276,6 +303,11 @@ OpenemsNamedException> appPropertyConfigurationFactory() { throw new OpenemsException("Number of charging stations can only be 0 < n <= 2."); } + var maxHardwarePowerPerPhase = OptionalInt.empty(); + if (p.containsKey(Property.MAX_HARDWARE_POWER)) { + maxHardwarePowerPerPhase = OptionalInt.of(this.getInt(p, Property.MAX_HARDWARE_POWER)); + } + final var schedulerIds = new ArrayList(); final var alias = this.getString(p, l, SubPropertyFirstChargepoint.ALIAS); @@ -293,7 +325,7 @@ OpenemsNamedException> appPropertyConfigurationFactory() { .addProperty("evcs.id", evcsId) // .build())// ); - + final List clusterDependency; if (numberOfChargingStations == 2) { final var aliasCp2 = this.getString(p, l, SubPropertySecondChargepoint.ALIAS_CP_2); final var ipCp2 = this.getString(p, l, SubPropertySecondChargepoint.IP_CP_2); @@ -308,6 +340,15 @@ OpenemsNamedException> appPropertyConfigurationFactory() { JsonUtils.buildJsonObject() // .addProperty("evcs.id", evcsIdCp2) // .build())); + clusterDependency = EvcsCluster.dependency(t, this.componentManager, this.componentUtil, + maxHardwarePowerPerPhase, evcsId, evcsIdCp2); + } else { + var removeIds = Collections.emptyList(); + if (p.containsKey(Property.EVCS_ID_CP_2)) { + removeIds = Lists.newArrayList(this.getId(t, p, Property.EVCS_ID_CP_2)); + } + clusterDependency = EvcsCluster.dependency(t, this.componentManager, this.componentUtil, + maxHardwarePowerPerPhase, removeIds, evcsId); } final var ips = Lists.newArrayList(// @@ -319,7 +360,8 @@ OpenemsNamedException> appPropertyConfigurationFactory() { return new AppConfiguration(// components, // schedulerIds, // - ip.startsWith("192.168.25.") ? ips : null // + ip.startsWith("192.168.25.") ? ips : null, // + clusterDependency // ); }; } diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/IesKeywattEvcs.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/IesKeywattEvcs.java index 64efed395b6..97007ccfcf1 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/evcs/IesKeywattEvcs.java +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/IesKeywattEvcs.java @@ -1,6 +1,10 @@ package io.openems.edge.app.evcs; -import java.util.EnumMap; +import static io.openems.edge.core.appmanager.formly.enums.InputType.NUMBER; + +import java.util.Map; +import java.util.OptionalInt; +import java.util.function.Function; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; @@ -10,28 +14,31 @@ import com.google.common.collect.Lists; import com.google.gson.JsonElement; -import com.google.gson.JsonPrimitive; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.function.ThrowingTriFunction; import io.openems.common.session.Language; -import io.openems.common.utils.EnumUtils; +import io.openems.common.types.EdgeConfig; import io.openems.common.utils.JsonUtils; +import io.openems.edge.app.common.props.CommonProps; import io.openems.edge.app.evcs.IesKeywattEvcs.Property; import io.openems.edge.common.component.ComponentManager; import io.openems.edge.core.appmanager.AbstractOpenemsApp; -import io.openems.edge.core.appmanager.AppAssistant; +import io.openems.edge.core.appmanager.AbstractOpenemsAppWithProps; import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.AppDef; import io.openems.edge.core.appmanager.AppDescriptor; import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; -import io.openems.edge.core.appmanager.DefaultEnum; -import io.openems.edge.core.appmanager.JsonFormlyUtil; -import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder.Type; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; +import io.openems.edge.core.appmanager.OpenemsAppCategory; import io.openems.edge.core.appmanager.TranslationUtil; +import io.openems.edge.core.appmanager.Type; +import io.openems.edge.core.appmanager.Type.Parameter; +import io.openems.edge.core.appmanager.Type.Parameter.BundleParameter; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; /** * Describes a IES Keywatt evcs App. @@ -55,27 +62,54 @@ * */ @Component(name = "App.Evcs.IesKeywatt") -public class IesKeywattEvcs extends AbstractEvcsApp implements OpenemsApp { +public class IesKeywattEvcs extends AbstractOpenemsAppWithProps + implements OpenemsApp { - public static enum Property implements DefaultEnum, Nameable { + public static enum Property implements Type, Nameable { // Component-IDs - EVCS_ID("evcs0"), // - CTRL_EVCS_ID("ctrlEvcs0"), // + EVCS_ID(AppDef.componentId("evcs0")), // + CTRL_EVCS_ID(AppDef.componentId("ctrlEvcs0")), // // Properties - ALIAS("IES Keywatt Ladestation"), // - OCCP_CHARGE_POINT_IDENTIFIER("IES1"), // - OCCP_CONNECTOR_IDENTIFIER("1"), // + ALIAS(AppDef.copyOfGeneric(CommonProps.alias())), // + OCCP_CHARGE_POINT_IDENTIFIER(AppDef.of(IesKeywattEvcs.class) // + .setTranslatedLabelWithAppPrefix(".chargepoint.label") // + .setTranslatedDescriptionWithAppPrefix(".chargepoint.description") // + .setDefaultValue("IES1") // + .setField(JsonFormlyUtil::buildInputFromNameable, (app, property, l, parameter, field) -> // + field.isRequired(true))), // + OCCP_CONNECTOR_IDENTIFIER(AppDef.of(IesKeywattEvcs.class) // + .setTranslatedLabelWithAppPrefix(".connector.label") // + .setTranslatedDescriptionWithAppPrefix(".connector.description") // + .setDefaultValue(1) // + .setField(JsonFormlyUtil::buildInputFromNameable, (app, property, l, parameter, field) -> // + field.setInputType(NUMBER) // + .isRequired(true) // + .setMin(0))), // + MAX_HARDWARE_POWER_ACCEPT_PROPERTY(AppDef.of() // + .setAllowedToSave(false)), // + MAX_HARDWARE_POWER(AppDef.copyOfGeneric(// + EvcsProps.clusterMaxHardwarePowerSingleCp(MAX_HARDWARE_POWER_ACCEPT_PROPERTY, EVCS_ID))), // ; - private final String defaultValue; + private final AppDef def; + + private Property(AppDef def) { + this.def = def; + } - private Property(String defaultValue) { - this.defaultValue = defaultValue; + @Override + public Type self() { + return this; } @Override - public String getDefaultValue() { - return this.defaultValue; + public AppDef def() { + return this.def; + } + + @Override + public Function, BundleParameter> getParamter() { + return Parameter.functionOf(AbstractOpenemsApp::getTranslationBundle); } } @@ -87,53 +121,45 @@ public IesKeywattEvcs(@Reference ComponentManager componentManager, ComponentCon } @Override - protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appPropertyConfigurationFactory() { return (t, p, l) -> { - // values the user enters - var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName(l)); - var ocppId = this.getValueOrDefault(p, Property.OCCP_CHARGE_POINT_IDENTIFIER); - var connectorId = EnumUtils.getAsInt(p, Property.OCCP_CONNECTOR_IDENTIFIER); + final var controllerAlias = TranslationUtil.getTranslation(AbstractOpenemsApp.getTranslationBundle(l), + "App.Evcs.controller.alias"); - // values which are being auto generated by the appManager - var evcsId = this.getId(t, p, Property.EVCS_ID); - var ctrlEvcsId = this.getId(t, p, Property.CTRL_EVCS_ID); + // values the user enters + final var alias = this.getString(p, l, Property.ALIAS); + final var ocppId = this.getString(p, l, Property.OCCP_CHARGE_POINT_IDENTIFIER); + final var connectorId = this.getInt(p, Property.OCCP_CONNECTOR_IDENTIFIER); - var factoryId = "Evcs.Ocpp.IesKeywattSingle"; - var components = this.getComponents(evcsId, alias, factoryId, null, ctrlEvcsId); - var evcs = AbstractOpenemsApp.getComponentWithFactoryId(components, factoryId); - evcs.getProperties().put("ocpp.id", new JsonPrimitive(ocppId)); - evcs.getProperties().put("connectorId", new JsonPrimitive(connectorId)); + var maxHardwarePowerPerPhase = OptionalInt.empty(); + if (p.containsKey(Property.MAX_HARDWARE_POWER)) { + maxHardwarePowerPerPhase = OptionalInt.of(this.getInt(p, Property.MAX_HARDWARE_POWER)); + } - return new AppConfiguration(components, Lists.newArrayList(ctrlEvcsId, "ctrlBalancing0")); + // values which are being auto generated by the appManager + final var evcsId = this.getId(t, p, Property.EVCS_ID); + final var ctrlEvcsId = this.getId(t, p, Property.CTRL_EVCS_ID); + + final var components = Lists.newArrayList(// + new EdgeConfig.Component(evcsId, alias, "Evcs.Ocpp.IesKeywattSingle", JsonUtils.buildJsonObject() // + .addProperty("ocpp.id", ocppId) // + .addProperty("connectorId", connectorId) // + .build()), // + new EdgeConfig.Component(ctrlEvcsId, controllerAlias, "Controller.Evcs", JsonUtils.buildJsonObject() // + .addProperty("evcs.id", evcsId) // + .build())// + ); + + return new AppConfiguration(// + components, // + Lists.newArrayList(ctrlEvcsId, "ctrlBalancing0"), // + null, // + EvcsCluster.dependency(t, this.componentManager, this.componentUtil, maxHardwarePowerPerPhase, + evcsId) // + ); }; } - @Override - public AppAssistant getAppAssistant(Language language) { - var bundle = AbstractOpenemsApp.getTranslationBundle(language); - return AppAssistant.create(this.getName(language)) // - .fields(JsonUtils.buildJsonArray() // - .add(JsonFormlyUtil.buildInput(Property.OCCP_CHARGE_POINT_IDENTIFIER) // - .setLabel( - TranslationUtil.getTranslation(bundle, this.getAppId() + ".chargepoint.label")) // - .setDescription(TranslationUtil.getTranslation(bundle, - this.getAppId() + ".chargepoint.description")) // - .setDefaultValue(Property.OCCP_CHARGE_POINT_IDENTIFIER.getDefaultValue()) // - .isRequired(true) // - .build()) // - .add(JsonFormlyUtil.buildInput(Property.OCCP_CONNECTOR_IDENTIFIER) // - .setLabel(TranslationUtil.getTranslation(bundle, this.getAppId() + ".connector.label")) // - .setDescription(TranslationUtil.getTranslation(bundle, - this.getAppId() + ".connector.description")) // - .setDefaultValue(Property.OCCP_CONNECTOR_IDENTIFIER.getDefaultValue()) // - .isRequired(true) // - .setInputType(Type.NUMBER) // - .setMin(0) // - .build()) // - .build()) // - .build(); - } - @Override public AppDescriptor getAppDescriptor() { return AppDescriptor.create() // @@ -141,8 +167,8 @@ public AppDescriptor getAppDescriptor() { } @Override - protected Class getPropertyClass() { - return Property.class; + protected Property[] propertyValues() { + return Property.values(); } @Override @@ -150,4 +176,14 @@ public OpenemsAppCardinality getCardinality() { return OpenemsAppCardinality.MULTIPLE; } + @Override + public OpenemsAppCategory[] getCategories() { + return new OpenemsAppCategory[] { OpenemsAppCategory.EVCS }; + } + + @Override + protected IesKeywattEvcs getApp() { + return this; + } + } diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/KebaEvcs.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/KebaEvcs.java index 0b3ac4e5bfa..67801bf87ee 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/evcs/KebaEvcs.java +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/KebaEvcs.java @@ -1,6 +1,8 @@ package io.openems.edge.app.evcs; -import java.util.EnumMap; +import java.util.Map; +import java.util.OptionalInt; +import java.util.function.Function; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; @@ -14,23 +16,28 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.function.ThrowingTriFunction; import io.openems.common.session.Language; +import io.openems.common.types.EdgeConfig; import io.openems.common.utils.JsonUtils; +import io.openems.edge.app.common.props.CommonProps; +import io.openems.edge.app.common.props.CommunicationProps; import io.openems.edge.app.evcs.KebaEvcs.Property; import io.openems.edge.common.component.ComponentManager; import io.openems.edge.core.appmanager.AbstractOpenemsApp; -import io.openems.edge.core.appmanager.AppAssistant; +import io.openems.edge.core.appmanager.AbstractOpenemsAppWithProps; import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.AppDef; import io.openems.edge.core.appmanager.AppDescriptor; import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; -import io.openems.edge.core.appmanager.DefaultEnum; import io.openems.edge.core.appmanager.InterfaceConfiguration; -import io.openems.edge.core.appmanager.JsonFormlyUtil; -import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder.Validation; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; +import io.openems.edge.core.appmanager.OpenemsAppCategory; import io.openems.edge.core.appmanager.TranslationUtil; +import io.openems.edge.core.appmanager.Type; +import io.openems.edge.core.appmanager.Type.Parameter; +import io.openems.edge.core.appmanager.Type.Parameter.BundleParameter; /** * Describes a Keba evcs App. @@ -53,26 +60,42 @@ * */ @Component(name = "App.Evcs.Keba") -public class KebaEvcs extends AbstractEvcsApp implements OpenemsApp { +public class KebaEvcs extends AbstractOpenemsAppWithProps + implements OpenemsApp { - public enum Property implements DefaultEnum, Nameable { + public enum Property implements Type, Nameable { // Component-IDs - EVCS_ID("evcs0"), // - CTRL_EVCS_ID("ctrlEvcs0"), // + EVCS_ID(AppDef.componentId("evcs0")), // + CTRL_EVCS_ID(AppDef.componentId("ctrlEvcs0")), // // Properties - ALIAS("KEBA Ladestation"), // - IP("192.168.25.11") // + ALIAS(AppDef.copyOfGeneric(CommonProps.alias())), // + IP(AppDef.copyOfGeneric(CommunicationProps.ip()) // + .setDefaultValue("192.168.25.11")), // + MAX_HARDWARE_POWER_ACCEPT_PROPERTY(AppDef.of() // + .setAllowedToSave(false)), // + MAX_HARDWARE_POWER(AppDef.copyOfGeneric(// + EvcsProps.clusterMaxHardwarePowerSingleCp(MAX_HARDWARE_POWER_ACCEPT_PROPERTY, EVCS_ID))), // ; - private final String defaultValue; + private final AppDef def; - private Property(String defaultValue) { - this.defaultValue = defaultValue; + private Property(AppDef def) { + this.def = def; } @Override - public String getDefaultValue() { - return this.defaultValue; + public Type self() { + return this; + } + + @Override + public AppDef def() { + return this.def; + } + + @Override + public Function, BundleParameter> getParamter() { + return Parameter.functionOf(AbstractOpenemsApp::getTranslationBundle); } } @@ -84,17 +107,32 @@ public KebaEvcs(@Reference ComponentManager componentManager, ComponentContext c } @Override - protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appPropertyConfigurationFactory() { return (t, p, l) -> { + final var controllerAlias = TranslationUtil.getTranslation(AbstractOpenemsApp.getTranslationBundle(l), + "App.Evcs.controller.alias"); + // values the user enters - var ip = this.getValueOrDefault(p, Property.IP); - var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName(l)); + final var ip = this.getString(p, l, Property.IP); + final var alias = this.getString(p, l, Property.ALIAS); // values which are being auto generated by the appmanager - var evcsId = this.getId(t, p, Property.EVCS_ID); - var ctrlEvcsId = this.getId(t, p, Property.CTRL_EVCS_ID); - - var components = this.getComponents(evcsId, alias, "Evcs.Keba.KeContact", ip, ctrlEvcsId); + final var evcsId = this.getId(t, p, Property.EVCS_ID); + final var ctrlEvcsId = this.getId(t, p, Property.CTRL_EVCS_ID); + + var maxHardwarePowerPerPhase = OptionalInt.empty(); + if (p.containsKey(Property.MAX_HARDWARE_POWER)) { + maxHardwarePowerPerPhase = OptionalInt.of(this.getInt(p, Property.MAX_HARDWARE_POWER)); + } + + var components = Lists.newArrayList(// + new EdgeConfig.Component(evcsId, alias, "Evcs.Keba.KeContact", JsonUtils.buildJsonObject() // + .addPropertyIfNotNull("ip", ip) // + .build()), // + new EdgeConfig.Component(ctrlEvcsId, controllerAlias, "Controller.Evcs", JsonUtils.buildJsonObject() // + .addProperty("evcs.id", evcsId) // + .build())// + ); var ips = Lists.newArrayList(// new InterfaceConfiguration("eth0") // @@ -104,41 +142,37 @@ protected ThrowingTriFunction getPropertyClass() { - return Property.class; + public OpenemsAppCategory[] getCategories() { + return new OpenemsAppCategory[] { OpenemsAppCategory.EVCS }; } @Override - public OpenemsAppCardinality getCardinality() { - return OpenemsAppCardinality.MULTIPLE; + protected KebaEvcs getApp() { + return this; + } + + @Override + protected Property[] propertyValues() { + return Property.values(); } } diff --git a/io.openems.edge.core/src/io/openems/edge/app/hardware/KMtronic8Channel.java b/io.openems.edge.core/src/io/openems/edge/app/hardware/KMtronic8Channel.java index 309429aa0de..f7587ae501f 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/hardware/KMtronic8Channel.java +++ b/io.openems.edge.core/src/io/openems/edge/app/hardware/KMtronic8Channel.java @@ -1,10 +1,12 @@ package io.openems.edge.app.hardware; -import java.util.EnumMap; +import java.util.Map; +import java.util.function.Function; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import com.google.common.collect.Lists; @@ -15,23 +17,25 @@ import io.openems.common.session.Language; import io.openems.common.types.EdgeConfig; import io.openems.common.utils.JsonUtils; +import io.openems.edge.app.common.props.CommonProps; +import io.openems.edge.app.common.props.CommunicationProps; import io.openems.edge.app.hardware.KMtronic8Channel.Property; import io.openems.edge.common.component.ComponentManager; -import io.openems.edge.core.appmanager.AbstractEnumOpenemsApp; import io.openems.edge.core.appmanager.AbstractOpenemsApp; -import io.openems.edge.core.appmanager.AppAssistant; +import io.openems.edge.core.appmanager.AbstractOpenemsAppWithProps; import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.AppDef; import io.openems.edge.core.appmanager.AppDescriptor; import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; import io.openems.edge.core.appmanager.InterfaceConfiguration; -import io.openems.edge.core.appmanager.JsonFormlyUtil; -import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder.Validation; -import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; import io.openems.edge.core.appmanager.TranslationUtil; +import io.openems.edge.core.appmanager.Type; +import io.openems.edge.core.appmanager.Type.Parameter; +import io.openems.edge.core.appmanager.Type.Parameter.BundleParameter; /** * Describes a App for KMtronic 8-Channel Relay. @@ -39,7 +43,7 @@ *

       {
         "appId":"App.Hardware.KMtronic8Channel",
    -    "alias":"FEMS Relais 8-Kanal",
    +    "alias":"",
         "instanceId": UUID,
         "image": base64,
         "properties":{
    @@ -53,35 +57,67 @@
       }
      * 
    */ -@org.osgi.service.component.annotations.Component(name = "App.Hardware.KMtronic8Channel") -public class KMtronic8Channel extends AbstractEnumOpenemsApp implements OpenemsApp { +@Component(name = "App.Hardware.KMtronic8Channel") +public class KMtronic8Channel extends AbstractOpenemsAppWithProps + implements OpenemsApp { - public static enum Property implements Nameable { + public static enum Property implements Type { // Component-IDs - IO_ID, // - MODBUS_ID, // + IO_ID(AppDef.componentId("io1")), // + MODBUS_ID(AppDef.componentId("modbus10")), // // Properties - ALIAS, // - IP; + ALIAS(AppDef.copyOfGeneric(CommonProps.alias())), // + IP(AppDef.copyOfGeneric(CommunicationProps.ip(), // + def -> def.setTranslatedDescriptionWithAppPrefix(".ip.description") // + .setDefaultValue("192.168.1.199") // + .wrapField((app, property, l, parameter, field) -> field.isRequired(true)))), // + CHECK(AppDef.copyOfGeneric(CommonProps.installationHint(// + (app, property, l, parameter) -> TranslationUtil.getTranslation(parameter.bundle, // + "App.Hardware.KMtronic8Channel.installationHint")))), // + ; + + private final AppDef def; + + private Property(AppDef def) { + this.def = def; + } + + @Override + public Type self() { + return this; + } + + @Override + public AppDef def() { + return this.def; + } + + @Override + public Function, BundleParameter> getParamter() { + return Parameter.functionOf(AbstractOpenemsApp::getTranslationBundle); + } } @Activate - public KMtronic8Channel(@Reference ComponentManager componentManager, ComponentContext context, - @Reference ConfigurationAdmin cm, @Reference ComponentUtil componentUtil) { + public KMtronic8Channel(// + @Reference ComponentManager componentManager, // + ComponentContext context, // + @Reference ConfigurationAdmin cm, // + @Reference ComponentUtil componentUtil // + ) { super(componentManager, context, cm, componentUtil); } @Override - protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appPropertyConfigurationFactory() { return (t, p, l) -> { + final var alias = this.getString(p, l, Property.ALIAS); + final var ip = this.getString(p, Property.IP); - var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName(l)); - var ip = this.getValueOrDefault(p, Property.IP, "192.168.1.199"); + final var modbusId = this.getId(t, p, Property.MODBUS_ID); + final var ioId = this.getId(t, p, Property.IO_ID); - var modbusId = this.getId(t, p, Property.MODBUS_ID, "modbus10"); - var ioId = this.getId(t, p, Property.IO_ID, "io1"); - - var comp = Lists.newArrayList(// + final var comp = Lists.newArrayList(// new EdgeConfig.Component(ioId, alias, "IO.KMtronic", // JsonUtils.buildJsonObject() // .addProperty("modbus.id", modbusId) // @@ -91,7 +127,7 @@ protected ThrowingTriFunction getPropertyClass() { - return Property.class; + protected Property[] propertyValues() { + return Property.values(); + } + + @Override + protected KMtronic8Channel getApp() { + return this; } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/app/heat/CombinedHeatAndPower.java b/io.openems.edge.core/src/io/openems/edge/app/heat/CombinedHeatAndPower.java index 5509335f847..9974030ca51 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/heat/CombinedHeatAndPower.java +++ b/io.openems.edge.core/src/io/openems/edge/app/heat/CombinedHeatAndPower.java @@ -1,12 +1,19 @@ package io.openems.edge.app.heat; -import java.util.EnumMap; +import static io.openems.edge.app.common.props.CommonProps.alias; +import static io.openems.edge.app.heat.HeatProps.createPhaseInformation; +import static io.openems.edge.app.heat.HeatProps.relayContactDef; +import static io.openems.edge.core.appmanager.validator.Checkables.checkRelayCount; + import java.util.List; -import java.util.TreeMap; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.function.Function; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import com.google.common.collect.Lists; @@ -17,25 +24,26 @@ import io.openems.common.session.Language; import io.openems.common.types.EdgeConfig; import io.openems.common.utils.JsonUtils; +import io.openems.edge.app.heat.CombinedHeatAndPower.CombinedHeatAndPowerParameter; import io.openems.edge.app.heat.CombinedHeatAndPower.Property; +import io.openems.edge.app.heat.HeatProps.RelayContactInformation; +import io.openems.edge.app.heat.HeatProps.RelayContactInformationProvider; import io.openems.edge.common.component.ComponentManager; -import io.openems.edge.core.appmanager.AbstractEnumOpenemsApp; -import io.openems.edge.core.appmanager.AbstractOpenemsApp; -import io.openems.edge.core.appmanager.AppAssistant; +import io.openems.edge.core.appmanager.AbstractOpenemsAppWithProps; import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.AppDef; import io.openems.edge.core.appmanager.AppDescriptor; import io.openems.edge.core.appmanager.ComponentUtil; +import io.openems.edge.core.appmanager.ComponentUtil.PreferredRelay; import io.openems.edge.core.appmanager.ConfigurationTarget; -import io.openems.edge.core.appmanager.DefaultEnum; -import io.openems.edge.core.appmanager.JsonFormlyUtil; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; -import io.openems.edge.core.appmanager.TranslationUtil; +import io.openems.edge.core.appmanager.Type; +import io.openems.edge.core.appmanager.Type.Parameter.BundleProvider; import io.openems.edge.core.appmanager.dependency.DependencyDeclaration; import io.openems.edge.core.appmanager.dependency.DependencyUtil; -import io.openems.edge.core.appmanager.validator.CheckRelayCount; import io.openems.edge.core.appmanager.validator.ValidatorConfig; /** @@ -63,26 +71,53 @@ } * */ -@org.osgi.service.component.annotations.Component(name = "App.Heat.CHP") -public class CombinedHeatAndPower extends AbstractEnumOpenemsApp implements OpenemsApp { +@Component(name = "App.Heat.CHP") +public class CombinedHeatAndPower + extends AbstractOpenemsAppWithProps + implements OpenemsApp { + + public record CombinedHeatAndPowerParameter(// + ResourceBundle bundle, // + RelayContactInformation relayContactInformation // + ) implements BundleProvider, RelayContactInformationProvider { + + } - public static enum Property implements DefaultEnum, Nameable { + public static enum Property implements Type { // Component-IDs - CTRL_CHP_SOC_ID("ctrlChpSoc0"), // + CTRL_CHP_SOC_ID(AppDef.componentId("ctrlChpSoc0")), // // Properties - ALIAS("Blockheizkraftwerk"), // - OUTPUT_CHANNEL("io0/Relay1"), // + ALIAS(alias()), // + OUTPUT_CHANNEL(chpRelayContactDef(1)), // ; - private final String defaultValue; + private final AppDef def; + + private Property( + AppDef def) { + this.def = def; + } + + @Override + public Type self() { + return this; + } - private Property(String defaultValue) { - this.defaultValue = defaultValue; + @Override + public AppDef def() { + return this.def; } @Override - public String getDefaultValue() { - return this.defaultValue; + public Function, CombinedHeatAndPowerParameter> getParamter() { + return t -> { + return new CombinedHeatAndPowerParameter(// + createResourceBundle(t.language), // + createPhaseInformation(t.app.componentUtil, 1, // + new PreferredRelay(4, new int[] { 1 }), // + new PreferredRelay(8, new int[] { 1 })) // + ); + }; } } @@ -94,14 +129,14 @@ public CombinedHeatAndPower(@Reference ComponentManager componentManager, Compon } @Override - protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appPropertyConfigurationFactory() { return (t, p, l) -> { final var chpId = this.getId(t, p, Property.CTRL_CHP_SOC_ID); - final var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName(l)); - final var outputChannelAddress = this.getValueOrDefault(p, Property.OUTPUT_CHANNEL); + final var alias = this.getString(p, l, Property.ALIAS); + final var outputChannelAddress = this.getString(p, Property.OUTPUT_CHANNEL); - var components = Lists.newArrayList(// + final var components = List.of(// new EdgeConfig.Component(chpId, alias, "Controller.CHP.SoC", JsonUtils.buildJsonObject() // .addProperty("inputChannelAddress", "_sum/EssSoc") .addProperty("outputChannelAddress", outputChannelAddress) // @@ -134,32 +169,6 @@ protected ThrowingTriFunction r.relays).flatMap(List::stream) // - .toList()) // - .setDefaultValueWithStringSupplier(() -> { - var relays = this.componentUtil.getPreferredRelays(Lists.newArrayList(), - new int[] { 1 }, new int[] { 1 }); - if (relays == null) { - return Property.OUTPUT_CHANNEL.getDefaultValue(); - } - return relays[0]; - }) // - .setLabel(TranslationUtil.getTranslation(bundle, - this.getAppId() + ".outputChannel.label")) // - .setDescription(TranslationUtil.getTranslation(bundle, // - "App.Heat.outputChannel.description")) // - .build()) - .build()) - .build(); - } - @Override public AppDescriptor getAppDescriptor() { return AppDescriptor.create() // @@ -174,21 +183,29 @@ public OpenemsAppCategory[] getCategories() { @Override public ValidatorConfig.Builder getValidateBuilder() { return ValidatorConfig.create() // - .setInstallableCheckableConfigs(Lists.newArrayList(// - new ValidatorConfig.CheckableConfig(CheckRelayCount.COMPONENT_NAME, - new ValidatorConfig.MapBuilder<>(new TreeMap()) // - .put("count", 1) // - .build()))); + .setInstallableCheckableConfigs(checkRelayCount(1)); } @Override - protected Class getPropertyClass() { - return Property.class; + public OpenemsAppCardinality getCardinality() { + return OpenemsAppCardinality.SINGLE; } @Override - public OpenemsAppCardinality getCardinality() { - return OpenemsAppCardinality.SINGLE; + protected CombinedHeatAndPower getApp() { + return this; + } + + @Override + protected Property[] propertyValues() { + return Property.values(); + } + + private static

    // + AppDef chpRelayContactDef(int contactPosition) { + return AppDef.copyOfGeneric(relayContactDef(contactPosition), def -> // + def.setTranslatedLabelWithAppPrefix(".outputChannel.label") // + .setTranslatedDescription("App.Heat.outputChannel.description")); } } diff --git a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatProps.java b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatProps.java new file mode 100644 index 00000000000..6f799d73cf3 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatProps.java @@ -0,0 +1,209 @@ +package io.openems.edge.app.heat; + +import static io.openems.common.utils.JsonUtils.toJsonArray; +import static io.openems.edge.app.common.props.CommonProps.defaultDef; +import static io.openems.edge.core.appmanager.formly.builder.selectgroup.Option.buildOption; +import static io.openems.edge.core.appmanager.formly.builder.selectgroup.OptionGroup.buildOptionGroup; +import static java.util.stream.Collectors.joining; + +import java.util.Arrays; +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Function; + +import com.google.common.collect.Lists; +import com.google.gson.JsonNull; +import com.google.gson.JsonPrimitive; + +import io.openems.edge.core.appmanager.AppDef; +import io.openems.edge.core.appmanager.ComponentUtil; +import io.openems.edge.core.appmanager.ComponentUtil.PreferredRelay; +import io.openems.edge.core.appmanager.ComponentUtil.RelayContactInfo; +import io.openems.edge.core.appmanager.ComponentUtil.RelayInfo; +import io.openems.edge.core.appmanager.Nameable; +import io.openems.edge.core.appmanager.OpenemsApp; +import io.openems.edge.core.appmanager.TranslationUtil; +import io.openems.edge.core.appmanager.Type; +import io.openems.edge.core.appmanager.Type.Parameter.BundleProvider; +import io.openems.edge.core.appmanager.formly.Exp; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; +import io.openems.edge.core.appmanager.formly.expression.BooleanExpression; +import io.openems.edge.core.appmanager.formly.expression.StringExpression; +import io.openems.edge.core.appmanager.formly.expression.Variable; + +public final class HeatProps { + + public static record RelayContactInformation(// + String[] preferredRelays, // + List allRelays, // + List defaultRelays // + ) { + + } + + /** + * Provider interface for a {@link RelayContactInformation}. + */ + public static interface RelayContactInformationProvider { + + /** + * Provides a {@link RelayContactInformation}. + * + * @return the {@link RelayContactInformation} + */ + RelayContactInformation relayContactInformation(); + + } + + /** + * Utility method to create a {@link RelayContactInformation}. + * + * @param util the {@link ComponentUtil} + * @param cnt the number of relays + * @param first the first {@link PreferredRelay} + * @param preferredRelays the other {@link PreferredRelay PreferredRelays} + * @return the created {@link RelayContactInformation} + * @see RelayContactInformationProvider + */ + public static RelayContactInformation createPhaseInformation(// + final ComponentUtil util, // + final int cnt, // + final PreferredRelay first, // + final PreferredRelay... preferredRelays // + ) { + final var preferredRelayList = Lists.newArrayList(preferredRelays); + preferredRelayList.add(first); + return new RelayContactInformation(// + util.getPreferredRelays(cnt, first, preferredRelays), // + util.getAllRelayInfos(), // + preferredRelayList // + ); + } + + /** + * Creates a {@link AppDef} for selecting a relay contact. Sets the default + * value from the {@link RelayContactInformation} and also disables options + * which are already used by other components. + * + * @param

    the type of the parameter + * @param contactPosition the number of the contacts to select e. g. a HeatPump + * app needs 2 relay contacts to be configured pass 1 for + * the first and 2 for the second one. + * @param allContacts the other names of contacts + * @return the {@link AppDef} + */ + public static

    // + AppDef relayContactDef(int contactPosition, Nameable... allContacts) { + return AppDef.copyOfGeneric(defaultDef(), def -> { + def.setDefaultValue((app, property, l, parameter) -> { + final var preferredRelay = parameter.relayContactInformation().preferredRelays[contactPosition - 1]; + return preferredRelay == null ? JsonNull.INSTANCE : new JsonPrimitive(preferredRelay); + }); + def.setField(JsonFormlyUtil::buildSelectGroupFromNameable, (app, property, l, parameter, field) -> { + final var information = parameter.relayContactInformation(); + final var defaultString = " (" + + TranslationUtil.getTranslation(parameter.bundle(), "App.Heat.defaultRelayContact") + ")"; + + final Function disabledExpressionFunction = channel -> { + if (channel.usingComponents().isEmpty()) { + return null; + } + + var exp = Exp.initialModelValue(property).notEqual(Exp.staticValue(channel.channel())); + for (final var nameable : allContacts) { + if (nameable.name().equals(property.name())) { + continue; + } + exp = exp.and(Exp.initialModelValue(nameable).notEqual(Exp.staticValue(channel.channel()))); + } + + return exp; + }; + + final BiFunction titleExpressionFunction = (relayInfo, + channelInfo) -> { + final var isDefault = information.defaultRelays().stream() + .filter(pr -> pr.numberOfRelays() == relayInfo.channels().size()) // + .findFirst() // + .map(t -> relayInfo.channels().get(t.preferredRelays()[contactPosition - 1] - 1) + .equals(channelInfo)) + .orElse(false); + final var channelDisplayName = channelInfo.getDisplayName() + (isDefault ? defaultString : ""); + if (channelInfo.usingComponents().isEmpty()) { + return StringExpression.of(channelDisplayName); + } + + final var componentsString = channelInfo.usingComponents().stream() // + .map(c -> c.alias().isBlank() ? c.id() : c.alias()) // + .map(t -> "\\'" + t + "\\'") // + .collect(joining(", ")); + + var exp = Exp.initialModelValue(property).equal(Exp.staticValue(channelInfo.channel())); + for (final var nameable : allContacts) { + if (nameable.name().equals(property.name())) { + continue; + } + exp = exp.or(Exp.initialModelValue(nameable).equal(Exp.staticValue(channelInfo.channel()))); + } + + return Exp.ifElse(exp, // + StringExpression.of(channelDisplayName), // + StringExpression.of(channelDisplayName // + + " - " + TranslationUtil.getTranslation(parameter.bundle(), + "App.Heat.relayContactAlreadyUsed", componentsString)) // + ); + }; + information.allRelays().forEach(relayInfo -> { + field.addOption(buildOptionGroup(relayInfo.id(), relayInfo.getDisplayName()) // + .addOptions(relayInfo.channels(), (channelInfo) -> buildOption(channelInfo.channel()) // + .setTitleExpression(titleExpressionFunction.apply(relayInfo, channelInfo)) + .onlyIf(!channelInfo.usingComponents().isEmpty(), + b -> b.setDisabledExpression(disabledExpressionFunction.apply(channelInfo))) // + .build()) + .build()); + }); + }); + }); + } + + /** + * Creates a {@link AppDef} for a group of picking contacts from relays. Used to + * not pick contacts twice. + * + * @param the type of the app + * @param the type of the property + * @param

    the type of parameter + * @param phaseDefs the {@link AppDef AppDefs} of the relay contacts + * @return the {@link AppDef} + */ + @SafeVarargs + public static , // + P extends BundleProvider> AppDef phaseGroup(// + PROP... phaseDefs // + ) { + return AppDef.copyOfGeneric(defaultDef(), def -> { + def.setField(JsonFormlyUtil::buildFieldGroupFromNameable, (app, property, l, parameter, field) -> { + + for (var phaseDef : phaseDefs) { + final var array = Exp.array(Arrays.stream(phaseDefs).filter(t -> t != phaseDef) + .map(Exp::currentModelValue).toArray(Variable[]::new)); + + final var expression = array.every(t -> Exp.currentModelValue(phaseDef).notEqual(t)); + final var errorMessage = TranslationUtil.getTranslation(parameter.bundle(), + "App.Heat.duplicatedRelayContactSelected"); + field.setCustomValidation(phaseDef.name() + "_VALIDATION", expression, + StringExpression.of(errorMessage), phaseDefs[phaseDefs.length - 1]); + } + field.setFieldGroup(Arrays.stream(phaseDefs) // + .map(type -> type.def().getField().get(app, type, l, parameter).build()) // + .collect(toJsonArray())) // + .hideKey(); + }); + }); + } + + private HeatProps() { + } + +} \ No newline at end of file diff --git a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java index b316298d20c..6de2dd0bcce 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java +++ b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java @@ -1,15 +1,21 @@ package io.openems.edge.app.heat; -import java.util.EnumMap; +import static io.openems.edge.app.common.props.CommonProps.alias; +import static io.openems.edge.app.heat.HeatProps.createPhaseInformation; +import static io.openems.edge.app.heat.HeatProps.phaseGroup; +import static io.openems.edge.app.heat.HeatProps.relayContactDef; +import static io.openems.edge.core.appmanager.validator.Checkables.checkRelayCount; + import java.util.List; -import java.util.TreeMap; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.function.Function; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Reference; -import com.google.common.collect.Lists; import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; @@ -17,24 +23,26 @@ import io.openems.common.session.Language; import io.openems.common.types.EdgeConfig; import io.openems.common.utils.JsonUtils; +import io.openems.edge.app.heat.HeatProps.RelayContactInformation; +import io.openems.edge.app.heat.HeatProps.RelayContactInformationProvider; +import io.openems.edge.app.heat.HeatPump.HeatPumpParameter; import io.openems.edge.app.heat.HeatPump.Property; import io.openems.edge.common.component.ComponentManager; -import io.openems.edge.core.appmanager.AbstractEnumOpenemsApp; -import io.openems.edge.core.appmanager.AbstractOpenemsApp; -import io.openems.edge.core.appmanager.AppAssistant; +import io.openems.edge.core.appmanager.AbstractOpenemsAppWithProps; import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.AppDef; import io.openems.edge.core.appmanager.AppDescriptor; import io.openems.edge.core.appmanager.ComponentUtil; +import io.openems.edge.core.appmanager.ComponentUtil.PreferredRelay; import io.openems.edge.core.appmanager.ConfigurationTarget; -import io.openems.edge.core.appmanager.JsonFormlyUtil; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; -import io.openems.edge.core.appmanager.TranslationUtil; +import io.openems.edge.core.appmanager.Type; +import io.openems.edge.core.appmanager.Type.Parameter.BundleProvider; import io.openems.edge.core.appmanager.dependency.DependencyDeclaration; import io.openems.edge.core.appmanager.dependency.DependencyUtil; -import io.openems.edge.core.appmanager.validator.CheckRelayCount; import io.openems.edge.core.appmanager.validator.ValidatorConfig; /** @@ -64,16 +72,50 @@ * */ @org.osgi.service.component.annotations.Component(name = "App.Heat.HeatPump") -public class HeatPump extends AbstractEnumOpenemsApp implements OpenemsApp { +public class HeatPump extends AbstractOpenemsAppWithProps implements OpenemsApp { + + public record HeatPumpParameter(// + ResourceBundle bundle, // + RelayContactInformation relayContactInformation // + ) implements BundleProvider, RelayContactInformationProvider { - public static enum Property implements Nameable { + } + + public static enum Property implements Type { // Component-IDs - CTRL_IO_HEAT_PUMP_ID, // + CTRL_IO_HEAT_PUMP_ID(AppDef.componentId("ctrlIoHeatPump0")), // // Properties - ALIAS, // - OUTPUT_CHANNEL_1, // - OUTPUT_CHANNEL_2 // + ALIAS(alias()), // + OUTPUT_CHANNEL_1(heatPumpRelayContactDef(1)), // + OUTPUT_CHANNEL_2(heatPumpRelayContactDef(2)), // + PHASE_GROUP(phaseGroup(OUTPUT_CHANNEL_1, OUTPUT_CHANNEL_2)), // ; + + private final AppDef def; + + private Property(AppDef def) { + this.def = def; + } + + @Override + public Type self() { + return this; + } + + @Override + public AppDef def() { + return this.def; + } + + @Override + public Function, HeatPumpParameter> getParamter() { + return t -> new HeatPumpParameter(// + createResourceBundle(t.language), // + createPhaseInformation(t.app.componentUtil, 2, // + new PreferredRelay(4, new int[] { 2, 3 }), // + new PreferredRelay(8, new int[] { 2, 3 })) // + ); + } } @Activate @@ -83,15 +125,15 @@ public HeatPump(@Reference ComponentManager componentManager, ComponentContext c } @Override - protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appPropertyConfigurationFactory() { return (t, p, l) -> { - final var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName(l)); - final var ctrlIoHeatPumpId = this.getId(t, p, Property.CTRL_IO_HEAT_PUMP_ID, "ctrlIoHeatPump0"); + final var alias = this.getString(p, l, Property.ALIAS); + final var ctrlIoHeatPumpId = this.getId(t, p, Property.CTRL_IO_HEAT_PUMP_ID); - var outputChannel1 = this.getValueOrDefault(p, Property.OUTPUT_CHANNEL_1, "io0/Relay2"); - var outputChannel2 = this.getValueOrDefault(p, Property.OUTPUT_CHANNEL_2, "io0/Relay3"); + final var outputChannel1 = this.getString(p, Property.OUTPUT_CHANNEL_1); + final var outputChannel2 = this.getString(p, Property.OUTPUT_CHANNEL_2); - var components = Lists.newArrayList(// + final var components = List.of(// new EdgeConfig.Component(ctrlIoHeatPumpId, alias, "Controller.Io.HeatPump.SgReady", JsonUtils.buildJsonObject() // .addProperty("outputChannel1", outputChannel1) // @@ -99,8 +141,8 @@ protected ThrowingTriFunction r.relays).flatMap(List::stream) // - .toList(); - return AppAssistant.create(this.getName(language)) // - .fields(JsonUtils.buildJsonArray() // - .add(JsonFormlyUtil.buildSelect(Property.OUTPUT_CHANNEL_1) // - .setOptions(options) // - .onlyIf(relays != null, t -> t.setDefaultValue(relays[0])) // - .setLabel(TranslationUtil.getTranslation(bundle, - this.getAppId() + ".outputChannel1.label")) - .setDescription(TranslationUtil.getTranslation(bundle, // - "App.Heat.outputChannel.description")) // - .build()) - .add(JsonFormlyUtil.buildSelect(Property.OUTPUT_CHANNEL_2) // - .setOptions(options) // - .onlyIf(relays != null, t -> t.setDefaultValue(relays[1])) // - .setLabel(TranslationUtil.getTranslation(bundle, - this.getAppId() + ".outputChannel2.label")) - .setDescription(TranslationUtil.getTranslation(bundle, // - "App.Heat.outputChannel.description")) // - .build()) - .build()) - .build(); - } - @Override public AppDescriptor getAppDescriptor() { return AppDescriptor.create() // @@ -166,21 +178,32 @@ public OpenemsAppCategory[] getCategories() { @Override public ValidatorConfig.Builder getValidateBuilder() { return ValidatorConfig.create() // - .setInstallableCheckableConfigs(Lists.newArrayList(// - new ValidatorConfig.CheckableConfig(CheckRelayCount.COMPONENT_NAME, - new ValidatorConfig.MapBuilder<>(new TreeMap()) // - .put("count", 2) // - .build()))); + .setInstallableCheckableConfigs(checkRelayCount(2)); } @Override - protected Class getPropertyClass() { - return Property.class; + public OpenemsAppCardinality getCardinality() { + return OpenemsAppCardinality.SINGLE; } @Override - public OpenemsAppCardinality getCardinality() { - return OpenemsAppCardinality.SINGLE; + protected HeatPump getApp() { + return this; + } + + @Override + protected Property[] propertyValues() { + return Property.values(); + } + + private static

    // + AppDef heatPumpRelayContactDef(int contactPosition) { + return AppDef.copyOfGeneric(relayContactDef(contactPosition, // + Nameable.of("OUTPUT_CHANNEL_1"), Nameable.of("OUTPUT_CHANNEL_2")), + b -> b.setTranslatedLabelWithAppPrefix(".outputChannel" + contactPosition + ".label") // + .setTranslatedDescription("App.Heat.outputChannel.description") // + .wrapField((app, property, l, parameter, field) -> field.isRequired(true)) // + .setAutoGenerateField(false)); } } diff --git a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatingElement.java b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatingElement.java index 4a4a82de686..aae809d44c6 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatingElement.java +++ b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatingElement.java @@ -1,12 +1,22 @@ package io.openems.edge.app.heat; -import java.util.EnumMap; -import java.util.List; -import java.util.TreeMap; +import static io.openems.common.channel.Unit.SECONDS; +import static io.openems.common.channel.Unit.WATT; +import static io.openems.edge.app.common.props.CommonProps.alias; +import static io.openems.edge.app.heat.HeatProps.createPhaseInformation; +import static io.openems.edge.app.heat.HeatProps.phaseGroup; +import static io.openems.edge.app.heat.HeatProps.relayContactDef; +import static io.openems.edge.core.appmanager.formly.enums.InputType.NUMBER; +import static io.openems.edge.core.appmanager.validator.Checkables.checkRelayCount; + +import java.util.Map; +import java.util.ResourceBundle; +import java.util.function.Function; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import com.google.common.collect.Lists; @@ -16,28 +26,29 @@ import io.openems.common.function.ThrowingTriFunction; import io.openems.common.session.Language; import io.openems.common.types.EdgeConfig; -import io.openems.common.utils.EnumUtils; import io.openems.common.utils.JsonUtils; +import io.openems.edge.app.heat.HeatProps.RelayContactInformation; +import io.openems.edge.app.heat.HeatProps.RelayContactInformationProvider; +import io.openems.edge.app.heat.HeatingElement.HeatingElementParameter; import io.openems.edge.app.heat.HeatingElement.Property; import io.openems.edge.common.component.ComponentManager; -import io.openems.edge.core.appmanager.AbstractEnumOpenemsApp; -import io.openems.edge.core.appmanager.AbstractOpenemsApp; -import io.openems.edge.core.appmanager.AppAssistant; +import io.openems.edge.core.appmanager.AbstractOpenemsAppWithProps; import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.AppDef; import io.openems.edge.core.appmanager.AppDescriptor; +import io.openems.edge.core.appmanager.ComponentManagerSupplier; import io.openems.edge.core.appmanager.ComponentUtil; +import io.openems.edge.core.appmanager.ComponentUtil.PreferredRelay; import io.openems.edge.core.appmanager.ConfigurationTarget; -import io.openems.edge.core.appmanager.DefaultEnum; -import io.openems.edge.core.appmanager.JsonFormlyUtil; -import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder.Type; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; -import io.openems.edge.core.appmanager.TranslationUtil; +import io.openems.edge.core.appmanager.Type; +import io.openems.edge.core.appmanager.Type.Parameter.BundleProvider; import io.openems.edge.core.appmanager.dependency.DependencyDeclaration; import io.openems.edge.core.appmanager.dependency.DependencyUtil; -import io.openems.edge.core.appmanager.validator.CheckRelayCount; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; import io.openems.edge.core.appmanager.validator.ValidatorConfig; /** @@ -68,29 +79,77 @@ } * */ -@org.osgi.service.component.annotations.Component(name = "App.Heat.HeatingElement") -public class HeatingElement extends AbstractEnumOpenemsApp implements OpenemsApp { +@Component(name = "App.Heat.HeatingElement") +public class HeatingElement extends AbstractOpenemsAppWithProps + implements OpenemsApp { - public static enum Property implements DefaultEnum, Nameable { + public record HeatingElementParameter(// + ResourceBundle bundle, // + RelayContactInformation relayContactInformation // + ) implements BundleProvider, RelayContactInformationProvider { + + } + + public static enum Property implements Type, Nameable { // Component-IDs - CTRL_IO_HEATING_ELEMENT_ID("ctrlIoHeatingElement0"), // + CTRL_IO_HEATING_ELEMENT_ID(AppDef.componentId("ctrlIoHeatingElement0")), // // Properties - ALIAS("Heating Element App"), // - OUTPUT_CHANNEL_PHASE_L1("io0/Relay1"), // - OUTPUT_CHANNEL_PHASE_L2("io0/Relay2"), // - OUTPUT_CHANNEL_PHASE_L3("io0/Relay3"), // - POWER_PER_PHASE("2000") // + ALIAS(alias()), // + OUTPUT_CHANNEL_PHASE_L1(heatingElementRelayContactDef(1)), // + OUTPUT_CHANNEL_PHASE_L2(heatingElementRelayContactDef(2)), // + OUTPUT_CHANNEL_PHASE_L3(heatingElementRelayContactDef(3)), // + OUTPUT_CHANNEL_PHASE_GROUP(phaseGroup(OUTPUT_CHANNEL_PHASE_L1, // + OUTPUT_CHANNEL_PHASE_L2, OUTPUT_CHANNEL_PHASE_L3)), // + POWER_PER_PHASE(AppDef.of(HeatingElement.class) // + .setTranslatedLabelWithAppPrefix(".powerPerPhase.label") // + .setTranslatedDescriptionWithAppPrefix(".powerPerPhase.description") // + .setDefaultValue(2000) // + .setField(JsonFormlyUtil::buildInput, (app, property, l, parameter, field) -> { + field.setInputType(NUMBER) // + .setUnit(WATT, l) // + .isRequired(true) // + .setMin(0); + })), // + HYSTERESIS(AppDef.of(HeatingElement.class) // + .setTranslatedLabelWithAppPrefix(".hysteresis.label") // + .setTranslatedDescriptionWithAppPrefix(".hysteresis.description") // + .setDefaultValue(60) // + .setField(JsonFormlyUtil::buildInput, (app, property, l, parameter, field) -> { + field.setInputType(NUMBER) // + .setUnit(SECONDS, l) // + .isRequired(true) // + .setMin(0); + }) // + .bidirectional(CTRL_IO_HEATING_ELEMENT_ID, "minimumSwitchingTime", // + ComponentManagerSupplier::getComponentManager)), // ; - private final String defaultValue; + private final AppDef def; + + private Property(AppDef def) { + this.def = def; + } + + @Override + public Type self() { + return this; + } - private Property(String defaultValue) { - this.defaultValue = defaultValue; + @Override + public AppDef def() { + return this.def; } @Override - public String getDefaultValue() { - return this.defaultValue; + public Function, HeatingElementParameter> getParamter() { + return t -> { + return new HeatingElementParameter(// + createResourceBundle(t.language), // + createPhaseInformation(t.app.componentUtil, 3, // + new PreferredRelay(4, new int[] { 1, 2, 3 }), // + new PreferredRelay(8, new int[] { 4, 5, 6 })) // + ); + }; } } @@ -102,30 +161,31 @@ public HeatingElement(@Reference ComponentManager componentManager, ComponentCon } @Override - protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appPropertyConfigurationFactory() { return (t, p, l) -> { - final var heatingElementId = this.getId(t, p, Property.CTRL_IO_HEATING_ELEMENT_ID); - final var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName(l)); - final var outputChannelPhaseL1 = this.getValueOrDefault(p, Property.OUTPUT_CHANNEL_PHASE_L1); - final var outputChannelPhaseL2 = this.getValueOrDefault(p, Property.OUTPUT_CHANNEL_PHASE_L2); - final var outputChannelPhaseL3 = this.getValueOrDefault(p, Property.OUTPUT_CHANNEL_PHASE_L3); + final var alias = this.getString(p, l, Property.ALIAS); + final var outputChannelPhaseL1 = this.getString(p, l, Property.OUTPUT_CHANNEL_PHASE_L1); + final var outputChannelPhaseL2 = this.getString(p, l, Property.OUTPUT_CHANNEL_PHASE_L2); + final var outputChannelPhaseL3 = this.getString(p, l, Property.OUTPUT_CHANNEL_PHASE_L3); - final var powerPerPhase = EnumUtils.getAsOptionalInt(p, Property.POWER_PER_PHASE).orElse(2000); + final var powerPerPhase = this.getInt(p, Property.POWER_PER_PHASE); + final var hysteresis = this.getInt(p, Property.HYSTERESIS); - var components = Lists.newArrayList(// + final var components = Lists.newArrayList(// new EdgeConfig.Component(heatingElementId, alias, "Controller.IO.HeatingElement", JsonUtils.buildJsonObject() // .addProperty("outputChannelPhaseL1", outputChannelPhaseL1) // .addProperty("outputChannelPhaseL2", outputChannelPhaseL2) // .addProperty("outputChannelPhaseL3", outputChannelPhaseL3) // .addProperty("powerPerPhase", powerPerPhase) // + .addProperty("minimumSwitchingTime", hysteresis) // .build()) // ); - var componentIdOfRelay = outputChannelPhaseL1.substring(0, outputChannelPhaseL1.indexOf('/')); - var appIdOfRelay = DependencyUtil.getInstanceIdOfAppWhichHasComponent(this.componentManager, + final var componentIdOfRelay = outputChannelPhaseL1.substring(0, outputChannelPhaseL1.indexOf('/')); + final var appIdOfRelay = DependencyUtil.getInstanceIdOfAppWhichHasComponent(this.componentManager, componentIdOfRelay); if (appIdOfRelay == null) { @@ -133,7 +193,7 @@ protected ThrowingTriFunction r.relays).flatMap(List::stream) // - .toList(); - return AppAssistant.create(this.getName(language)) // - .fields(JsonUtils.buildJsonArray() // - .add(JsonFormlyUtil.buildSelect(Property.OUTPUT_CHANNEL_PHASE_L1) // - .setOptions(options) // - .onlyIf(relays != null, t -> t.setDefaultValue(relays[0])) // - .setLabel(TranslationUtil.getTranslation(bundle, - this.getAppId() + ".outputChannelPhaseL1.label")) - .setDescription(TranslationUtil.getTranslation(bundle, // - "App.Heat.outputChannel.description")) // - .build()) - .add(JsonFormlyUtil.buildSelect(Property.OUTPUT_CHANNEL_PHASE_L2) // - .setOptions(options) // - .onlyIf(relays != null, t -> t.setDefaultValue(relays[1])) // - .setLabel(TranslationUtil.getTranslation(bundle, - this.getAppId() + ".outputChannelPhaseL2.label")) - .setDescription(TranslationUtil.getTranslation(bundle, // - "App.Heat.outputChannel.description")) // - .build()) - .add(JsonFormlyUtil.buildSelect(Property.OUTPUT_CHANNEL_PHASE_L3) // - .setOptions(options) // - .onlyIf(relays != null, t -> t.setDefaultValue(relays[2])) // - .setLabel(TranslationUtil.getTranslation(bundle, - this.getAppId() + ".outputChannelPhaseL3.label")) - .setDescription(TranslationUtil.getTranslation(bundle, // - "App.Heat.outputChannel.description")) // - .build()) - .add(JsonFormlyUtil.buildInput(Property.POWER_PER_PHASE) // - .setLabel(TranslationUtil.getTranslation(bundle, - this.getAppId() + ".powerPerPhase.label")) - .setDescription(TranslationUtil.getTranslation(bundle, - this.getAppId() + ".powerPerPhase.description")) - .setInputType(Type.NUMBER) // - .setMin(0) // - .setDefaultValue(2000) // - .isRequired(true) // - .build()) - .build()) - .build(); - } - @Override public AppDescriptor getAppDescriptor() { return AppDescriptor.create() // @@ -210,21 +222,33 @@ public OpenemsAppCategory[] getCategories() { @Override public ValidatorConfig.Builder getValidateBuilder() { return ValidatorConfig.create() // - .setInstallableCheckableConfigs(Lists.newArrayList(// - new ValidatorConfig.CheckableConfig(CheckRelayCount.COMPONENT_NAME, - new ValidatorConfig.MapBuilder<>(new TreeMap()) // - .put("count", 3) // - .build()))); + .setInstallableCheckableConfigs(checkRelayCount(3)); } @Override - protected Class getPropertyClass() { - return Property.class; + public OpenemsAppCardinality getCardinality() { + return OpenemsAppCardinality.SINGLE; } @Override - public OpenemsAppCardinality getCardinality() { - return OpenemsAppCardinality.SINGLE; + protected HeatingElement getApp() { + return this; + } + + @Override + protected Property[] propertyValues() { + return Property.values(); + } + + private static

    // + AppDef heatingElementRelayContactDef(int contactPosition) { + return AppDef.copyOfGeneric(relayContactDef(contactPosition, Nameable.of("OUTPUT_CHANNEL_PHASE_L1"), // + Nameable.of("OUTPUT_CHANNEL_PHASE_L2"), Nameable.of("OUTPUT_CHANNEL_PHASE_L3")), + b -> b // + .setTranslatedLabelWithAppPrefix(".outputChannelPhaseL" + contactPosition + ".label") // + .setTranslatedDescription("App.Heat.outputChannel.description") // + .wrapField((app, property, l, parameter, field) -> field.isRequired(true)) // + .setAutoGenerateField(false)); } } diff --git a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java index 437c9e9d0d7..b7931c2a97f 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java +++ b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java @@ -27,6 +27,7 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.function.ThrowingTriFunction; import io.openems.common.session.Language; +import io.openems.common.session.Role; import io.openems.common.types.EdgeConfig; import io.openems.common.utils.EnumUtils; import io.openems.common.utils.JsonUtils; @@ -47,14 +48,16 @@ import io.openems.edge.core.appmanager.AppDescriptor; import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; -import io.openems.edge.core.appmanager.JsonFormlyUtil; -import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder.Type; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.OpenemsAppPermissions; import io.openems.edge.core.appmanager.TranslationUtil; import io.openems.edge.core.appmanager.dependency.DependencyDeclaration; +import io.openems.edge.core.appmanager.formly.Exp; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; +import io.openems.edge.core.appmanager.formly.enums.InputType; /** * Describes a FENECON Home energy storage system. @@ -221,7 +224,7 @@ public FeneconHome(@Reference ComponentManager componentManager, ComponentContex @Override public AppDescriptor getAppDescriptor() { return AppDescriptor.create() // - .setWebsiteUrl("https://fenecon.de/produkte/home/") // + .setWebsiteUrl("https://fenecon.de/fenecon-home-10/") // .build(); } @@ -499,10 +502,11 @@ public AppAssistant getAppAssistant(Language language) { .setLabel( TranslationUtil.getTranslation(bundle, this.getAppId() + ".feedInLimit.label")) // .isRequired(true) // - .onlyShowIfNotChecked(Property.RIPPLE_CONTROL_RECEIVER_ACTIV) // - .setInputType(Type.NUMBER) // + .onlyShowIf(Exp.currentModelValue(Property.RIPPLE_CONTROL_RECEIVER_ACTIV).isNull()) + .setInputType(InputType.NUMBER) // .setDefaultValue(0) // .setMin(0) // + .onlyPositiveNumbers() // .onlyIf(batteryInverter.isPresent(), f -> { f.setDefaultValue(batteryInverter.get() // .getProperty("feedPowerPara").get()); @@ -529,7 +533,7 @@ public AppAssistant getAppAssistant(Language language) { .setLabel( TranslationUtil.getTranslation(bundle, this.getAppId() + ".acMeterType.label")) // .setOptions(AcMeterType.getMeterTypeOptions(bundle)) // - .onlyShowIfChecked(Property.HAS_AC_METER) // + .onlyShowIf(Exp.currentModelValue(Property.HAS_AC_METER).notNull()) .setDefaultValue(AcMeterType.SOCOMEC.name()) // .isRequired(true) // .build()) // @@ -542,7 +546,7 @@ public AppAssistant getAppAssistant(Language language) { .add(JsonFormlyUtil.buildInput(Property.DC_PV1_ALIAS) // .setLabel("DC-PV 1 Alias") // .setDefaultValue("DC-PV1") // - .onlyShowIfChecked(Property.HAS_DC_PV1) // + .onlyShowIf(Exp.currentModelValue(Property.HAS_DC_PV1).notNull()) .onlyIf(this.componentUtil.getComponent("charger0", "GoodWe.Charger-PV1").isPresent(), j -> j.setDefaultValueWithStringSupplier(() -> { var charger = this.componentUtil // @@ -562,7 +566,7 @@ public AppAssistant getAppAssistant(Language language) { .add(JsonFormlyUtil.buildInput(Property.DC_PV2_ALIAS) // .setLabel("DC-PV 2 Alias") // .setDefaultValue("DC-PV2") // - .onlyShowIfChecked(Property.HAS_DC_PV2) // + .onlyShowIf(Exp.currentModelValue(Property.HAS_DC_PV2).notNull()) .onlyIf(this.componentUtil.getComponent("charger1", "GoodWe.Charger-PV2").isPresent(), j -> j.setDefaultValueWithStringSupplier(() -> { var charger = this.componentUtil // @@ -583,15 +587,14 @@ public AppAssistant getAppAssistant(Language language) { .setLabel(TranslationUtil.getTranslation(bundle, this.getAppId() + ".emergencyPowerEnergy.label")) // .setDefaultValue(emergencyReserveEnabled) // - .onlyShowIfChecked(Property.HAS_EMERGENCY_RESERVE) // - .build()) + .onlyShowIf(Exp.currentModelValue(Property.HAS_EMERGENCY_RESERVE).notNull()).build()) .add(JsonFormlyUtil.buildRange(Property.EMERGENCY_RESERVE_SOC) // .setLabel(TranslationUtil.getTranslation(bundle, this.getAppId() + ".reserveEnergy.label")) // .setMin(5) // .setMax(100) // .setDefaultValue(5) // - .onlyShowIfChecked(Property.EMERGENCY_RESERVE_ENABLED) // + .onlyShowIf(Exp.currentModelValue(Property.EMERGENCY_RESERVE_ENABLED).notNull()) .onlyIf(emergencyReserveEnabled, f -> { // f.setDefaultValue( emergencyController.get().getProperty("reserveSoc").get().getAsNumber()); @@ -636,6 +639,13 @@ public OpenemsAppCardinality getCardinality() { return OpenemsAppCardinality.SINGLE_IN_CATEGORY; } + @Override + public OpenemsAppPermissions getAppPermissions() { + return OpenemsAppPermissions.create() // + .setCanSee(Role.INSTALLER) // + .build(); + } + private List getFeedInSettingsOptions() { var options = new ArrayList(45); options.add("UNDEFINED"); diff --git a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.png b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.png index 892e5521f52f66bb4cfe2abaea38ab131d310dd9..83eee6547f3b8c9d4bcc98f881c987c327481f16 100644 GIT binary patch literal 38175 zcmXt92Q-`C``1Cu)+|Nsy{XuxMeI!|64c(asNL46y+>=-ED52uSk>C9w$$D;wW-bj z&G-C&=fpX$@Fw@Y_qosW8PAQ<(NZQMq$R|`!68vqQPji1xig9V!N&vM>8C9r4V0+C%nit->|vbM9rFf?ld3k&ykscYl(l2Nl;<-!0=#3U6PvU-|4j z7-~A1bPq5eWg&5iz+U6d0QF99D z!q6aMywDB{A!pogu)Ej`n%Gw)|Gi@gq5=N&dLe0=U5JKw=KiCZ$b!w$cyHq#(f|JE zXEDWgx^JurDTmx7VG`@zSR)dpa^Qxn&Ju1h#;v~7&7-#K;Q$B(6jF(x#GqXN&0LuY zo_#|)89>6}$RrMeWwtvytUsDpz#ml4+vM9)IxIRJg=h@UR0@gm$GzjYqD`Wr>W`9W z7ZTXqRZEWbAyfrxB@jqH7h$SR6q{;d3HP#u5`B+K@_i5bJn07fcWb1y$Txf@42TZ* z=V=vegNb#z8AOKgAumD$W>}oN!zDhE58nVeSeI$YOHp?`(IK-$WsFianJKy;!eT`N zZ`t0Z@P~y^7chzs1hty^^+8(6B}(xSNLkS~tL?6mYjEXzE|V)5glbtLc;m4dLm?ba zWywyJRYdTD4I@#XRM8PR0n8;;vPx7bG}=JFAfLxTx&(9TTiI$V&0fu3P~<&_Lh4e2 zYc!MDh%!9VShSWSoa;`^2jg6XoLvGx^Zv1DG$S-b+NpV$k(Fw9L?lXZiVU6vx|)OD zp$)8mpkj0BCUD1UVhRhCm<7Vm&QfM^8B*9pa-(sxn4QIL|t#=GJ2zlW4# zvs2HU1SX#7sdTJ~xLhI?lMf|CSpL+_!^`^MW-Z#QY8&8P*hNl3c$DbYS2Oq=9Bx>j zbVnPuTm0V=)^b&fjXJR|A^i$nlIoL%iu+d_gsx^+?bxx+9TaC!rXeh7wq!I0UB~G`}egcMv%=(a3B!fpU(;30Y8HBXnD=lmqFrI zXH@j1b4xa%JdG8{U-rzYUtdmsP#AL(6SjZcMLR=qu;PvCE0b$>MEX|b^jn2< z$$Ws$q@v|bFP&LcjE<2Bu|ZDNE-;6;!Hz4QkHO5)jUd;BnEvZUe%I2(`w&O9Wb1I5 zV%}!1==C55U5yzOGAF4L7(b~7in?wAsoUTpFfsgs3B3VTtY~G(OecqbD3cZ7uH%X( z?HVZ%H9tm%sMB{v7Sedb^5Kr1sx2DYWz@U+q}=>)YW{d5*7)~q@w$d-CPi&KX61C} z^vR5fiss3I%m`h2Rt{VfI2jYc)l#I*Q&H#2HG4sWU8QjuvKHNY;*posO)RkW{E*c$?8+@8;AD zCz4@m@QhC!D154=fz7aUz@>nlWO9I@C0@q}R3If!+ZDNuKj9f)xoxkCd_mq7x%^gD z6t}-XKD+Bh9a}lOcGIp)ufJuM`lMY^ubr|Iu93P@>9{~%w3x#7CPd@WS+Q;0Aq%l@^o(LE*2X*AsVi-LRHk-H8|3-I0+*EMKDEp?GwSK+9||!)X1ISmO)2jv1N%Y!ZM} zpp`Dm*OANk9#0XFtXexEPtpd=rNKlqp>`?}BJD*vg4Qpsa&;9+S*-HAxkjNfA6WM) zt0LM0o=MoV?dti-$?GsZuqev}2k(W3{Rw@UNU^jSn@|s$6w0dk*{<)1EeC@v(#lw! zZWKq*mPWp%uB5hkUjQflN{}q-GESpY2zOjPr}u_fNzgDwWgu-X0%5H2%Mo8{o#^E8 zU7V(7m?(pD>h3-iY2<~^AT(9=)*-pIcgYP_^LV6m$A!V=QypFhI`Eqa-^pWMjX$7u0UVhUhSwb9?0 zIQ7dTg6gvG8rWrRC#HN-H|{SrVSMiZXJwmH$W1AcsXJPPk7VR+db(Zc$p(&8=@mPs zr5NJ>erZC>ywNd2#$HuT-h*`>T$+Mo>pm&fRL=#Z0d+;rr4|$%X5|O+Ah$eE(~r)ku>UZIZqJD%?zR_+ zlNpg#6;OE`Q!^CVPTf~>%}ciTFS??ZS;PuJmY~01=ss~!W&VBJ3ejA1=v?m48^=%O zJZfmVsdWTTOCGHp-rbdz-xhg&ez$z`;C^kzmcwbOCP?2^<5++B4^Kj}Dax(wf_5lZu4m=rqxW3$F#bua6}CI@i)>65esZ&n z!$P)bQ%tIyRm-0)RChiv&F`{ogn!rIPPBn5BE0;`D{q*UY#!?n^-B%obPziQHz+#3 zC+616sQM%fJN8D);;6*gJjy$LoFx7*jMy^b*9xoE)cc}As$U6RRCb&G7%;Rs#?!0* z2@V-Q6*8=0blR!b00~N#P=cVj1_+O)h<_fGMm>(t*d6ZL6E1*v3#ki#))30l%2>Nh zcWUq;X8PeQra%d%N$BZ43M}q+UD<}>?dU9paYgSRt_|4E7*(19xGlEFik^{CJePd; zp>I+)#{W`{5Kh+a5r*=oe){(^)`=0hqOKq`bX2mbn9X-+s+z;0I!el0X7+@V9rjnL z76_i)6!@f6EcyJJ;t$A|1W|=crwhtgDi={sA}tR+e7uk2p+l_=lvPw^Bh~l&3D2kK z#pB)h@Y9gS5LP&~oHv9y$|_cwwI%StQdhw@k&kFnSt2WE2~pYh16Y;Jl21b$n)}1f zVm~c}z6Z+oypiexF0#bkgyXYr_k;=(GMnC+^BLAo6SDC?Y%P ze)Fyu@;TkrxdXIr`3XtXgMB|(Q3n~PFp!EO4V6U3rV+7H|DMDt)ngxooKKTa@k?mO z7Y}_0q*aDGpQ5JyQyuwek~91%(4AfBCR8e)&OMuO39WZr-Nt5LK5fuEiXf`=fG{K9 z)4YHf#@u~i6GbB&@pI|J)vFbEoKJ(cXa8p0Jlx&iod?EGIZrk|>pM|rqXUyYeE87) z?c2z&+Ih?Wc4pq*TuHWF?n=`?eY&){Y2O9=8xY)rW~%yfE7`!~vLf_*)3@y(;z?}V z@Jk4vv-oK8npaW72jKk{_LNwoKk%}5K$(rnS>xJ}&a^n4_bflXbtkJ{P=9R4H2JU9w?pbm?7z;yEs><@}BeBmO* zhS30?L1(a34of_BQBYuDEU-+=$H$bql%Ro!wXvMX9nK3|G& z>~m=)1-4O25F=hpzusrh(NwY?#m=HC2ta*09XB6X?~hWO2foj{{%d!(^e7}!TlRbv zI;@&mvcbWpS}yQm^(C(hS;tAW%EfXkjg0<>n1|-OOWsQKf|1Oc`oHXIx}*w7yD6OaHVN z{Q>-`WQ;pkHYj>!#VYV}r=ho+_z{B)aF?_HmlB6*u4nz~t-W=JR93FW_kVs3gN^i0 z*X(+Ui{tSvci&z1z+0tVufSM$3X?t89ufvt$X^R7`Yq_7i~8(q$OY%s2;(&w06-nG ziYr2zScHQhF3|J$-ulXwuJ~!aApeb3X!;H!-P_2?E3S- zx0Go>2=REk4(?>&9cTH(nSxn9vj``>_`){Edl)DWGYxK5LNtr3t3|3*Pl)m2E@Y&n ze&k5|?r%@<&Z4}D5*X!NRZ;`m1HO@yRnK7Yp~!bvo$BSI_stT!kz_(9(h^ z!^rpt0$m=n8iB}yqZ?0eGF}rMaYDF>L5z9H6U7&l_eKhGT_EV*QD@!SFK|-uL4mqD z36okWRi9qI<(8i5cyk*3WO`OX`Si0CoO;eV86RxS^wd^m6nvqcSseF|^QV21)HD0u z%I<;*XdJB#Cs`P{4(7idAA&^az9V2xsNxmlKYAN5FZ~yD4=b>`H}8p4T!RM;k-B z_aeuNxDVxMh%;3@B;S90{M-GxIE+@d{3sA{xq>U2+S70jg-F-O%tQ1Fy0-?OF@A#@ zJHi<%HYXB&-pjx~!leK)qwBeMV~%DAEwCZfYCWsuKNDY3ELeIPYkrN=nP4ZKP$B=q z=d^k*VlK#DoXwxAN5ZG5H}MMs>bDKjL5-(_Ju-@%n1=3w_t*X6fgD6FXl5asC*J=t zm1T%kbBxkF_?&%rzBPt#kQU9xt-)hYD*x$HjG=aq@42|=CC3@JqqzKP<8l|X%G}Wh z`q5#K9#E5y-4E@Z{#7)E%$4duM7b49x4|ysTxM5K!Q3p?ku{XM2FybnT#2j$%0!&c0V7I zR%kH3vb!79P@gI=unbliBUVi=;IV267Vg4`>u1kOK^q%zD8W&UaI!ApZ4?fze8s#s z8*`G$OjyDDN&Yf!aWJOaBUW4Pn)1Pe2NJ;ccb9;wo}OJ`Di0pr?mueuJL1sN()wNZ zW5v}1nfb4EbV6#VwUQ#4_`_&qMo4@?iE<&CQsGFy$uVlf9=my#6szZp`p~i~Hlz;{ zJNv570^&A?7MAyJ_d>d}&~|5MD|;&k0lpq9-DJxWKipGU;@`m?)?uys@W^*kj~!3u z{1LAwNLg$$OStDlZiB9_7jBziLsn1hOl zEQ*hasK^Ppx6loC{&QX=TlJLWnt%0cOvZfAD-kw%SZ+grF?Xmm^E)E-$*Auw(&-B}5U(FV65ibT_&_kJ_^EhEk1i_vFZDU|?XmW4 z>?wm>$L$%TTvr_3$Fs_X8*16xD_(2k2N z&!vZW_pS~YZ-p6T10%5^FO?&P@%i}iqlAo%^Y;Ct%Rycne$Uq@-|J-?!xbVm;TQEA zA*TI?&01|xH`K?L`g$ytQ()`oq@ewkN~fGsHruB8%@9K1xikq%R0QZ*S4@N8!2~+w zyY|ghNY7jVC(T3UM(-oBT)B{_DQ7b779F_owv^d8o>>{GF8|z5gE1lFY6KW}Ks9&4 zbo<0?6x2p1FIpxD%sXvDfJ!%p zHkH$!JHpY}LVq6C5?yJaixZ=r}T^;d`WM1ZSIt}+lP}?qoX?lwXcwHRw#v*MpVinD;r8%w5ICRMWtRLX* zdaNV(*oQjs$ALhpP)*wtd`bQ_hV8S>wuTuW#{sAId=GW|^}XFWfW5r_P}zQdDIW7=|xf5`|Zk4CsM$v(uHJX+3oSu`j=WL+;YGEI7 zd+Ky`Ar}(L#qGY=K`?*2$Plj`@j~=L0j)>1eH#otuq&o%EtKA&Bf2$=DW22txqZvP zww8Z5tNJ9Yr~+~N`~saeNv>y3gQKObeK2L02Li?HEwm>{`|f75%EWy_vl})ssvb65 zj$>tF>KARf4o@S~Cxh#H&0(ZFA8?dc?%b?u2Zsgt%5@H8xCv#ocXTY_`0nK`o^_L| z8h$|%jhE^ug9^A_8gP~(!X^aS#NDk4>uuz}Zd$~JSwAu`-ppyB{EM+>%$#4=Yc>cG z9}od}1)J)qL3T1EzCNF+j`ArQ^-nCp2}mKwVJfhS1(T>cv!aep)i8-@-E^w{g1g(a zHlMA8G!FI;gk9(9nbllbG+B#`W4U;A(%1{$f^=+|b)2%zO^bUih@hTH54a6XOxHTO zArxtXsYMe;Cq>@=TjV2IYy@q$*L&i5bi{bPung<(I%v!yBH^!EjxkyXi;(?Ah5JA` zgZDTJY!>=(2_|b_MALdu79x4axe{&3I3AEKztJVN964t-*0E`wmBs6iR(sO@;gyr_ z`@V92m%F~+(MjwlfkB?WYsNlL?jaW*guc}8Q$dKXWSz$I#)J)Pmtr~+S4)7 z6CXrVsRx7-cfQ3hw83qGMU{NE(c>PFxHQM^8`Zq?u8hLwZ`}Lvx*Nj1cE>-;KX%5R zXM}55x58B8ucnGwXaEk)Bdn=U3HD&0v)V>=19T9;A_rIJMf0#1d3m*qyoPeY&*M)v)}Q65%5u%02<5GZxbKR745##O@f8M#_6v`K8u2K zF8{ok|Ih0{5{z8TuROKtuU3RW&epTs64?kCp8JOZWT&R4M%^e>m-4kJE%0L29P`@s zHMu92kOTV zyl%|oommt+-g&Y%R5qCnyQC+Z0FXItbp@O-v|~{%*F(x}9_LF1XEiW27D9iS%V25w zUHS&{DoQ-E^C{``>SG45p%&uae?cOi#>r)CDRjVExsQ$?o;owpC7GQ_sw}Ty5?zM4 z6W(mBg7Y%0`4fDfpx^ygH?&L!#JpdCGZc~g9UYRwLH|VYHAe2b(XDxv!CBh8Y+O`a zFl4Fh|8s~!dcW{^aD(uSWx6|itcCX`+Megj^^mPAElRV1t&qOLD#sYf@l+tK-h z81&v6{UKjdbfSG)o+wMcqhfYoliJ#S6-e6I&D$j043Xn9hy$SBNLDtNFH5z>=>xbZ zp1fS_Y*0^3<%20&Xe_eNC^0wVFs1-sHCYs%7Fo|Xpp*v46pbH1Iyfl?Z33QY_czoq z2XRR*WCd{w*!y`O(fzvKXE|;)bIVfKH`Q|wGUI{heKyH~wjrgm2KO&coH{*X0XW7t z0O$-^R|YI5X8&{$^Q$cCJ7%?W)JEGT=zxB7=8UXe+8B^irBzU9hhf*lAXuJPv2G(i z$MM-rujKsdWxo{)nvu;E#r4*gTB~6CU$T8~U4XxT^j_QL!}jYPH{l#DK(%py^Ck?- zl>se4LRQw5hhFzLmt1;%J#FTP!nvWu@3Jm=vUeBIzZC#bs9%Nt8}RXd)ftC0<5DJWZN7C7Op=7}E)iX@7r7LB{_W zKY`)-kL$}*kJDY)Y>QtFvonhXVQ}(BK~utvN%Q$G>)cO?_=7p$g?v7B0CYU?psy@F zPW16mLj9*nMGs9BlO;axdz%*aH!F6!K%$QFfl-oA2Cojo&<5V5N~23b(U+$#qZ@>g zZh~muV$bn;G%~3r!XVV>@sp(0GF)N@^nhaBIX^TjxJfY|tbpq=y&bYOA zjeXTVz?k3C0@%MX;8XyM8xv$N@b}tpgKNa384{A%2!K3ioDTT4z(z$&3>{H*?eHk( z!#Mpy9zJ?RN4~*;|Jl+SyXcW>6Y%+7f=|sQ(3JUlRS(nX=D4dhlZ=764VDL)SDVWE zEtvypyy{!FYrBR!Ub8M`n!9i2q0jqHq>wDdhvRB^C}=wp(<}EAO}f@LWAJLmL`=>% znB@Gl$fb;hf^20VL-645)U&I94eed+H<C&kdD&{=sixLKq3_3y|Y#{FmI_ARy^eeE)%k$nxl!Ra?ee(;} zkc_O6jenETq>aP`ZRSWCN$x{$=`F|NQ|nbk*qa7DObxJGW|7lA)4prxxygwLxCt}y zVC14?asIAqg=op1Y@OfByTZtT|Mdb$I8cK}xMWcR$1v-A9u69Sc3r?Fl%sX9K1lZ; z;}Z-%AfjhrSO!LQRw#GDdexVAMbLKIy7yX!0T749b396orHnpj@*3%Ht=0!$Ux4D{ z<8g4twkTH)8qZ~AG<#2GY5(KMn}j}^!PSm&>m@|qNl1miGa{4{pY2mn+4wMSrqRLv zQ}u8{Q6t3A3ua$-%|k`}3mzEJ{&G7u_h&lpkdz~$*6tI zMgYUiXfhOpXv+W28np#dZB#5htV1D%^M(!%*VJ1A&&k^_CMx%RLr#6)-d>*`^l|1b zt|Z8H1A9`K{vr0okU$4k=NVsXOWQC@w^8_AO`3wL%{LT>TO5ufJ7NdF#;brt4;ENoTeN|| z4-gg{5;C0h!cvh@Mus&WxbwY>^cha+d&(H)vL}He8jjMuyDzM<(9MUA&l7J0cj<3k<%DoA)tHHzrXFClV{$#q9O4JL}6D^|zkCakX_|<05S_>tJy;QeMjgD!bG>m7rvy1#Eo z`FoK2KwYt-%TL%|fRFqkf8C2>q*E9&a?d;VV|{Xr$$QeR5kDaG=G%f|usrvZ21lds z9)s-FzCz3Ce8Ah|O){*Kf|U}1q=vK$=6_p7@w`&8iqd|~m)uv_xYnAu! zT_|+Tdlj-G8_4SB(toU)+;xA#9>mCcD5Ya9s@;+AeF5<5|5E2hUP!$8&0&Uwq+|yW z@dryE8JDk57kYqey5zy?@-1d{E0y&c$F8$Doj70&%DEewVcQ)YcK~r9&irbb8Y{K{ z5|Dmc_nQCO?hCVAEgofl_D<@T7^2DHyNPE9^7kA?jm)V*lgf1{2lp85;*a!W5H;Nr zk#Iag2efa>Z(-&I^BCF5(P}PGL}kyB<)cco)*paOy$>+FCF=Gw++Bd<^&~KgPvkMk z1;?hOPyotN+F|rGD8)N24vHOBT_^h!Ss?1=uD7%gW)^N9-~MyEjQ}Kj>W~BM2pxH5 zfR3UxCh{AZi(=zVk1Y%- zyVP>IlRm~;nr$>q>~3+~M}S0q!F5L#vNt(r$%JtgK#r!V$_u%Xl)e1>3}AvXZ})Mq zVsqrbrJo-ggD%B@;*w-zOtJNgSM6k?3#-anfKbm=lWp$?0Glf9@|?Jd}3O z?aD+L;QeR|wUo^a3=kiVr+XW`p)Tz3HtXtj9%(a2PjG|omMNHV z$t%hGHa!@v8X14?n*hkKzigiYPs=xuS$bH&m0031#tBN@pEkfq!Hu3h7G-6BBj*_s z2peBZVg_IAG%PG*OZ?Zo;Amj80OrdIFi1dtOE@^_VO;t7NKVFR^+SLY*51wKd1dTY zHyC}Y+%-AC$VcGuvMP3RN|xnc%^x3c*+LnEa_0t#$JYiWq?GJcY9Trsulh)se^r~RHDaYyd8v}VZgM)jB z7An0>MFwSA3K#C@CQA=*PJ!kgK-4p9^$)LXyC9UioEmaJTGtFb?BhH;lDqZ7UI5@^ zS8c_I;n*5ei7ngA)1cGuk)A0o#M7-K7aPgRQ7bfsBb(V$#oG!{O^*y0<8;7V14h^d zeE9m&d4IssIsMnKUq5MmCCOj+n{Ro9!^z2cwjy_96?nGv5x|zbkdj)d{w(r$g60hf z|JKMI^nvJpodeF{Tql z#^zC8U1T6#+arD^&Q#|*kby(snHM@>Qp`?*%VHD8Xlk1A+j+TsPPg(2l_p z;y%T)wJ29xT#BhFoc7*;67Y)@$KvQ0w>NvY@iIfgS%aA%Z5Fi)zSizEXZswRG8Jx# zkJL2zrG^*uWc7x}7Fm$|iz3D5>`>^&SE+89KRT0C>tuacxH)7{`sR+W?*lJA5@xic zLYmX525Yg*(82MRejwmY4PJb8j`%G{L7rGV5uU!P;re>}-!mHG-&E3xt#;zvCf_s_ ziXYA6@lP8eax^2b~8v zfEl5k(Psmo%0!&H&JpVbUwXi6SR7!=N+1%@U~u$hY$5-$<2>q1%E z$+smn8YKw-g8{MjsW}U(qI?qHys6lIB1ro^D(36B{m5*!PtBgBdtJtur|Eh9RrXsV z@v`)_jk?Gg9jdvKb%op@#wN3fg0zX;-W_$ax#|sP^d44-f*vaJQ>g;23(PP36=UsR zdVomz2@amFbs;)m^~8`EsX)|Qqtde!2QpZ|-^?nbz!>fRJ`q*&Sf#>}oqYlr{A>wi+L!x1`&V$TX1+%ehhBelBtAp|td zxw0pykYm;$O&OEG%X_bHmv6d;R1ek%ma3T|HV-Ru7%NFh)v1Pn!|Xa@VDxe~*m zZd}gKV&u*-7i+6~QuFl#_!s`0YSh6q-}v480M+N7H74u6!Hx+!&K0JCR~r;Al?-O2 z7nm!x-4-jMFd=uHP<^>qW4UH6KhjWSRvI(~5Q;lrxRq6tzRRdJAb5_Nl(x>=0f3H; z97Wx*|Jd>YAhs+H-TWN_@Xca*oA<;^Mc5=na0OcNxC+q3dpNhK>ZWhF(`cxdzrCPb zi!30IRKr=*OEN64(j#V4ilZqVV}IYRQD(SdV1F6r+dBE`PWc;M?&!O^l*~h3f&ZrL z&N4#IsjmPt_!W2m_bggx+9<>vk;$W?mMKw9fKX%MDK@2rp7yNDbL1uW8;1^F@5&W@5WIw#0db6AdaT-Rq z6pzJ`v3R$%p5VuQJee;b?#)71{pe(ikhS z<}bQz-5;8ReAKv{ih+;63|FHarR56=q0l>V(Z(}Lx6uTD71d1IIF~GL)fygzly)N1 zzG(E(0!a$Kwo4RmKN(beN*5igGx35_IdY|I56DmPmVEXiX08qHxXPtK7faaLTrP$d zLcx!c5!((AjN5h!84?Xo+co__FCl)vffh3Aal*`!%R*&9J=w@E4w z?|pv!m!^=97iTM3(Mqv)(&V7;Arac0QgG5%H(x#qE}qaC_v&$~ zJGX#&W}J3Kmq%=~@7^*{awQ}r?0>Hvtn>hlHox^gBj+2lNcBxmg#7rPjIZf!wgtJW zH=&SCS6XfK&`>f&zMa}8Y;VjTj1W-qR&CS`x2^DMN{>e2Ez^2K3YB8np0Y$+{%6@H z6$x)UK0yGIJLbM$9kCglyR;ZjXSoaURb>oJb}9h}%BXO)sDgpuRtu7p)!3O4jXn%k zrBXie_d6D(`tfzB@emfY^;AGFE!q!IfUxM5GcWMN!Q0utmz$e_SXNGnz!|;|ae%}J z#!a9hVa)xr;7k|m#yGLJSobj)!?NGKf~Z8td?_r2A_J5LYCm^;2t)v0_p-BLKA$Be zIQCrT-Znl2_m?f`utf=?^l3;fJ7-WRZ>{%;9Gq*FyHScETnIj;T5goj1zKHl2moK{ znv6kC_65Ds3mDT6%Mjhv0<)46D0T0NTASgg5^f-inYFzAcKDf1m39r@^jZObLRqm$ zDOz_B>P$@hNDG|%mCNP$Ayh(=1n)n(#u#)=hE=u2 z-R4DSbS{X@L%hNGPK0%ut-1_OfM_HMHXHh6@Jz6S3pVCbRIRFT1k+7fAH{Zn;U@Ki zST$QM$+8Uu(fZV5BJ=!j1bXY0NZ82C+JZy1Lr(I}CPJ?6q#VGqvuE!mTD%qgJ{Z6n z`$)vvI97B(h!&vWHoc=Xp|1g*Qdp9;UzoPIx0+_fBPYwJW@JP3bk^@rb+4WvX2vS-zvm`DVipKpr6YuFPndAg|-$eI{*R7Pyf<_GM8EvSX1a_`DE{-nWdb2_?C8a+L-u|>4tW~7!dXcl(my&hXezP9ZaeEMQ z!yQpbcIcU8`|PtuVoB&)Q8V#lGbCC`H(bIPxugWDei?Bd7!Ci#s<{GI{mCP5P;B3S zC5ZOJIQ@B*qxUMA&0(CEDg;ZO-paU+x|HsG(r9pSqWgM15OPX|`ATmH*gl#=l1-yc z1Cdbk2L=UdB!7MBsm#_zAG@Uw_OmCg|1T_I}C_v7b{U6s&fhPZFTw3N$h+yW1aK54}&Q zJR&WCn;^c9%jx*fD6aH-b@}VjH#5c_>f&}f<^z`{#jyRp&jvj04V2#%FPB~_T!D0Ah|$aR-mjovk!Gs*kkWu6(a=On$$z{v0|qs{oO+elj%5&L>8^aG)829 zVD069R+vha>**&k_Zf~P$Y^SS2gJ>UyKQfFx_~{1;*Bn4+-2JKKXT^7lUK&&FNs`P zjGnbB%Tbo5mN*>U05o@Zt{F|a!dbxHRZa$<i_Xu!Y1ZdxpYE|ykP*lS`b)aLJ6x9TJ<%YQ+ zTB|0N`kLR&9_xl3yQj_2{?~S7+VCaL!1pJO6c8JaLe3QE>FGsrStAXa{L_m17g9QV zqCQhF-DXJr(MdGOY|D~uyo4YQnjLtXHRHNrO$|!B|1k^g1)Tb7hi35uz)Z)|QlL}y z==^H7b!AITR_}=FZwAu1&CSfZz14K|e8qc#xD;i4qnsx>Y-qAlo*@V0rp{9luIobJL#?b@gMV>WCB>)}a%U3s?+t@z7_ft1{!z@w4#?qkwlp5AotfO=6MirZ( zCFQ8pLoeK#rqoildu>Sn&qH%axq(dSKcj9~)^*=#7f!M~mlerP_(_ev|9h>#X`T8b zBTW*DKK-_G;)v-I$B7mJm!jt1-=J~$0**3R7!>fLYCU)wX1TTnGETqqUCpm!_)AV= z-}A5{$!{n|as6xr)o)mkvTL z!IeJdpla`my}G1)Lz*9HW-Ba`6)goNS~Izt74G5nAx8RMO>s(|ZHIX;MMc&95N%{xKfaB5rP0o8 z?T;JUC!VLz1e2L()|=WsCiJ3eKE?OpIH)1G1r%=b=#iO_7FhKY}(q$LtT zN*a;LBmNbRt2}fG&9RkQpwwb&JG!umaHv8q z1ziAk(=h3y*?gW>EF!9J6k>4CC=qT8*?eqeP0)CgTZ4t0x;L~&tIiogV?Kd7uk;(n zD?YxMIU>|GQ2s2gi6%6&SNdKb`*rxlnN#>k^a-(=dwNp@zKDotusbm%skM>%Q>(m*c76Vx>0T3}_!%RF7SgQ%V`; z6>>3oI2ErV^1ZrWfmReat7?5JuLCfQ7-w6jm)=Ipt_L6E#h}W z(&;;8(AYPii7bvy70@$j3sx@+L59-{fZAbOW2`@au)h!3%T^NfVohy;Tb%7NQjh%m zjCfVD8JWW7g}4W>%RkQmeVCFwks{_z)H%=28}?j*{)vo#!{MuJTlQPj)57vGRJDHh zYfB#JHK~w~k2AM^<$kf_+s26Y>r>HZd79DuX(QSZ4F;MmxLwXQbce~B>{leZtwmlY zvkm%cjnx{Pu5D*MaG?T^TwxnF0mo@=AVZUrNvrDk8T~^#tM9&=bsOfF0wOBvhzRcq zGYaekY1Z+;3_?3_3PdEG6i3v+hF)u9LY$aW{#OBas@c-WjMg3d)cQm(g`6 z$7-h-gkM8t1}CQ=dfFBOiMzu}6^*%Q9EBC>{#3_vOljh6lW19ip#{zl$k`Yda1pln9pQm>MZ>aeo<#h7UFa%FFu+hBR?V07g}X_1 z5bfo6bPoK2-lQ?H_)LZb(%i=)wfNup9o8fV*8RZGbTZJV%2c1&h@WWV%-e^d<=5Z!zac|k%IZxb)lH|>?Nh4}e>$ZU z7cqUbo*L+zw`D!R;sOB``;^6?%{FU85>UwiELkI;>X0c07aOQf13`Dw*Sk4FwEQZe z#=E-YRmYZRcl)r4o;n_ho~-K8%fV3DKth2c?z(@Of9qK|%{BcTL!5R86L;kGVgeo&N3U4+@-mW_c8F`^8;}hIXtux;5`%@JhpzF(uvXPeYrN?=0@` zSLxSBSjXuwd;cpSlj!{9cK91YPWsn4=cCB{N5aY2z~2$b#MMf9dpV14ku)tO?lb^%|!$ z4;`h+m_8W?pQhd-i#c5o?%`PYA08Eb>wfw=-3HrB545YbRYw%zb~lcjt6UqJyS&=6 zo!tI)k(#cQDexoZJl4fQoHl+bvC-G!G@y&W)bvIgRLBT)7ztQweV1WZi}BunUV~*!RHzT< zVIH2IAFzG}R{y>MB*=SMa~ zU)=yH)s$gSFIY&#y_napuBj|25naD?>BFp&XVO;M7n1T@bhL3i_g=IVhX}hK+m8X@ zTu15s)(k9b@~Dv`jAro;@bQ^U{0HcfTU%TGZK_jR92CTO_lg|+f73Yg{&|fRCGwr( z@MN3bEd=a6zgm1CXMa~Z;%9!wi@@mL=w1`8`4+->Okf?CMO>)@hZ%puL z#DGUFi}kBzhC-u@)IWIP5``O9j@*4;8wFFkbLr_?x8l@q#L8);=wpeli&0q z2p4Y}`pc#41vAf>U2Hh%EEq)N`+NwdGGU~r8*F}&(;e!gag%+R9Cg*UZ$PUxPg(Q^ zaFkb8R?=2l0aFgZf{eju%-FB(u&}_uf#{Y^aZ&*lixpEHn#~l=+1$biekd!sDAxV< zd*hK%Pc|&|QUN2z?Z#Tx24Wp1ZmB;}GSW>L_8?6TUe!Bu0ZZ2oJ^ZME?RHX4Ufh{! zc>C`!R~$0I~wk=qz=` zP-{vm{dE0l*j?FCG+{XV-TgU9MO%Q$za8mACax)mk00{b$S(Cz&R3a0cb)T(YMo$( zyxc)=0;??7e=#@lEmioB-rXH8S~>#`bWud=?7kly$su@EAu7Z&Sa$ zb(uyJ^Q=wK0v7GEbZry;{`TJgdVEPhwo-QauU^!4bsX-Kmd?HulxE+aYxl0}4`>Iv z^CH8gJn(Vd(nS&mGJBf1F*vbiHz(DV@%mL*(ThHip;>Ri&a4YT44EN*F zmeEcjFNvKCCo3`ZwJmnkODL(`7V;Qiz-d<|`Jy zJgsfngOk{XVk8@5H-2OoQd~^e<6p9<&QOmy|B-uTun+po#a*5!ZdihLaiD&xx+shI!u0yqnjyL7Uc;=6jf zA~}v-Y{?^%?&ET)!u9 zvj1AQBjWl)#kbu9>(>+!z`6fVmY`31AR3KgEVjL7*RSX)?6BCUzI=3Eo>T)ox`koO zYr$CX!ZW%|WXrn?sr^8t)& z{w#*^Ejl4hSv^8ggO)f042R_^8|f2BK9t7?EKU%n)D68+o>RyTG-Z(?#T=%lQ}Q2m z_7i|*Qx2QFyYTmS0>OHKFdd&Su+lY=jI7uw$3o_x4|z>YeL=?uO|yz8{R@|q>4W%V zK!lN`e(0!jQ(Q4Ms&ipxR0tgR)u0K_oG8)x4#S{MTnRM@z zn2-d$M+msn9W+g9qJ!$!GRx!0fV(>+bybMwIEa5DN&l7%wP{q2M2`7t(D6nft7Imf zBpmwsIyoNxgvh4A>S5y585@qx{ZGy6JLp941|crd6T?SQ>g)SYzR_OLch;yb7@#HG z>jIC;re$fL-~p#Xd2_R&814M$JS3ObfyXCjCLt3+ZFrMRKPeHfhw#yORZFPk0LJGJ z(jQs_L7nto{kGeqMII^kjwFcnv1VW0TCorNo-9pEU^Y$KzdFhM@&cW5VG|t**319+30kbUXaYr16G}MrIy;o?eOgtR3rS1@s;= z`%k^yN8dn`)6B|3wV=J3qt>jd#p34;SH%eQ1cxg{#wT8s$QzNZOwarvK^O9ATUqNF zwz;$ylb=;)dJ8m}U4HKDvHnD!8lc|7lA4cOx;+ASl@0Yr4-MCXL3dAJGo;D8o*`=+ z=cn#-az_Hd)c7mHN9ECaM9`v5C&U`vS=&uYme!^~+BX6RhZIkh)+rtGgNmo9!zUoC zE<57zTz%7c@S+C>r`F@s7GmU-^OeH(yxwU2A5Gr@ zPxbr0uaYt^K19(@On!T$c--HP59lA2qA`g!1TYuYG z%aL1em5n<0#e9|E$gFpzY?|{;>4j+3qfigw zyUl~K6c(Aqd^4j5P}Kwy#hvSK7ymGnjXH`_3t=p!!p0mi3r9B9&MS)7RI(LjtG*U3+^E7bM;J?Y!>z>lg^yI1!gRv&Gfp zrNyqz(0U1GZal|qOmB?1P`J}Wosq5aOOr96@RCG5G#cD&2a%1e?Qdtrw}l4`L-W);H$TjTO2|SxrT@1 z!2tCcU}OOTC}2A6{n@i6`A=c>sMgOPtRFu72T{RK!QGC_Apv?_ygc50SCk@Zx^Nd+ zwBc3mf|4*CFBZ@Da2>tJ!N4T5VB*mn^`0ruW+LTn!gvw+J+W|k61KUc@ku`O&3Eu3 zvD33M(PGIxvR)S@ddv0{*Jj13{gz%=wl^7dd!O_|MTO%0?np-Y?Eb*K;nK?e1#)RP zy5#&k3y!G+C>9gK&t)IuE|o|%znf|&TL52qgdWhhyx6<`jTdq#TS3HpCpciRX)6In7Zj-g@>ZD3HXjxdTe&H1N5jj z*kC!E>+DHs(Yt-Q=3h1 zuKw?T$QiUseW3z2%vy@uPE5%Z!uH758$yF>3Z72;Jg!7 zjWS=v!=K}bYfQ<=W4fXnJxHOpFb`pxdIcnkmGa-kY@xsx0C*z6P$xtIo0>cM^{F_x zq7PqbV_27A&TC@yEs>jw&r$oahMrrAPEK!lK%sqtm&6P!dEfAArOJSFcZA?mP;P z_wTjT^-)TL1Ycmuz0&3p^-^+Y;+%2+m`k~JO`@z4G^o#iTw@VO=&a-!m!ZI@{Z`e2 zx`)m;>U683_xmcjRn9V% znmE(*GtL~B*Xlhe;Jo#wql%U=>zOO~RyvKWci~o~K=zTON zMYAeO{1SkS$Lhr%cMd~+-xWyM7yze}00T``;6*(;R{?!YVD_dp>n||Q+dGX4xY>-lfAb9=B z=u=-`1adR52>u~f|6gu2P#u-k;vsw(=F?363&L>1`oz@*mq@y`?%u1MTmrA2t`SSidAYCgg{f2TBd zVoNLdpv?UBCZtLPIYQ8AjqN!?c4=V`e*5lJS381*@P#6IQdJFsq*|0XJo?_cCiihV z2wo=LCQ7^Q&7$-%UKXGQ#;o^dQobOoID8AT!IlRncjJck9k4EApnJjT*q%ur9G)XZ z5h~1d-JS%?*%wFEp86$DLt?7G4z3mul}$aqF|zv8SFBo?4X3!e?=>85Eqs`vErigI z$GbBVc~fW`<6G%_Cn=)O+|DgIHeZWiUi`+p`XqwdGEWzR3NQ`#dc+DLW~Q5hY^GoA zN!K9;B+2GZ@|m!@oIJjx$OoGRaW@sNtWdvP9rh0DJ#{-GNlS`u(V+HekI77 z-^Z5Y;d4d(y~T5m)VE=LEiXrgUamcCA{T-`=-$KFoM)@s3+E@{G=a+6unCMc7C)QW&ID&Waj-?xVPob(m3k$Q+;EF z|HM(LK1m*u%Bie}14YSs%%ahSjMjYq32@$m!MOWtqy*!8k;u$E0BrWA4TWOBlGNvA z>jc?MP<)BPuF_3%HlSdRk~{~&9i&vhrj>Y?W3^dAeslK()xDkGEI3n$BQ5*fWahe^ zy>6T$Fg+<|B%ku0uQKJyhivxfttb_#D4%E=DQmSG>AUGEaKL(6RjpLk0jV^h**}lK zurp8)+<6Yy_9o7_cUZ4@qrPzewwQxO%~K;il@ZIYn!Cq@;Pb7Zu-c zS{Z83T$EU+@5wIfDhex87)Z_qcdmji^4ICowm%td?S~=7FV|#uGzz@O%zDpT2HnAF zLhH4BBa?tb0}mLQ-7scru0Mjob@w$c^J99jI*R26i+YN3HOD-=@QR>_L~RwGC$s~t zJcLKvQnM?kW8S+lZufMmYcLq0o*p#8Agn@M9ErmWjozzw-DjI3fDkwMa6}nY^#}Hn z*`4JadBKVB{A;3O=dE*4{{ltF0RlPQoEaU7n$dm+bgcW*@b0T4R?kv5>3*YM*!%T{>J>rhaorUrRV z{ZlEd-D;L-$c&FC%YTSi*VnV`LZ0*+vk=WqL=_*+0F{qV(#Xe|HOddNhQR`cY=-N8 zMJL?DQI+|94+7h94sPFRYC!6dsENOy>ZYnKV8*q50WuNrvY~$$A1wa)(@4uFcm&7g zkivrE3B>QbkSlepr3IQpK#Yv{7UVmV$rElO4Jg(1Br{6?OeJFTh#mPoJ@W%kh!o*} zD2(%5x7c_8*0wC9;R8abmSWb)Q{pA(W{-WDwIEN0s)6mwfNN$kFu^bAlFGD3wIk;As6?egykFEyiXov#A%^2j+D`%i&K_T@+ z+Rz#sk-!>cW)LFsw^4+nFg{y8^?7O7@Z@W4Q{G<$1&2O8yWI_|OWdiPT}gthlX=S!}QwQ9OHAHi5v=S`$g#utx~|=1G=_;E;%`}=ljd}TWs_i%f4ZC zlh}v%o0@#Zs2xi;NLcJNk5RoAk5To4tFH5_XKsdCkwV-R+imCijR}ez4_(L>MCj^~ zyuk+p61XBr4GBc&#Z1!(n$6Hnhmt#{;5*loP>`Vo`AAhnj~+dnZbpBQBK|gCL}|sf zewF3n6&fX6Dl4}!f&=41_6bh0RV=VN{o~VSaj_4S%6PRUr0bOd!>6N=nQ_Oc8GYos z_QV4dl-A=_aW)oqNIyilkhO@uk(=n7Lv1AY>VedPq=oV5uW*%IYTi)RS+t0}1+P{5 ze__cbEkpWS9ll!FXd($PBYv;+Lv|ihS6dvvzsI3O-Em5(Z%XM^8iYlA%w5W7da5lD z=|swL4D@!Eyl$qWZOb04kFtqRIj!!mahv2itp|MXPEc+DmFP`hVH#1pz6oxOi&$z| zfROx#&4G-5Xlr5jQ@zIh%m&Qoe;_0bEEm#6ky-UDaRdK~fk^Jx^*<)-y;C<6YV^%} z6xU5DQdKn{y_ohg60=`J+ATE;P9LT68b6kx?bzsNoXm{Z<9T9Ly`u7j^4pi8r2Q%$ zZ50CfUKdRn?6j{yQS)@sPxBZvy3mz2P+bG6wP^$0;fAh;c*24K`za-_titpmCa*qtg(Mt76hxz7M;#ws6QIAeBUl22x6^40+U&HqXvkBtogvM+CW zU~<0#RPhXz;G4GTkOh{Ft`ai^LgDeR*rKYJJ^p()h0tYcp}B3+_0+SGA1j`#7$&?f zCyqcjqUch>tt2VJgRJhWr_5dpG;h@JHg)sCZXdQ&fQKNT+%G z2QBFN4tlw2=dMjN{<#)PaKbLFI)7*gIb=Ixu;4XAFe-jLvEuC9pMeA3Tb~r|g=x-i zu)%Qfh_UoIge0;&BvRq=#yd|Oz4G*F+M5Z2j~&BLrv9a=-hUqJBi})i*Fk%R)2pe- zPro(PI{LR;yMDy>oGq8#F*p5ul&T_dZ+N(5^%1+_G!@mBYY)zPa*dYEnNdvCbz0dj zq`qnZ-Fm*^F@qV;@2jfd_{G_kCj{$(IX0=i#mVc3+0fXDc>5bap3^STE#yyig(`IQ zNS~WE$P|*5f!n{k7aiNw+(RTs@Wr0u2tw+gZrg{5L;1x65Tr1~MQzwG{3gG$YtEM~ zls*9mIB17akyGa%wMmqQL!^oYs=>x0A^1|1INFpGbM`*4dE9g|&7L0*2ZBE&r%%R7 ziF$dZQ^r7uc2P}R(-&Mz#;rs{ah3FXPIhh964n79v57_@lly|r#cp2H?W+?IX;ja# z;LxXH9y!!>8;iNm(8 z%@;pVi&DlXxihsk;QyI&8uh~bB8lu551!6c+N!O_F>|+mDcaxoZWTUOxrdXNZ2iGY zgZ!x=O3wEPpPn>7%fUY72_~JWjzrTVJuKbE})xLLp9-UoM>4u!$-j+qoxS zKfxaf;!0GEEZ@0ffR6VSq$ys%syWxB_HDJb>IchCB&{rJLvjBR%ZXV~tJ>GX#33gh z>A#j3cQfpfs116mj2J|pZ)k?$9c1FC^d!>G z9zkwXMvuTy=GP$5ukR2jyi>JMmTKz4IcHRTF^~{~t(oSD7=q+0QS~<3oUhe6H3ht5 zP_e#w#PJ7i7nhp{&)i)mpTmYWiu;}$wwG8xvF=a{0tIq|rs()=d@@p03YEK-0)uza8`BggSC~UiUt8NvGKAuK%SgnOl=jvd0gFjCAwvHB) z;q}#+eHVn17dpG}h=X}HfA=2n$3iQSM4-_7CY!Sgqz8-3RNLsPRZ}-(a#Ln3d=@U6 z2>#1|sbpn%RC04Q;?Q}bXZVQ0C4#>+-37s3y4l2{zC*k4@J_ah^SR8>HC&+@_<^kt zsGdK>dCnaM*Ra+M)ByA$+R1k#^AG+9OzEG_e25FG{Pr6plmeMTK(6gS`QHbAwSOY&GZ`Id1KoqdvGuxYRBzyp$cQKL z4>8)_S3L1`3UROHGzHTVUOXJ)H*_+ON055?3V8luO%*dd<(W1)bW@RH+Yg%(sFG;|Vrey#+pKUiD|m)?$!*NM zG-t?OJus{E1m5(kIae8L`ybs?)UV=t+vdw1o1VQR9o-DY_k~Ka1y6zN9S{}Y#R)$K zBp3UI3tvHV`nW^+K6!w9f%ZirKJd&5!wGpeE8DxYLiZ_v2D+KWQh-j+z+*wRj=$eK z`Kbz_q~@nl*42%)@@u-ynbMP?_k3^p>3l)x_gW!w z{&^;b3Xt}mI&QGzny*Mo}KL5Et0kd!Di$`zDj0fGnQHRiXrV4{Gp zfT)I{jCkD;pu1G`s@hKCC@jOahV&YjfPjtjA;XEFeSF4^zGpMk-IbI<81ZTcw!`6Z z^(_6_6lASR9rVF9{z*Jf3|Vaij{bYC{NjCDaa$c>W?s1{$(oFFg~?9rAas*|Df9PC zpO#C^&sCO0{6aE&)1@-@Cui!0@)J8O=9ZccHBqmF>(_#QA5vl-v3kw`4+r7O|h1|oBo(ho<{zE-rRM6`Mb8Plzlj*l3h{|{XB@n9E zb*oLOMzDeMLW4q;TB6|oAvvedc^2ncbq1Y1)o z9k4TGf(nVmJ}A=NJ#0xVPkS8)?A+GZc|f+Xc+8nXMRCJhl3@Wbp1;gLk5ET%u03*+ zk0WK%$CZ&9!_7N6Zaumuz0c9&N+dWwyUON*igTvV{8UHzAl+;1Z3PBxPL!#ymb*G= zW|m%43{ASO_kuKCU%!Stt-SOV`$MlMUa3oGJHZ#(FSQ0Zcq^-mJ28BK;IhDl=c19%x@dJfj&5U8yq zBNoT`tNM~ifr)*!Y}dynoR>NJmOTy}VkJMNAs(wDE)sVaK-<4o{@~#wz?)YHz z__CF-(f3ffyh?l;K;`+EtXuaN1bj98!2Ql-Pxv=8+rGTY2alXSEnJp{sWEhhmp8(P zX2dKen01ns4h=Qe?8!V=(|adq`^oT5ADWZf93c~>7W;hiwTZlopqhd_n^jpZIvX0w z6wGKsEbdw^Sx8o=v^I^tO<0-~DQ~({YEYkO*66jJZFw)|4l@Uo_hf79WAwKo^az;P zY`x545P<^`rn8sI>2#?Kmu}6puh%SBx^fq;)ceTmh%8CVx<1MKwzWU>t7wPHI2U+2V#Fp{j;J^G=4^vP)k2 zQ1nNuLUVtSz9^&Ur_p#kF6m~=b#l@f>U92sH)}kpF~?h&f)@1CN9okvlpo2;=QGLO z)5X2uf*R>2cQc;XLCy8ZOwe3&OB`GW;#hRQMg{x>nUTt+y(xR8*7E73-f5fYy*DI2 zUN^O&`^(|mrdj@&AoMQJ>~DKxRO0OaDmG-@dwqct_k0#m#;pDS>=j27xyg7eOVSA5 z#A%nD07|jDurt<=-hJNObpyyITg8VbyUg4T2Yi1hNyE>FeG@Kif!WEY)U=|TZ!kZ8{`F2dqk@ou ztpj*JnMWhQ?L;Ym)R+Bp;a?qEtEK&M6-4@s)5PaG-`|O>Eav-fDX%OQAA-+5vvH>KPdxICf~+)4AEPjlqr@ci;HQE?Qmj%=R6ceB!3_3qE~IQIyYP=f9ZaP zyUrJiOL?kd`G)e3LO|+xVp0MZG)@u@Q|~z`2DMg0T}1}0yfMMcwO!^lGVxLAjfEnw z|1y|Qq=_1yK=_4lZtlgp<4@U5haS4AK7~_NiteXY9vtad{0(a-%R>DA|6BmA^AvY` z;|DM3hic1zDVV;wNv}-6Ht8xIkO^_mjnz1xa~aZhvc@;$QQf(ohTlJCB2R4gaWjH& zd)N+uNsC27{aA+-M(Y=Q;l5n#U)*KsSt={kluWqj=Bh;{sw)$OJZ#>RZE+5Hs1*K& zkd`MMg|gJEJr5a=7RsJenW*aZp5$0o4%!L6DPm@~wSlM;a5_JNjSyit)5=}ImZ5Np z`82`w&8pEwVVL?^i+iZggYG54y!MWJ0H#Z*^^MJTSqYmlc+#nZ1gbvT<>v9SpEwUq zoC*mpM8T^&EB!ytf}0BJ)A3_Z8WODY-*?t7zdHcypv=VgX0%a3vo*!%q$$ViUPZLd zN3royAUA!SsLf#(Uu8}K$^iqb9_H6)?mu(ppa~fYPTzfBbiFrn4L-2&FO1iv@QObw z@WKOC93IuV#@hnB&h}4n9~ta)6+Voov?q%uWqWSw`$mGr9ap5c5!+8wmP9wKo&eC;@8vr2x*b4UF$^_t=n1w&1M(l3% z-I$0uv`iWG@tyTN3cbT|Cb&C80~is9q$Y;+bXDeK1a67+f}?*H2L_kL5&87=KMSv8kijQgpzz7&b-3~6Q7orvpxoIDxtyy zK$nRjCwel!UaF#(5@l`dR4yv0G8uyzM|qQpj6fV8`IX4j@i~ElW!`zeNwT@bF6^|m22d(`0*yMniEs*=B@IUx{}-Rr6h0$xbW7#YsTuI*E|5RQEHF3odbW-+m;w8w7ADgj+(F$(Pa$frap>R`qf6p*p-)_#(?WO5bUp$OXOLWD~UGQGGDy|EK zXkJpdTNgO(^Bn=o2DOVD=>$O5^D{a&xym8lmC9vllVzmaP|7)rrRp(0y-Q24xHl?@ zkvwE8&>^bSysJp*{g3Ay*NYz-!KeTjf3(u**z3yxW?(*sv!H<6fIy6l?_wv@ZB4df zBHM%2WwXp#*sZ{*Xdwl^{%zAyQbB!d`BHwp`M-`Dpv?vVrgp|yF^5`OoLvKO8_65e zNwc>lG5wVcp0R`i+?J&{%Igh0f;ZO?>>U8)!^GBAKdlm_QHZ`2Dl{y1y>n7C@u6(u zLb-I)Q0`^h<;NBkd>$=RSJCl{QP{M2(dpQJqDDy0`XBY zy~`=90ky}9Grzax2}I`dR&+OO2sXX3a2I`_?|BvWN69ooeRxEKOeR>G#lDndt(e2Z z!#dA%P$UMs`$p4Rm9B(Ng)IC0ue|~IC57cw1yv9{@$RGF<9zdfj z=loboLh5?r3;nTQ7Mne$r+z~Yfwr4;;~xte)J}kxWhW>$gA_HuFtq7y)j0dLLn|*W z1pX(R3N$wn@KXdOXz;t`)fx8By4K640uywHMp}aqaL}@+U>Q0VW@8w zMV<9UkU!OF2|O=pNXRf^U9wEOk8WoY^Wi+b`hucXBD3+Sid5PU86MdXN6IL;T8;0BOsH(K~jif&rx`P|*EL)&nOg?id>mEDWg}<5YM}E2*oE znO(C_@ske|>cRFDs%ODhrKI9^7leC!*!#H1&y0)Jw^i`Xa($7>Ov*>TsHj``z;Ql1 zHy4n!0eKos8;-=dxCflrSl%vD)f9R{ZDV%z=sqfepOLwmMlxPWeCn*y*iNOFfS1GH zKd2!im`=rGnW+w3Jb^GN<6|RQXo4k4_@aME54G;nmr0M0SVCVoIYZj?SuUme?yZ~? z^4OO^^3OPZPXi4hj5XUZvH(+)w9QMXNA&$?<+5XSfTt5xP-=Xu$T4TswlR}KWU1pN zPNk~vk!FvhLv(aJtGyTK9+0V_(v&GGy6peY)kPj6nCkKC?xDjT>ARb zj*6SrCKtg>7@l{73zm`BkBv2_Xmn6_s_1G&4%Z9y?`M}VFNLaHU)c%THe)~SC6b*9 zCP2~6L=hZQaj~F#cZGP=_VcIj>T1 zMFm}V=4m!dYdb=TyPB%G`;%$SWKs)#Db-LF^cOY!qDy;#6|-4N{@8MN-&f#WKNhJK zYVb(EX+^I|zkKM%d~@abzFYsQ5i<0W|2MBnp)ikZq`pgL@jw%3%mYddgtwQNsMq%= z;;Kj&{^j^p2*XPm4JERNiNq0gQiRTmo8>U|L@IYtbngc-=g(tpS*{^4q*Ym(`6>@~ zfnBQqVEocgqjCBi?%K8)_;8X13s2mKi2a%TarQS5(VU*L4))LY@5afj$@a7az+AB< ztUmoDt+M>!^ZAc2Ea|e-T}3=5U`Vg#el}dydos>FNcv&kc69uxeydA<>kk=~pDyU; zKzZ?Z__*ZwN`Q!xo0J(cMtNHsADY!*{=NDqE2iIk|6HNn`!U08kUx(OInTf~eA8h5 z2h**NgFiAJJ))av7PKOI-Uo zr;bk>A-UVi$7Fi*Z@)1OyXu*kk&9|2>`!uDTAXe|Bf2%j?WD7G8|?<5F*)t`nq;Bm z?9kVeZ{wx!d?6(!CU!!bbjc-d-RfojY(HeoE-AastXckFsu2~@&0oG=2qGOht@>xs zCyoJG`U#^(Z-#E9;txR6l11jOmTiY5_eR?lwS9suN+I2B`-}A4{reXeKBmR?11tye6VzFu{0h+o|NW~uHbY*Ejf+A#?Z79B@9aFC`L&XEAzRv`@*~R|uoI8mvX)pA1Sa@i=E=bLiQn_4_C>`P@qa=D4Q4Mz zzjAixVSKEi=LUyczkK;1NAuR>$B$P63S9z%)X^poN{GYI;jdqKI+RjDCHC_Z&m|ek z@J?Vzt+OA$b8gYm@Ky5)l0C~-#H^o0nWehSXdPWJ7Jf*K?F`vwsVR*vDKeMq`ho88 zGm~16Drr9r5fzKRV(NbAf~M1Muu=gV+mEQag`mBL<#WtO2wl6XEery)SV z9@SpPYYaAIN{3a>D29p{ic zX@a^2pHh$OGZy5d@XftU}o3t3$@k9FlDPq`Doa~qu9wti%&!m z1h0s|!u@<*AV&LJU%c|LB!Cv29pT#?#lN(% zsnsuUx%r<~doP>LoRr*K<2Nn4jjf*p@?fD3ttSl(-MSMxeNpC4^C0P^5%2$$R)8?ZpA?UF`gjq};d^VIS?k?%&MO!FmU4~|)r z63LO|)s+@`~S5Icns#J@ggbuW+Ita4wx$Brjm77lbwby?Z#V z_Z9h0mt(^<#*n-Cs3jHOX>*f*`i(RkISlz0XwC~BnbO6%^d02$GOreHRJkc*2uqt{ znSNU=^*Ms&5E1_;r0fywdWe4U*0wDGSCkRPiAF&Ffx7p*@xSzMUOC{nv3rOurVIQ%K0&{hedXq zP45E6WdxzTWxSN#?FNkju?|UK{vTh&2uGxR!xl?)BrJm z`Sw*{2OmarW;22QFlX5@DFP~^z3tZw{3e0>@0wpTATy+@+H*$3Zg_A^s#V`sC10XG z0vu8+Vd1yp;!_1qt*-nTnQ}0j*`9V<)G}mtMYj=Z+4-i%VfNLTTTjzr6(U&w(*)WF6vTM1nV(|JKo6Rghm_1)J3Dl#CW7fB zZsIzXSTg($6_=@FbMAbwY>J|5UNL3hD?+vg<0O?Xp>UC`Q<*a2_$veAQmf@VH2i>p9;?)QZ2I4cfpv+$>%~i=ha`2b25fB-+z2llL2qwI!1H9788kV*6rArQUu~b zRw#ljV%kYmyTU+6F#S$O6EXt!?=jt;fx;x4YOR^z2XGm?yWhpx2Gut-kU>UzmPkz_ zsyxV&NbHD4W?pJ)R;Wx$bq5jtlUgf@W}8a)@L zv2u5JXaBU^$$Mf%rTQ_3Crl#;7$DEinv2ri@qWCG>5GWCe_eAFb)4Pub))X~R$=$( zSJ&ejVXix+ZZX6DGOW*L#BXzb4sB1kxCrrSHphSV3S2NsAHtY#E5%$Z`^nNT0_uCR zdEGocx?n*LGG~??3UQ%9b`*RX&OUKtqP+M;^iF@xR?lmNpenDu`Eb`w|5TUPLQj)f z9~#pv1l`GNN>W`eXwp7?o$X947=zR?Jv#Ti{?K?$nU=vgwOZT*Fa^7L z4bP14Kp0VME2{Jb5mc>!N=;`jc$v>2#B=_H2j$1bUw%^YPLlj-uej0Q9Fs2QDIeMJ8 zm`DzfR^jNa%oo+rDbpv7WU1kQbWsAXLT_(xCs8^l@vKsVa^-K#OHw_RYPse$H}BUz zZ*yjT-x-)cfs7|{BTn3XSZ^U8vxfP++=ywG4gZ;N4Yjm{#DCzX z2Q$*zDvL8P1!`k!8XIS4LLJI{GeI^Ig@cxYU1j9tBDW_v1Q+5yTopKL&lK3|6=y$( z6PxGuEuW129G{=hhuY5QeyhLE2aS{~>PxWYYpkIq_>*}&R7+%H{iHH>kTgw^pEzde zRAJ%>aOkIIXCVyTrTV-^CMNQ=-$hp`VK+*grAMEH-j6W{7lvfXc&2{3piN5W0-H(J zdA$0;^`|tEY{c)(8vopboTDi7;(g#jEg}=&^xRQ8f%Wx7#zoFSS^;-uVh;3bA&+kV z&`dl@@{?u+tL!jP7R1sM6EH;`J0`85;uhAtdRepLdyqG^&W{rYXFe)`D;Gw6L(4xt z%O;r`Q7JN-T7zyyX+(RCE>ol8 zl3ST(7xpD<9LjKXZ&L-p94v0gp2)5spOM$G2N`ladTF`H&n{>icA#xXZSLE2z0mW< zU3zO!M6{^I;qxEeenrHc62dFOkHykb(&%dF5T3EW0N#M~m%#A{b+IkgA$`%ICWPCNO5X8*` zXG6#S4YCGz^M56*pPc{e@0umw?AHuGYhB+%s4NGqA}6RjS3R6k0N{QGKy^}*YD z71&{~4yM)2s4bYnW>PA&jHv^LP}X?aTct%EMV##Gql={+&`^i(y6k#`0dq-wAvq-u zW~u`Q=&kAVBgP;DO1>*IaRkC$r6!O5k7bq`f?WmCSs46=`Dyd4Qme%in>f}4t|3=L zvC2)4Ozhk?arXXeBj{B z__k~xE=pI5-7NNvoc0I~8}&Saz$dzszsWDbXDJD@Q?&ump)mUeBwC&5AI@lX?Z`4j2q^zXDZI&e}6SR;dhAYEX;>JcF@U6azMK1 zm^zSYK!Ibkf75h**IC%U*%G2_N}PcJ$CA9~$B2FhJnK!z zW#R^(k$K|B0iRSS`eSPg`-*0YvQmAjy`)S>t^!7AxtK&Ys*X0AYG?!tYLotY#*vuu zj1(wt>K)rje!fA!@u4`y13-TGqoY7ZYZ|)!JZHp#0w0D;3a%r%axdN z>HIjvu_S+dKTe`hbroq5I@j`wyI-W!(=8kEn)%b`&)4bV2|f#VS_wa8=w|Dl|Bk;- zyG8#%0-72?z67hsU5F(rsPy~?qYYf^<;-ZZDj~)#zDvXsfAn{;tg-rre!>hlj^%Jl z==+qxm2H;n@R-sJDBbl&g2?_>owAl-)ui@zFH9VN|M0pG&U@FrAr>5Se1<6I+t-t) zwZ;yk=&7&d*3JhW-ztvKH|N>x_Zt9FhW^)Fmf#g?uZKgmqA7h4gT%h>CCu+BbqPDI z%HSt85NL5N=DH@LQ&m4Zyhyi}jCXGADCt=);Tv|m(~P2N1io@!s=oBgZ-DM_e0)5- zc?9ld&x(61Pd53ga?7dVuI^~5(Fv>NQV;@5uEE7sAcgNG3 zj*e2BRCIasTlingq*eY3zjAlwLnr`hGo)s?uQ>QLfjxp1B2<5EzWyns?e5E=`ntzq zlrAM>9nU2dX(B;Pdi%9iAvg4`+@U>p4E`-HLOU&8*cK5f?#h0|J~qZADOY~&tLF)M ztYFPzy&x4D%J0&}Lj1h(7HY`qnSo?KZGjmFCg!7?pxIwJ+TmeWU81zd44#hc)3t)> z{VHSQ`hO6wIZ6sakm_w`wMa~`Uw@A?^+L*-!#!!d5*NQ%r}`y1MDxC-mTctbu(z)+ zSDg}L?=T34Kn&|ynW3NdA1bDOdTDANB^hm3_9ymC?QzcCQBerk@ne+MG?{YxS+Q7X zREg8m6y>Hi-dWG`kE#0qf=){E=RXUnW|&I(WCgZarA`sXt-<$d?4x4JdrH!E$3ZVS z8E4TMFb)+4U-tJ!Hjd6z9;Ug8@gnJlSY3U{$xkw5aUm1YS1GokQLpUJ^v1uJGT|G% zRjf6It)seS;mo&5yQ^7rRF#!=d^$Ox^^`h&Gh){3GPiq+RpA-e=j71;z(F6i-q&UA zG88y~c=Sw{MW3W|U$~Zw)W;t!Ds-GSiUl%YI0Xa*@<*I#_V2vLLz~2{sP?`R(?3%9#H? zatJr&-X_{1K`t@%^m#cW1h|%*!Vmxvsiq^Yd1Tn%??o{t&ub2ARt4{zi;x(vBN5{#wyM*v7$ z93Q36OSS_V7*thN3CmX%P8|h;$Wgt6HNAv0xPei5WG$ldo&Il$vWzyz>WL(YRimGS zCt2qga)GT(_o=D(n&&R=UV)BN|CJk7GOY_4d}9)&3J&X?Xi!-mm`%B?4{&}oTM7I2 zT3(JP+H2X(b;k2lF8K`KD$sjoTl{C}IUMIQi_`Z1e6e^3;0oX#(2{V}1cLZ)iLAc| zS`TTvRs|%9zKSO8Ix{9GBV)DgkGv7=Ao{5=AjbI&|9_8bS65f3hM8j0eglSLTv)&U z*=DAJPC0rSNhwxf@Xh5_0OHfQUrx{Fv%9(XUgLpJe}OX~KCaE12Zq318yg!*JaUWo zsd&MH)+;aqumgQIIyv;t{(Fy+;M`}gQRb)7CQ1GlOE*JN-Xb1R&yr49sW>DszpxZ{ zO1M76Mnv3{3I0Rc{^zSuRdsb3Xr=kC&D0&M@>SJ~JrB2wBVtm8fDz}lKXc)vc#{r3 zE@X*zeIB&};sL~Zxitpd!O!vqNb8d9f1XMTj_g|)g~ES(zdKf5uePRH0Bs^WgN!sbB0wuc+_fU|x@Zk!Qek6jd*k5XpqM3xf+Hb?XZr4!VfB0Sk3>#Y zD!F6g$XALtei$pCbv4~@_vA`)Qa#&GM7sWN+>{CkDU;FmVKkg%0tFv{UBRx!zjk)f zcA8TXvw0+q^UB5<-{Hm!z*`qhaZ+P0=;;F_!ba8`L>+1Y5N z+28)s@42UlheuftReYvT3UK_pyu9wk?u6><>T7sYKYxlR`#Xs;oru?O&mv7=V2<~$ ze}1MTF2R=0xh~WCn1+P3`=Oto-?suX{4isZp0U&x<7HNBwE)b0vOE)CSxryO&?=OB z-t#T=E)-;xxz%~qc~eyzy_Sj6($n`QO+DrZa~^}h0oQqy4F8&efq~L7Y~5(I+h_t8 z!Vpk%SR~r2)Ph>8RDTmF+X4 zBPN_h(=f%oySv+;+bF_&5&B@lg~~WBAG^sC!)n&-cL%Iu@B_g1&!eN{8%NS$N2AP} zBh!T}7d&2`)8h;^mY0{4E=-#4+iT-u@oz?TmSy@RVSTL}O;tQ%+3bYi9024%hVC^V zX(I~@C7MXi9ID9qB%i9G_n!?)=T*1wmyt|FsPy{kc6$Ojw>Nrj7qr*GdBi^m{{7E% zeR~@&1KOT~5uf_;Lv(Sc?eff(q6%@X6YPo}H20e>&rK3aT!e}z^t6U5d|18;jgIhW zJ0*DX_xFvJ)zv-F+@Js?4nW6D;CchX5C-7+?@MHb6;9Ax*S`;gxzE91h>MHkmK48`kgcQR zEfABHmzPHs^kmP>y}Xi@J6mJ>dwJ5{4U{&qIXR5MXX_=6FR$b#Cno`8wkOdKmtu;9 z@lo;V?2&~b1Nl;kKvDMiSiJ4eLTpm&6!1u40(S^*=*`SzYPi%ABQ#Q^i)R8nDR~?< ztL*d#uY~x3kjjA?a~6eBp-*(BixrG2Q7#qq$_a1sJ^D0_d~-VYH^={H2d-Cz4CrI> zWu6o_9QF0}U>Sn+2PFhRT@Zu50JiKn3vX~8E-%~HnFu7`M@_hZnX1qAK;rJH{6s~s?=yB^r^bo8Di?-lsq4vMz+kW($o0}lAExsAnlfkFYs>f=e ziPGk?mt7dG80{q0J9@9oSn8L_8`7y`KS`%BYtG1@xnzquiykuxdjGO6NT!__i4vgGEe$u`d(CL3oxoHs)4PG zYr24Ij`*>%auH-VZokG03JLZc8!gVJJjLg`Q7)tF8`7;?J&NflpNTeFU0DI?EL@4=*Y{e-si~{afR=?}sYd^@$JGnO zlw@k{bKSAjLcekCNN|lk3@t+cSwIal6XS!tCjhj3e0*$?{7oeQ+%+<*!ixDpxdqp_Zywx_ z#Fs~vFF~HgHY9`*i!xWJ#NNOgYwy;x0_QH^jY=$y@17)->PQ*%7hlJ0=d+7y4z?xv z?N7BJlJAd5zD@X3OCF7AS}s)UfZN!om;ElP+QeV9gqMKxkIUbd>#S< zujwvu>>3yvdR|?I;C8=9>(PC15)K?7*stLM93!d$vYmV2BEdD|1*dA~n+W^7F#>N^M8Q~fsJzwslTAZl>XSm zO0(`KJ#vhd2Al;&8$%`)k-{X(L;b!3M&Jomc<+6I zT%APcU!ofE-D37U;7G**n)vuTA5o~cZss7@1r`d0x}cB{+wJRP{b#^X;NS(Tm4!Mr zgbd&V5G5qNSI6C$3)jmGG7JRL`vEY+ZOm6!J7t$kTu+{XGA`$Nosoc`AaJoO7+e1Z zYK+AbuP!-Q5uFQkw*`Wnm+z6K@@@B>s1)gDMKb*RHEJlvy_PC2C*3$KUf7(+oXEjxGwd~8&7Gx*_Egoc*>hkp zfZcE>t`P`$XtVavkV1)OeclXKHagX3=Vgb|brDZ5zxxR4GIPpeFi)xgZ1e1};_J%4 zU}3$vPqb3Omu&}j;csx=vBt21QQ+t2kE6T>Wh1i8G|>EBU6g2!)FX#lymbA^By`oD z@ZRa=TYaMTJZhyqSu^OTzn(3v+(QVhP^Ju}G}(5@$MYqB2fbLq^!F_@nsj)5?ZjHm zxUAhOKm0MzJ68J7szt8>qjX{9gEBY;gnjWT$De;7*G#Fo6W0*ZJF_l3jAp*UBVdOs z^*ohN-GGCggKPT&+`+1wn`41sfxVZk;6qvl1|!8UD7b*z%oY~nm!*iq#zlnsxvB`d zC?GmzIk!S%ex=AbHjd6aL`&`iA${+NJR zoi1soZZ{_{-^y9(uCwPFWFvl$D=b{=@Dk0^0H<|U?u139#HgSG@!egrWbbhON#A~J9v|^9Z>8a!NG+Aq@jCY zF^nas9$j%9?hYV$=0M4Attyf_q<0j#uyJ>YKu&x*Iq`%sqU1m!BwO) zDfGG!S4afCbY`4&jD2qUE+&%8s2{hMsYK{^G2EOR+e2~(C;?aG71kx+a!G-j@TU1t zUe}z-{wuD&n&xIp4s2Qg|Jefn&2;+jG!22n4tJhmXy`g(N+UFvIA)@rhsFZT1~6B| zKOeU%n?%GhnfjoJvi?hdap9Htsq9s16qB-O)UyKuhX1jyW2Gsxk|>M!9MKQ8aBvH_ z-d=3--A4oo8x$J(JlkDGNU5^h8`Y zI4XKxB6#5vYMPU6IisNM^&p`FyhA@iU@&^IGX*T%ZuNd6ec!3ZVYp+M`BQmvETkir zeq;2$1z>A|Tr7<>`4V*_yuu>pVQYG|bENr+iy?bv;GXv65gcW4fQR9>l$MAOQ{`Ib zEk)nkH5_-7YGCe z1!_Cgm>j1kRyZsA#I>Tqf@p1$Ol}g1HMzSZJOe|2^ZAamiD&mMC4gN)K{p1JVsm;; zQuZj&O>vj5BQ&FfH)($p^gx6s=@TLK!}+&erf!9zC8Ur0nZjK733>Z6)~s)dXi1ik z*<7ggd<;L)yV%SWuk0u@De3DVx&$ib;MuIiD;1EPG8?8Et*Yq9@+G?)a*`DsjM#G$fD*rZZRFUqu+np&gfoOCHP0{WDIIj)< z$&c|zc(^Q};1EX-nP`m%r4ZE_LXM9A*>)LU%ayKRb1M-pKN!w6ybRjm==oo1RRy0C zjtw7qR<$bzHd2xJ4R$Xma15V%d`LSeSzct+H9QSFO%Avs_%mScdH9mB9S7{dBlfN2 z!<#_tZQ=KQ_@I2|UY5d)btS??cIY;0?OysoP;@nm?f*I1xAwcKw!jBaAq(Au<#Vb6 zlN(Iq1K4LI=`y?R>&v8IpP`H_vXy&cM6e)(!MsZFer|i76SPZK{WvxO=)1s4G|GBC zw*>rEYYlQ6(#){+gue!ySmK>;kVvq5yzD5O4u9p`;9Z&e)hNV_rSZmO$93r(4*#!% zN3_|E6er~{;nD{i@1ePY@ESn}*mVR5&vgU-`D=(!-QJJ$=d2Qa(1RIwo3s?$G6!>E z{0NOg$pTGpewRjQ=Oi)^Uk=rUx6Eh7l=%3I@7pos2K1NuzdyN2D2b|AUnK^=rV@_6 z6UJ0_Ozx&HzH&(m`ledI{cw6r3;^?%aJvm)Mga8tfrR<+wN7;7r2-{|rvplPKBe@b zb4Uiy70N0QB1V48m}$#$Dz~e~^fhtD6AqH7r(UYQg=cH#u$PN&4#qfn4%3DlO1n|a zsXxvrA+X-JP8b4B<-(!{5^tZCsGgoxjRRLLc2=(9xvt}ZP6+EmO0OB!YoffHka%oA z{kf@`r|;1)o?AS5*L+lP$!xfEC}j~Sf+|> zfOV74B2U^Uy4P~IXl+=@6&bOFS+EMB7z;5_LTnp)dI6Q9fI2gvj=F z>N|oSSP5gcwX~!_-OfWphrcxA6zv#eJqrscNFwoC1l*AP2s-9UKCOD*W$9ovEM;$M?*tSXO1R)Bud#O_;}AY3~- z7Y@DvvHt5qlUJt;SJvhjUrPiVyA>4`SJnkTvtmJ`FG$T{v(t42Re7k@ot&qlkt~O; ztcagOjlC#hqjkh}TnNKI(jL265<1L9*P%YKIDbVZeLfMF74) zUgEm0fF_;kf*;E*tzm!f2JxgH->b|gpkxxLJiH$w*mYD7LrrBRVmWW3CugN z3&AaJdy=o|3MmsCU$bL-{Yzt#0b|~8Yip~jDq4v!xaybY}3KzD^U;DLQ^ApDSd*{TAk8xUY$PIH0A zHp$x$Gp^t9-oH~_90=rHDJiLjwl*mA2HeinQ~Mx-3Aj@4fhnMz85kNic6Vn3lA&VG zTpU+ZK_2pt3a;MUZE@cSC4$9?V3pMs!jz-8o(P3%P}950&#ARID8(!Za7ia|xWkX> z*2iYUAh`^XL_IY+X0c(be$`RC_O36?iPy*Ze5l|7?^39c2eMFyb1@&JJAl;fC5e~v zaosX6l4zcj`Y)n=f{8s7hn`h9FzcF&c7p1aO;~T rRQHY>My8~9++_EvNYuY~^t!%GJT literal 70955 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 diff --git a/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ManualRelayControl.java b/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ManualRelayControl.java index 3b0972881c1..13ccd4c4c66 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ManualRelayControl.java +++ b/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ManualRelayControl.java @@ -1,8 +1,8 @@ package io.openems.edge.app.loadcontrol; +import static io.openems.edge.core.appmanager.validator.Checkables.checkRelayCount; + import java.util.EnumMap; -import java.util.List; -import java.util.TreeMap; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; @@ -25,15 +25,16 @@ import io.openems.edge.core.appmanager.AppConfiguration; import io.openems.edge.core.appmanager.AppDescriptor; import io.openems.edge.core.appmanager.ComponentUtil; +import io.openems.edge.core.appmanager.ComponentUtil.PreferredRelay; +import io.openems.edge.core.appmanager.ComponentUtil.RelayContactInfo; import io.openems.edge.core.appmanager.ConfigurationTarget; import io.openems.edge.core.appmanager.DefaultEnum; -import io.openems.edge.core.appmanager.JsonFormlyUtil; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; import io.openems.edge.core.appmanager.TranslationUtil; -import io.openems.edge.core.appmanager.validator.CheckRelayCount; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; import io.openems.edge.core.appmanager.validator.ValidatorConfig; /** @@ -111,12 +112,14 @@ public AppAssistant getAppAssistant(Language language) { return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildSelect(Property.OUTPUT_CHANNEL) // - .setOptions(this.componentUtil.getAllRelays() // - .stream().map(r -> r.relays).flatMap(List::stream) // + .setOptions(this.componentUtil.getAllRelayInfos().stream() // + .flatMap(r -> r.channels().stream()) // + .map(RelayContactInfo::channel) // .toList()) // .setDefaultValueWithStringSupplier(() -> { - var relays = this.componentUtil.getPreferredRelays(Lists.newArrayList(), - new int[] { 1 }, new int[] { 1 }); + var relays = this.componentUtil.getPreferredRelays(1, // + new PreferredRelay(4, new int[] { 1 }), // + new PreferredRelay(8, new int[] { 1 })); return relays == null ? null : relays[0]; }) // .isRequired(true) // @@ -143,11 +146,7 @@ public OpenemsAppCategory[] getCategories() { @Override public ValidatorConfig.Builder getValidateBuilder() { return ValidatorConfig.create() // - .setInstallableCheckableConfigs(Lists.newArrayList(// - new ValidatorConfig.CheckableConfig(CheckRelayCount.COMPONENT_NAME, - new ValidatorConfig.MapBuilder<>(new TreeMap()) // - .put("count", 1) // - .build()))); + .setInstallableCheckableConfigs(checkRelayCount(1)); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ThresholdControl.java b/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ThresholdControl.java index 22ce817f0b3..1c84bde7d06 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ThresholdControl.java +++ b/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ThresholdControl.java @@ -1,8 +1,8 @@ package io.openems.edge.app.loadcontrol; +import static io.openems.edge.core.appmanager.validator.Checkables.checkRelayCount; + import java.util.EnumMap; -import java.util.List; -import java.util.TreeMap; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; @@ -26,15 +26,16 @@ import io.openems.edge.core.appmanager.AppConfiguration; import io.openems.edge.core.appmanager.AppDescriptor; import io.openems.edge.core.appmanager.ComponentUtil; +import io.openems.edge.core.appmanager.ComponentUtil.PreferredRelay; +import io.openems.edge.core.appmanager.ComponentUtil.RelayContactInfo; import io.openems.edge.core.appmanager.ConfigurationTarget; import io.openems.edge.core.appmanager.DefaultEnum; -import io.openems.edge.core.appmanager.JsonFormlyUtil; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; import io.openems.edge.core.appmanager.TranslationUtil; -import io.openems.edge.core.appmanager.validator.CheckRelayCount; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; import io.openems.edge.core.appmanager.validator.ValidatorConfig; /** @@ -112,13 +113,16 @@ protected ThrowingTriFunction r.relays).flatMap(List::stream) // + .setOptions(this.componentUtil.getAllRelayInfos().stream() // + .flatMap(r -> r.channels().stream()) // + .map(RelayContactInfo::channel) // .toList()) // .onlyIf(relays != null, t -> t.setDefaultValue(// JsonUtils.buildJsonArray() // @@ -148,11 +152,7 @@ public OpenemsAppCategory[] getCategories() { @Override public ValidatorConfig.Builder getValidateBuilder() { return ValidatorConfig.create() // - .setInstallableCheckableConfigs(Lists.newArrayList(// - new ValidatorConfig.CheckableConfig(CheckRelayCount.COMPONENT_NAME, - new ValidatorConfig.MapBuilder<>(new TreeMap()) // - .put("count", 1) // - .build()))); + .setInstallableCheckableConfigs(checkRelayCount(1)); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/app/meter/AbstractMeterApp.java b/io.openems.edge.core/src/io/openems/edge/app/meter/AbstractMeterApp.java index 7e2074091f6..4824bd319ba 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/meter/AbstractMeterApp.java +++ b/io.openems.edge.core/src/io/openems/edge/app/meter/AbstractMeterApp.java @@ -35,10 +35,6 @@ protected final JsonArray buildMeterOptions(Language language) { .addProperty("label", TranslationUtil.getTranslation(bundle, "App.Meter.production")) // .addProperty("value", "PRODUCTION") // .build()) - .add(JsonUtils.buildJsonObject() // - .addProperty("label", TranslationUtil.getTranslation(bundle, "App.Meter.gridMeter")) // - .addProperty("value", "GRID") // - .build()) .add(JsonUtils.buildJsonObject() // .addProperty("label", TranslationUtil.getTranslation(bundle, "App.Meter.consumtionMeter")) // .addProperty("value", "CONSUMPTION_METERED") // diff --git a/io.openems.edge.core/src/io/openems/edge/app/meter/CarloGavazziMeter.java b/io.openems.edge.core/src/io/openems/edge/app/meter/CarloGavazziMeter.java index 59fa0dcf32e..31bd88dd65d 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/meter/CarloGavazziMeter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/meter/CarloGavazziMeter.java @@ -25,12 +25,13 @@ import io.openems.edge.core.appmanager.AppDescriptor; import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; -import io.openems.edge.core.appmanager.JsonFormlyUtil; -import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder.Type; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.TranslationUtil; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; +import io.openems.edge.core.appmanager.formly.builder.SelectBuilder; +import io.openems.edge.core.appmanager.formly.enums.InputType; /** * Describes a app for a Carlo Gavazzi meter. @@ -107,17 +108,17 @@ public AppAssistant getAppAssistant(Language language) { .setOptions(this.buildMeterOptions(language)) // .build()) // .add(JsonFormlyUtil.buildSelect(Property.MODBUS_ID) // - .setLabel(TranslationUtil.getTranslation(bundle, "modbusId")) // - .setDescription(TranslationUtil.getTranslation(bundle, "modbusId.description")) // + .setLabel(TranslationUtil.getTranslation(bundle, "communication.modbusId")) // + .setDescription(TranslationUtil.getTranslation(bundle, "communication.modbusId.description")) // .setOptions(this.componentUtil.getEnabledComponentsOfStartingId("modbus"), - JsonFormlyUtil.SelectBuilder.DEFAULT_COMPONENT_2_LABEL, - JsonFormlyUtil.SelectBuilder.DEFAULT_COMPONENT_2_VALUE) // + SelectBuilder.DEFAULT_COMPONENT_2_LABEL, + SelectBuilder.DEFAULT_COMPONENT_2_VALUE) // .build()) // .add(JsonFormlyUtil.buildInput(Property.MODBUS_UNIT_ID) // - .setLabel(TranslationUtil.getTranslation(bundle, "modbusUnitId")) // + .setLabel(TranslationUtil.getTranslation(bundle, "communication.modbusUnitId")) // .setDescription( TranslationUtil.getTranslation(bundle, "App.Meter.modbusUnitId.description")) // - .setInputType(Type.NUMBER) // + .setInputType(InputType.NUMBER) // .setDefaultValue(6) // .setMin(0) // .isRequired(true) // diff --git a/io.openems.edge.core/src/io/openems/edge/app/meter/JanitzaMeter.java b/io.openems.edge.core/src/io/openems/edge/app/meter/JanitzaMeter.java index 16d1a67e8a4..cb089045579 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/meter/JanitzaMeter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/meter/JanitzaMeter.java @@ -1,10 +1,11 @@ package io.openems.edge.app.meter; -import java.util.EnumMap; -import java.util.HashSet; +import static io.openems.edge.app.common.props.CommonProps.alias; +import static io.openems.edge.app.common.props.CommonProps.defaultDef; + +import java.util.ArrayList; import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; +import java.util.function.Function; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; @@ -12,30 +13,40 @@ import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; -import com.google.common.collect.Lists; import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.function.ThrowingTriFunction; import io.openems.common.session.Language; import io.openems.common.types.EdgeConfig; -import io.openems.common.utils.EnumUtils; import io.openems.common.utils.JsonUtils; +import io.openems.edge.app.common.props.CommunicationProps; +import io.openems.edge.app.common.props.ComponentProps; +import io.openems.edge.app.common.props.PropsUtil; +import io.openems.edge.app.enums.MeterType; +import io.openems.edge.app.enums.ModbusType; +import io.openems.edge.app.enums.OptionsFactory; +import io.openems.edge.app.enums.TranslatableEnum; import io.openems.edge.app.meter.JanitzaMeter.Property; import io.openems.edge.common.component.ComponentManager; import io.openems.edge.core.appmanager.AbstractOpenemsApp; -import io.openems.edge.core.appmanager.AppAssistant; +import io.openems.edge.core.appmanager.AbstractOpenemsAppWithProps; import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.AppDef; import io.openems.edge.core.appmanager.AppDescriptor; +import io.openems.edge.core.appmanager.AppManagerUtil; +import io.openems.edge.core.appmanager.AppManagerUtilSupplier; import io.openems.edge.core.appmanager.ComponentUtil; +import io.openems.edge.core.appmanager.ComponentUtilSupplier; import io.openems.edge.core.appmanager.ConfigurationTarget; -import io.openems.edge.core.appmanager.JsonFormlyUtil; -import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder.Type; -import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder.Validation; -import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; -import io.openems.edge.core.appmanager.TranslationUtil; +import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.Type; +import io.openems.edge.core.appmanager.Type.Parameter; +import io.openems.edge.core.appmanager.Type.Parameter.BundleParameter; +import io.openems.edge.core.appmanager.formly.Exp; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; /** * Describes a App for a Janitza meter. @@ -61,118 +72,194 @@ * */ @Component(name = "App.Meter.Janitza") -public class JanitzaMeter extends AbstractMeterApp implements OpenemsApp { +public class JanitzaMeter extends AbstractOpenemsAppWithProps + implements OpenemsApp, ComponentUtilSupplier, AppManagerUtilSupplier { - public enum Property implements Nameable { + public enum Property implements Type { // Component-IDs - METER_ID, // - MODBUS_ID, // + METER_ID(AppDef.componentId("meter1")), // + MODBUS_ID(AppDef.componentId("modbus2")), // // Properties - ALIAS, // - MODEL, // - TYPE, // - IP, // - MODBUS_UNIT_ID, // + ALIAS(alias()), // + MODEL(AppDef.copyOfGeneric(defaultDef(), def -> def // + .setTranslatedLabelWithAppPrefix(".productModel") // + .setDefaultValue(JanitzaModel.UMG_96_RME.getValue()) // + .setField(JsonFormlyUtil::buildSelect, (app, property, l, parameter, field) -> { + field.setOptions(OptionsFactory.of(JanitzaModel.class), l) // + .isRequired(true); + }))), // + TYPE(AppDef.copyOfGeneric(MeterProps.type(MeterType.GRID), def -> def // + .wrapField((app, property, l, parameter, field) -> { + field.isRequired(true); + }))), // + INTEGRATION_TYPE(CommunicationProps.modbusType() // + .wrapField((app, property, l, parameter, field) -> { + field.isRequired(true); + })), // + IP(MeterProps.ip() // + .setDefaultValue("10.4.0.12") // + .wrapField((app, property, l, parameter, field) -> { + field.onlyShowIf((Exp.currentModelValue(INTEGRATION_TYPE) // + .equal(Exp.staticValue(ModbusType.TCP)))); + field.isRequired(true); + })), // + PORT(MeterProps.port() // + .wrapField((app, property, l, parameter, field) -> { + field.onlyShowIf((Exp.currentModelValue(INTEGRATION_TYPE) // + .equal(Exp.staticValue(ModbusType.TCP)))); + })), // + SELECTED_MODBUS_ID(AppDef.copyOfGeneric(ComponentProps.pickSerialModbusId(), + def -> def.wrapField((app, property, l, parameter, field) -> { + if (PropsUtil.isHomeInstalled(app.getAppManagerUtil())) { + field.readonly(true); + } + field.isRequired(true); + field.onlyShowIf(Exp.currentModelValue(INTEGRATION_TYPE) // + .equal(Exp.staticValue(ModbusType.RTU))); + })).setAutoGenerateField(false)), // + MODBUS_UNIT_ID(MeterProps.modbusUnitId() // + .wrapField((app, property, l, parameter, field) -> { + field.isRequired(true); + }) // + .setDefaultValue(7) // + .setAutoGenerateField(false)), // + MODBUS_GROUP(CommunicationProps.modbusGroup(// + SELECTED_MODBUS_ID, SELECTED_MODBUS_ID.def(), // + MODBUS_UNIT_ID, MODBUS_UNIT_ID.def(), INTEGRATION_TYPE)), // ; + + private AppDef def; + + private Property(AppDef def) { + this.def = def; + } + + @Override + public Type self() { + return this; + } + + @Override + public AppDef def() { + return this.def; + } + + @Override + public Function, BundleParameter> getParamter() { + return Parameter.functionOf(AbstractOpenemsApp::getTranslationBundle); + } } + private final AppManagerUtil appManagerUtil; + @Activate - public JanitzaMeter(@Reference ComponentManager componentManager, ComponentContext componentContext, - @Reference ConfigurationAdmin cm, @Reference ComponentUtil componentUtil) { + public JanitzaMeter(// + @Reference final ComponentManager componentManager, // + final ComponentContext componentContext, // + @Reference final ConfigurationAdmin cm, // + @Reference final ComponentUtil componentUtil, // + @Reference final AppManagerUtil appManagerUtil // + ) { super(componentManager, componentContext, cm, componentUtil); + this.appManagerUtil = appManagerUtil; } @Override - protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appPropertyConfigurationFactory() { return (t, p, l) -> { + final var meterId = this.getId(t, p, Property.METER_ID, "meter1"); + + final var alias = this.getString(p, l, Property.ALIAS); + final var factorieId = this.getString(p, Property.MODEL); + final var type = this.getEnum(p, MeterType.class, Property.TYPE); + final var modbusUnitId = this.getInt(p, Property.MODBUS_UNIT_ID); + final var integrationType = this.getEnum(p, ModbusType.class, Property.INTEGRATION_TYPE); - var meterId = this.getId(t, p, Property.METER_ID, "meter1"); + final var components = new ArrayList(); - // TODO which modbus should be used(new or already existing from home) only one - // meter installed so far. + final var modbusId = switch (integrationType) { + case RTU -> this.getString(p, Property.SELECTED_MODBUS_ID); + case TCP -> { + final var ip = this.getString(p, Property.IP); + final var port = this.getInt(p, Property.PORT); + final var tcpModbusId = this.getId(t, p, Property.MODBUS_ID); - var modbusId = this.getId(t, p, Property.MODBUS_ID, "modbus2"); + components.add(new EdgeConfig.Component(tcpModbusId, "bridge", "Bridge.Modbus.Tcp", // + JsonUtils.buildJsonObject() // + .addProperty("ip", ip) // + .addProperty("port", port) // + .build())); - var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName(l)); - var factorieId = this.getValueOrDefault(p, Property.MODEL, "Meter.Janitza.UMG96RME"); - var type = this.getValueOrDefault(p, Property.TYPE, "PRODUCTION"); - var ip = this.getValueOrDefault(p, Property.IP, "10.4.0.12"); - var modbusUnitId = EnumUtils.getAsInt(p, Property.MODBUS_UNIT_ID); + yield tcpModbusId; + } + }; - var components = Lists.newArrayList(// - new EdgeConfig.Component(meterId, alias, factorieId, // - JsonUtils.buildJsonObject() // - .addProperty("modbus.id", modbusId) // - .addProperty("modbusUnitId", modbusUnitId) // - .addProperty("type", type) // - .build()), // - new EdgeConfig.Component(modbusId, "bridge", "Bridge.Modbus.Tcp", // - JsonUtils.buildJsonObject() // - .addProperty("ip", ip) // - .build()) // - ); + components.add(new EdgeConfig.Component(meterId, alias, factorieId, // + JsonUtils.buildJsonObject() // + .addProperty("modbus.id", modbusId) // + .addProperty("modbusUnitId", modbusUnitId) // + .addProperty("type", type) // + .build())); return new AppConfiguration(components); }; } @Override - public AppAssistant getAppAssistant(Language language) { - var bundle = AbstractOpenemsApp.getTranslationBundle(language); - return AppAssistant.create(this.getName(language)) // - .fields(JsonUtils.buildJsonArray() // - .add(JsonFormlyUtil.buildSelect(Property.MODEL) // - .setLabel(TranslationUtil.getTranslation(bundle, this.getAppId() + ".productModel")) // - .isRequired(true) // - .setOptions(this.buildFactorieIdOptions()) // - .build()) // - .add(JsonFormlyUtil.buildSelect(Property.TYPE) // - .setLabel(TranslationUtil.getTranslation(bundle, "App.Meter.mountType.label")) // - .isRequired(true) // - .setOptions(this.buildMeterOptions(language)) // - .build()) // - .add(JsonFormlyUtil.buildInput(Property.IP) // - .setLabel(TranslationUtil.getTranslation(bundle, "ipAddress")) // - .setDescription(TranslationUtil.getTranslation(bundle, "App.Meter.ip.description")) // - .isRequired(true) // - .setDefaultValue("10.4.0.12") // - .setValidation(Validation.IP) // - .build()) - .add(JsonFormlyUtil.buildInput(Property.MODBUS_UNIT_ID) // - .setLabel(TranslationUtil.getTranslation(bundle, "modbusUnitId")) // - .setDescription( - TranslationUtil.getTranslation(bundle, "App.Meter.modbusUnitId.description")) // - .setInputType(Type.NUMBER) // - .setDefaultValue(1) // - .setMin(0) // - .isRequired(true) // - .build()) // - .build()) + public AppDescriptor getAppDescriptor() { + return AppDescriptor.create() // .build(); } @Override - public AppDescriptor getAppDescriptor() { - return AppDescriptor.create() // - .build(); + public OpenemsAppCardinality getCardinality() { + return OpenemsAppCardinality.MULTIPLE; } @Override - protected Class getPropertyClass() { - return Property.class; + public OpenemsAppCategory[] getCategories() { + return new OpenemsAppCategory[] { OpenemsAppCategory.METER }; } @Override - public OpenemsAppCardinality getCardinality() { - return OpenemsAppCardinality.MULTIPLE; + protected JanitzaMeter getApp() { + return this; + } + + @Override + protected Property[] propertyValues() { + return Property.values(); + } + + public enum JanitzaModel implements TranslatableEnum { + UMG_96_RME("Meter.Janitza.UMG96RME", "Janitza Netzanalysator UMG 96RM-E"), // + UMG_604("Meter.Janitza.UMG604", "Janitza Netzanalysator UMG 604-PRO"), // + UMG_511("Meter.Janitza.UMG511", "Janitza Netzqualitätsanalysator UMG 511"), // + ; + + private final String value; + private final String translation; + + private JanitzaModel(String value, String translation) { + this.value = value; + this.translation = translation; + } + + @Override + public String getTranslation(Language language) { + return this.translation; + } + + @Override + public String getValue() { + return this.value; + } + } - protected final Set> buildFactorieIdOptions() { - var values = new HashSet>(); - values.add(Map.entry("Janitza Netzanalysator UMG 96RM-E", "Meter.Janitza.UMG96RME")); - values.add(Map.entry("Janitza Netzanalysator UMG 604-PRO", "Meter.Janitza.UMG604")); - values.add(Map.entry("Janitza Netzqualitätsanalysator UMG 511", "Meter.Janitza.UMG511")); - return values; + @Override + public AppManagerUtil getAppManagerUtil() { + return this.appManagerUtil; } } diff --git a/io.openems.edge.core/src/io/openems/edge/app/meter/KdkMeter.java b/io.openems.edge.core/src/io/openems/edge/app/meter/KdkMeter.java index 028c3d27566..2a3415d5087 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/meter/KdkMeter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/meter/KdkMeter.java @@ -1,6 +1,9 @@ package io.openems.edge.app.meter; -import java.util.EnumMap; +import static io.openems.edge.app.common.props.CommonProps.alias; + +import java.util.Map; +import java.util.function.Function; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; @@ -15,22 +18,28 @@ import io.openems.common.function.ThrowingTriFunction; import io.openems.common.session.Language; import io.openems.common.types.EdgeConfig; -import io.openems.common.utils.EnumUtils; import io.openems.common.utils.JsonUtils; +import io.openems.edge.app.common.props.CommunicationProps; +import io.openems.edge.app.common.props.ComponentProps; +import io.openems.edge.app.common.props.PropsUtil; +import io.openems.edge.app.enums.MeterType; import io.openems.edge.app.meter.KdkMeter.Property; import io.openems.edge.common.component.ComponentManager; import io.openems.edge.core.appmanager.AbstractOpenemsApp; -import io.openems.edge.core.appmanager.AppAssistant; +import io.openems.edge.core.appmanager.AbstractOpenemsAppWithProps; import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.AppDef; import io.openems.edge.core.appmanager.AppDescriptor; +import io.openems.edge.core.appmanager.AppManagerUtil; +import io.openems.edge.core.appmanager.AppManagerUtilSupplier; import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; -import io.openems.edge.core.appmanager.JsonFormlyUtil; -import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder.Type; -import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; -import io.openems.edge.core.appmanager.TranslationUtil; +import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.Type; +import io.openems.edge.core.appmanager.Type.Parameter; +import io.openems.edge.core.appmanager.Type.Parameter.BundleParameter; /** * Describes a App for a Kdk meter. @@ -54,37 +63,76 @@ * */ @Component(name = "App.Meter.Kdk") -public class KdkMeter extends AbstractMeterApp implements OpenemsApp { +public class KdkMeter extends AbstractOpenemsAppWithProps + implements OpenemsApp, AppManagerUtilSupplier { - public enum Property implements Nameable { + public enum Property implements Type { // Component-IDs - METER_ID, // + METER_ID(AppDef.componentId("meter1")), // // Properties - ALIAS, // - TYPE, // - MODBUS_ID, // - MODBUS_UNIT_ID, // + ALIAS(alias()), // + TYPE(MeterProps.type(MeterType.GRID)), // + MODBUS_ID(AppDef.copyOfGeneric(ComponentProps.pickModbusId(), + def -> def.wrapField((app, property, l, parameter, field) -> { + if (PropsUtil.isHomeInstalled(app.getAppManagerUtil())) { + field.readonly(true); + } + field.isRequired(true); + })).setAutoGenerateField(false)), // + MODBUS_UNIT_ID(AppDef.copyOfGeneric(MeterProps.modbusUnitId(), def -> def.setDefaultValue(7) // + .wrapField((app, property, l, parameter, field) -> field.isRequired(true))) // + .setAutoGenerateField(false)), // + MODBUS_GROUP(AppDef.copyOfGeneric(CommunicationProps.modbusGroup(// + MODBUS_ID, MODBUS_ID.def(), MODBUS_UNIT_ID, MODBUS_UNIT_ID.def()))), // ; + + private final AppDef def; + + private Property(AppDef def) { + this.def = def; + } + + @Override + public Type self() { + return this; + } + + @Override + public AppDef def() { + return this.def; + } + + @Override + public Function, BundleParameter> getParamter() { + return Parameter.functionOf(AbstractOpenemsApp::getTranslationBundle); + } } + private final AppManagerUtil appManagerUtil; + @Activate - public KdkMeter(@Reference ComponentManager componentManager, ComponentContext componentContext, - @Reference ConfigurationAdmin cm, @Reference ComponentUtil componentUtil) { + public KdkMeter(// + @Reference ComponentManager componentManager, // + ComponentContext componentContext, // + @Reference ConfigurationAdmin cm, // + @Reference ComponentUtil componentUtil, // + @Reference AppManagerUtil appManagerUtil // + ) { super(componentManager, componentContext, cm, componentUtil); + this.appManagerUtil = appManagerUtil; } @Override - protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appPropertyConfigurationFactory() { return (t, p, l) -> { + final var meterId = this.getId(t, p, Property.METER_ID, "meter1"); - var meterId = this.getId(t, p, Property.METER_ID, "meter1"); - - var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName(l)); - var type = this.getValueOrDefault(p, Property.TYPE, "PRODUCTION"); - var modbusUnitId = EnumUtils.getAsInt(p, Property.MODBUS_UNIT_ID); - var modbusId = this.getValueOrDefault(p, Property.MODBUS_ID, "modbus1"); + final var alias = this.getString(p, l, Property.ALIAS); + final var type = this.getString(p, Property.TYPE); + final var modbusUnitId = this.getInt(p, Property.MODBUS_UNIT_ID); + final var modbusId = this.getString(p, Property.MODBUS_ID); - var components = Lists.newArrayList(// + final var components = Lists.newArrayList(// new EdgeConfig.Component(meterId, alias, "Meter.KDK.2PUCT", // JsonUtils.buildJsonObject() // .addProperty("modbus.id", modbusId) // @@ -97,37 +145,6 @@ protected ThrowingTriFunction getPropertyClass() { - return Property.class; + protected Property[] propertyValues() { + return Property.values(); } @Override @@ -144,4 +161,19 @@ public OpenemsAppCardinality getCardinality() { return OpenemsAppCardinality.MULTIPLE; } + @Override + protected KdkMeter getApp() { + return this; + } + + @Override + public AppManagerUtil getAppManagerUtil() { + return this.appManagerUtil; + } + + @Override + public final OpenemsAppCategory[] getCategories() { + return new OpenemsAppCategory[] { OpenemsAppCategory.METER }; + } + } diff --git a/io.openems.edge.core/src/io/openems/edge/app/meter/MeterProps.java b/io.openems.edge.core/src/io/openems/edge/app/meter/MeterProps.java new file mode 100644 index 00000000000..c3f0c8b946c --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/app/meter/MeterProps.java @@ -0,0 +1,72 @@ +package io.openems.edge.app.meter; + +import io.openems.edge.app.common.props.CommonProps; +import io.openems.edge.app.common.props.CommunicationProps; +import io.openems.edge.app.enums.MeterType; +import io.openems.edge.app.enums.OptionsFactory; +import io.openems.edge.core.appmanager.AppDef; +import io.openems.edge.core.appmanager.Nameable; +import io.openems.edge.core.appmanager.OpenemsApp; +import io.openems.edge.core.appmanager.Type.Parameter.BundleProvider; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; + +public final class MeterProps { + + private MeterProps() { + } + + /** + * Creates a {@link AppDef} for a {@link MeterType}. + * + * @param exclude {@link MeterType}s to exclude + * @return the {@link AppDef} + */ + public static final AppDef type(MeterType... exclude) { + final var optionsFactory = OptionsFactory.of(MeterType.class, exclude); + return AppDef.copyOfGeneric(CommonProps.defaultDef(), // + def -> def.setTranslatedLabel("App.Meter.mountType.label") // + .setDefaultValue(MeterType.PRODUCTION) // + .setField(JsonFormlyUtil::buildSelectFromNameable, (app, property, l, parameter, field) -> { + field.setOptions(optionsFactory, l); + })); + } + + /** + * Creates a {@link AppDef} for a ip for a meter. + * + * @param

    the type of the parameters + * @return the {@link AppDef} + * @see CommunicationProps#ip() + */ + public static final

    AppDef ip() { + return AppDef.copyOfGeneric(CommunicationProps.ip(), def -> { + def.setTranslatedDescription("App.Meter.ip.description"); + }); + } + + /** + * Creates a {@link AppDef} for a port for a meter. + * + * @param

    the type of the parameters + * @return the {@link AppDef} + * @see CommunicationProps#port() + */ + public static final

    AppDef port() { + return AppDef.copyOfGeneric(CommunicationProps.port(), def -> { + def.setTranslatedDescription("App.Meter.port.description"); + }); + } + + /** + * Creates a {@link AppDef} for a modbusUnitId for a meter. + * + * @return the {@link AppDef} + * @see CommunicationProps#modbusUnitId() + */ + public static final AppDef modbusUnitId() { + return AppDef.copyOfGeneric(CommunicationProps.modbusUnitId(), def -> { + def.setTranslatedDescription("App.Meter.modbusUnitId.description"); + }); + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/app/meter/MicrocareSdm630Meter.java b/io.openems.edge.core/src/io/openems/edge/app/meter/MicrocareSdm630Meter.java new file mode 100644 index 00000000000..57516f6c91d --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/app/meter/MicrocareSdm630Meter.java @@ -0,0 +1,186 @@ +package io.openems.edge.app.meter; + +import java.util.Map; +import java.util.function.Function; + +import org.osgi.service.cm.ConfigurationAdmin; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +import com.google.common.collect.Lists; +import com.google.gson.JsonElement; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; +import io.openems.common.types.EdgeConfig; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.app.common.props.CommonProps; +import io.openems.edge.app.common.props.CommunicationProps; +import io.openems.edge.app.common.props.ComponentProps; +import io.openems.edge.app.enums.MeterType; +import io.openems.edge.app.meter.MicrocareSdm630Meter.Property; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; +import io.openems.edge.core.appmanager.AbstractOpenemsAppWithProps; +import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.AppDef; +import io.openems.edge.core.appmanager.AppDescriptor; +import io.openems.edge.core.appmanager.AppManagerUtil; +import io.openems.edge.core.appmanager.AppManagerUtilSupplier; +import io.openems.edge.core.appmanager.ComponentUtil; +import io.openems.edge.core.appmanager.ConfigurationTarget; +import io.openems.edge.core.appmanager.Nameable; +import io.openems.edge.core.appmanager.OpenemsApp; +import io.openems.edge.core.appmanager.OpenemsAppCardinality; +import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.OpenemsAppStatus; +import io.openems.edge.core.appmanager.Type; +import io.openems.edge.core.appmanager.Type.Parameter; +import io.openems.edge.core.appmanager.Type.Parameter.BundleParameter; + +/** + * Describes a Microcare SDM630 meter App. + * + *

    +  {
    +    "appId":"App.Meter.Microcare.Sdm630",
    +    "alias":"SDM630 Zähler",
    +    "instanceId": UUID,
    +    "image": base64,
    +    "properties":{
    +      "METER_ID": "meter0",
    +      "TYPE": "PRODUCTION",
    +      "MODBUS_ID":"modbus0",
    +      "MODBUS_UNIT_ID":"10"
    +    },
    +    "appDescriptor": {
    +    	"websiteUrl": {@link AppDescriptor#getWebsiteUrl()}
    +    }
    +  }
    + * 
    + */ +@Component(name = "App.Meter.Microcare.Sdm630") +public class MicrocareSdm630Meter + extends AbstractOpenemsAppWithProps + implements OpenemsApp, AppManagerUtilSupplier { + + public enum Property implements Type, Nameable { + // Component-IDs + METER_ID(AppDef.componentId("meter0")), // + // Properties + ALIAS(CommonProps.alias()), // + TYPE(MeterProps.type(MeterType.GRID)), // + MODBUS_ID(AppDef.copyOfGeneric(ComponentProps.pickModbusId(), + def -> def.wrapField((app, property, l, parameter, field) -> { + field.isRequired(true); + }).setAutoGenerateField(false))), // + MODBUS_UNIT_ID(AppDef.copyOfGeneric(MeterProps.modbusUnitId(), // + def -> def.setAutoGenerateField(false) // + .setDefaultValue(10) // + .wrapField((app, property, l, parameter, field) -> field.isRequired(true)))), // + MODBUS_GROUP(CommunicationProps.modbusGroup(MODBUS_ID, MODBUS_ID.def(), // + MODBUS_UNIT_ID, MODBUS_UNIT_ID.def())), // + UNOFFICIAL_APP_WARNING(CommonProps.installationHintOfUnofficialApp()), // + ; + + private final AppDef def; + + private Property(AppDef def) { + this.def = def; + } + + @Override + public Type self() { + return this; + } + + @Override + public AppDef def() { + return this.def; + } + + @Override + public Function, BundleParameter> getParamter() { + return Parameter.functionOf(AbstractOpenemsApp::getTranslationBundle); + } + + } + + private final AppManagerUtil appManagerUtil; + + @Activate + public MicrocareSdm630Meter(// + @Reference ComponentManager componentManager, // + ComponentContext componentContext, // + @Reference ConfigurationAdmin cm, // + @Reference ComponentUtil componentUtil, // + @Reference AppManagerUtil appManagerUtil // + ) { + super(componentManager, componentContext, cm, componentUtil); + this.appManagerUtil = appManagerUtil; + } + + @Override + public AppManagerUtil getAppManagerUtil() { + return this.appManagerUtil; + } + + @Override + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appPropertyConfigurationFactory() { + return (t, p, l) -> { + // values the user enters + final var alias = this.getString(p, l, Property.ALIAS); + final var type = this.getString(p, Property.TYPE); + final var modbusId = this.getString(p, Property.MODBUS_ID); + final var modbusUnitId = this.getInt(p, Property.MODBUS_UNIT_ID); + + // values which are being auto generated by the appmanager + final var meterId = this.getId(t, p, Property.METER_ID); + + final var components = Lists.newArrayList(// + new EdgeConfig.Component(meterId, alias, "Meter.Microcare.SDM630", JsonUtils.buildJsonObject() // + .addProperty("type", type) // + .addProperty("modbusUnitId", modbusUnitId) // + .addProperty("modbus.id", modbusId) // + .build()) // + ); + + return new AppConfiguration(components); + }; + } + + @Override + public AppDescriptor getAppDescriptor() { + return AppDescriptor.create() // + .build(); + } + + @Override + public OpenemsAppCardinality getCardinality() { + return OpenemsAppCardinality.MULTIPLE; + } + + @Override + public OpenemsAppCategory[] getCategories() { + return new OpenemsAppCategory[] { OpenemsAppCategory.METER }; + } + + @Override + protected OpenemsAppStatus getStatus() { + return OpenemsAppStatus.BETA; + } + + @Override + protected MicrocareSdm630Meter getApp() { + return this; + } + + @Override + protected Property[] propertyValues() { + return Property.values(); + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/app/meter/SocomecMeter.java b/io.openems.edge.core/src/io/openems/edge/app/meter/SocomecMeter.java index 50221ded6f2..aa8372c6a01 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/meter/SocomecMeter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/meter/SocomecMeter.java @@ -1,6 +1,7 @@ package io.openems.edge.app.meter; -import java.util.EnumMap; +import java.util.Map; +import java.util.function.Function; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; @@ -15,22 +16,29 @@ import io.openems.common.function.ThrowingTriFunction; import io.openems.common.session.Language; import io.openems.common.types.EdgeConfig; -import io.openems.common.utils.EnumUtils; import io.openems.common.utils.JsonUtils; +import io.openems.edge.app.common.props.CommonProps; +import io.openems.edge.app.common.props.CommunicationProps; +import io.openems.edge.app.common.props.ComponentProps; +import io.openems.edge.app.common.props.PropsUtil; +import io.openems.edge.app.enums.MeterType; import io.openems.edge.app.meter.SocomecMeter.Property; import io.openems.edge.common.component.ComponentManager; import io.openems.edge.core.appmanager.AbstractOpenemsApp; -import io.openems.edge.core.appmanager.AppAssistant; +import io.openems.edge.core.appmanager.AbstractOpenemsAppWithProps; import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.AppDef; import io.openems.edge.core.appmanager.AppDescriptor; +import io.openems.edge.core.appmanager.AppManagerUtil; +import io.openems.edge.core.appmanager.AppManagerUtilSupplier; import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; -import io.openems.edge.core.appmanager.JsonFormlyUtil; -import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder.Type; -import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; -import io.openems.edge.core.appmanager.TranslationUtil; +import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.Type; +import io.openems.edge.core.appmanager.Type.Parameter; +import io.openems.edge.core.appmanager.Type.Parameter.BundleParameter; /** * Describes a App for a Socomec meter. @@ -54,35 +62,74 @@ * */ @Component(name = "App.Meter.Socomec") -public class SocomecMeter extends AbstractMeterApp implements OpenemsApp { +public class SocomecMeter extends AbstractOpenemsAppWithProps + implements OpenemsApp, AppManagerUtilSupplier { - public enum Property implements Nameable { + public enum Property implements Type { // Component-IDs - METER_ID, // + METER_ID(AppDef.componentId("meter1")), // // Properties - ALIAS, // - TYPE, // - MODBUS_ID, // - MODBUS_UNIT_ID, // - ; + ALIAS(AppDef.copyOfGeneric(CommonProps.alias())), // + TYPE(AppDef.copyOfGeneric(MeterProps.type(MeterType.GRID))), // + MODBUS_ID(AppDef.copyOfGeneric(ComponentProps.pickModbusId(), + def -> def.wrapField((app, property, l, parameter, field) -> { + if (PropsUtil.isHomeInstalled(app.getAppManagerUtil())) { + field.readonly(true); + } + field.isRequired(true); + }).setAutoGenerateField(false))), // + MODBUS_UNIT_ID(AppDef.copyOfGeneric(MeterProps.modbusUnitId(), // + def -> def.setAutoGenerateField(false) // + .setDefaultValue(7) // + .wrapField((app, property, l, parameter, field) -> field.isRequired(true)))), // + MODBUS_GROUP(AppDef.copyOfGeneric(CommunicationProps.modbusGroup(// + MODBUS_ID, MODBUS_ID.def(), MODBUS_UNIT_ID, MODBUS_UNIT_ID.def()))); + + private final AppDef def; + + private Property(AppDef def) { + this.def = def; + } + + @Override + public Type self() { + return this; + } + + @Override + public AppDef def() { + return this.def; + } + + @Override + public Function, BundleParameter> getParamter() { + return Parameter.functionOf(AbstractOpenemsApp::getTranslationBundle); + } } + private final AppManagerUtil appManagerUtil; + @Activate - public SocomecMeter(@Reference ComponentManager componentManager, ComponentContext componentContext, - @Reference ConfigurationAdmin cm, @Reference ComponentUtil componentUtil) { + public SocomecMeter(// + @Reference ComponentManager componentManager, // + ComponentContext componentContext, // + @Reference ConfigurationAdmin cm, // + @Reference ComponentUtil componentUtil, // + @Reference AppManagerUtil appManagerUtil // + ) { super(componentManager, componentContext, cm, componentUtil); + this.appManagerUtil = appManagerUtil; } @Override - protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appPropertyConfigurationFactory() { return (t, p, l) -> { + final var meterId = this.getId(t, p, Property.METER_ID); - var modbusId = this.getValueOrDefault(p, Property.MODBUS_ID, "modbus1"); - var meterId = this.getId(t, p, Property.METER_ID, "meter1"); - - var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName(l)); - var type = this.getValueOrDefault(p, Property.TYPE, "PRODUCTION"); - var modbusUnitId = EnumUtils.getAsInt(p, Property.MODBUS_UNIT_ID); + final var alias = this.getString(p, l, Property.ALIAS); + final var type = this.getString(p, Property.TYPE); + final var modbusId = this.getString(p, Property.MODBUS_ID); + final var modbusUnitId = this.getInt(p, Property.MODBUS_UNIT_ID); var components = Lists.newArrayList(// new EdgeConfig.Component(meterId, alias, "Meter.Socomec.Threephase", // @@ -98,44 +145,24 @@ protected ThrowingTriFunction getPropertyClass() { - return Property.class; + protected Property[] propertyValues() { + return Property.values(); + } + + @Override + public AppManagerUtil getAppManagerUtil() { + return this.appManagerUtil; } @Override @@ -143,4 +170,9 @@ public OpenemsAppCardinality getCardinality() { return OpenemsAppCardinality.MULTIPLE; } + @Override + protected SocomecMeter getApp() { + return this; + } + } diff --git a/io.openems.edge.core/src/io/openems/edge/app/peakshaving/PeakShaving.java b/io.openems.edge.core/src/io/openems/edge/app/peakshaving/PeakShaving.java new file mode 100644 index 00000000000..6ad2d50b3a4 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/app/peakshaving/PeakShaving.java @@ -0,0 +1,179 @@ +package io.openems.edge.app.peakshaving; + +import java.util.Map; +import java.util.function.Function; + +import org.osgi.service.cm.ConfigurationAdmin; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +import com.google.common.collect.Lists; +import com.google.gson.JsonElement; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; +import io.openems.common.types.EdgeConfig; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.app.common.props.CommonProps; +import io.openems.edge.app.common.props.ComponentProps; +import io.openems.edge.app.peakshaving.PeakShaving.Property; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; +import io.openems.edge.core.appmanager.AbstractOpenemsAppWithProps; +import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.AppDef; +import io.openems.edge.core.appmanager.AppDescriptor; +import io.openems.edge.core.appmanager.ComponentManagerSupplier; +import io.openems.edge.core.appmanager.ComponentUtil; +import io.openems.edge.core.appmanager.ConfigurationTarget; +import io.openems.edge.core.appmanager.OpenemsApp; +import io.openems.edge.core.appmanager.OpenemsAppCardinality; +import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.Type; +import io.openems.edge.core.appmanager.Type.Parameter; +import io.openems.edge.core.appmanager.Type.Parameter.BundleParameter; + +/** + * Describes a symmetric peak shaving app. + * + *
    +  {
    +    "appId":"App.PeakShaving.PeakShaving",
    +    "alias":"Lastspitzenkappung",
    +    "instanceId": UUID,
    +    "image": base64,
    +    "properties":{
    +      "CTRL_PEAK_SHAVING_ID": "ctrlPeakShaving0",
    +      "ESS_ID": "ess0",
    +      "METER_ID":"meter0",
    +      "PEAK_SHAVING_POWER": 7000,
    +      "RECHARGE_POWER": 6000
    +    },
    +    "appDescriptor": {
    +    	"websiteUrl": {@link AppDescriptor#getWebsiteUrl()}
    +    }
    +  }
    + * 
    + */ +@Component(name = "App.PeakShaving.PeakShaving") +public class PeakShaving extends AbstractOpenemsAppWithProps + implements OpenemsApp { + + public enum Property implements Type { + // Component-IDs + CTRL_PEAK_SHAVING_ID(AppDef.componentId("ctrlPeakShaving0")), // + // Properties + ALIAS(CommonProps.alias()), // + ESS_ID(AppDef.copyOfGeneric(ComponentProps.pickManagedSymmetricEssId(), + def -> def.wrapField((app, property, l, parameter, field) -> field.isRequired(true)) // + .bidirectional(CTRL_PEAK_SHAVING_ID, "ess.id", // + ComponentManagerSupplier::getComponentManager))), // + METER_ID(AppDef.copyOfGeneric(ComponentProps.pickElectricityGridMeterId(), + def -> def.wrapField((app, property, l, parameter, field) -> field.isRequired(true)) // + .bidirectional(CTRL_PEAK_SHAVING_ID, "meter.id", // + ComponentManagerSupplier::getComponentManager))), // + PEAK_SHAVING_POWER(AppDef.copyOfGeneric(PeakShavingProps.peakShavingPower(), def -> def // + .wrapField((app, property, l, parameter, field) -> { + field.isRequired(true); + }) // + .setAutoGenerateField(false) // + .bidirectional(CTRL_PEAK_SHAVING_ID, "peakShavingPower", + ComponentManagerSupplier::getComponentManager))), // + RECHARGE_POWER(AppDef.copyOfGeneric(PeakShavingProps.rechargePower(), def -> def // + .wrapField((app, property, l, parameter, field) -> { + field.isRequired(true); + }) // + .setAutoGenerateField(false) // + .bidirectional(CTRL_PEAK_SHAVING_ID, "rechargePower", // + ComponentManagerSupplier::getComponentManager))), // + PEAK_SHAVING_RECHARGE_POWER_GROUP( + PeakShavingProps.peakShavingRechargePowerGroup(PEAK_SHAVING_POWER, RECHARGE_POWER)), // + ; + + private final AppDef def; + + private Property(AppDef def) { + this.def = def; + } + + @Override + public Type self() { + return this; + } + + @Override + public AppDef def() { + return this.def; + } + + @Override + public Function, BundleParameter> getParamter() { + return Parameter.functionOf(AbstractOpenemsApp::getTranslationBundle); + } + + } + + @Activate + public PeakShaving(// + @Reference ComponentManager componentManager, // + ComponentContext componentContext, // + @Reference ConfigurationAdmin cm, // + @Reference ComponentUtil componentUtil // + ) { + super(componentManager, componentContext, cm, componentUtil); + } + + @Override + public AppDescriptor getAppDescriptor() { + return AppDescriptor.create() // + .build(); + } + + @Override + public OpenemsAppCategory[] getCategories() { + return new OpenemsAppCategory[] { OpenemsAppCategory.PEAK_SHAVING }; + } + + @Override + public OpenemsAppCardinality getCardinality() { + return OpenemsAppCardinality.SINGLE_IN_CATEGORY; + } + + @Override + protected PeakShaving getApp() { + return this; + } + + @Override + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appPropertyConfigurationFactory() { + return (t, m, l) -> { + final var ctrlPeakShavingId = this.getId(t, m, Property.CTRL_PEAK_SHAVING_ID); + + final var alias = this.getString(m, l, Property.ALIAS); + final var essId = this.getString(m, l, Property.ESS_ID); + final var meterId = this.getString(m, l, Property.METER_ID); + final var peakShavingPower = this.getInt(m, Property.PEAK_SHAVING_POWER); + final var rechargePower = this.getInt(m, Property.RECHARGE_POWER); + + final var components = Lists.newArrayList(// + new EdgeConfig.Component(ctrlPeakShavingId, alias, "Controller.Symmetric.PeakShaving", + JsonUtils.buildJsonObject() // + .addProperty("ess.id", essId) // + .addProperty("meter.id", meterId) // + .addProperty("peakShavingPower", peakShavingPower) // + .addProperty("rechargePower", rechargePower) // + .build())); + + return new AppConfiguration(components); + }; + } + + @Override + protected Property[] propertyValues() { + return Property.values(); + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/app/peakshaving/PeakShavingProps.java b/io.openems.edge.core/src/io/openems/edge/app/peakshaving/PeakShavingProps.java new file mode 100644 index 00000000000..1bdc79639d2 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/app/peakshaving/PeakShavingProps.java @@ -0,0 +1,110 @@ +package io.openems.edge.app.peakshaving; + +import static io.openems.common.channel.Unit.WATT; +import static io.openems.edge.app.common.props.CommonProps.defaultDef; +import static io.openems.edge.core.appmanager.formly.enums.InputType.NUMBER; + +import io.openems.common.utils.JsonUtils; +import io.openems.edge.core.appmanager.AppDef; +import io.openems.edge.core.appmanager.Nameable; +import io.openems.edge.core.appmanager.OpenemsApp; +import io.openems.edge.core.appmanager.TranslationUtil; +import io.openems.edge.core.appmanager.Type; +import io.openems.edge.core.appmanager.Type.Parameter.BundleProvider; +import io.openems.edge.core.appmanager.formly.Exp; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; + +public final class PeakShavingProps { + + /** + * Creates a {@link AppDef} for peak shaving power. + * + * @return the {@link AppDef} + */ + public static AppDef peakShavingPower() { + return AppDef.copyOfGeneric(defaultDef(), def -> def.setTranslatedLabel("App.PeakShaving.power.label") // + .setTranslatedDescription("App.PeakShaving.power.description") // + .setDefaultValue(0) // + .setField(JsonFormlyUtil::buildInputFromNameable, (app, property, l, parameter, field) -> { + field.setInputType(NUMBER) // + .setMin(0) // + .setUnit(WATT, l); + })); + } + + /** + * Creates a {@link AppDef} for peak shaving power per phase. + * + * @return the {@link AppDef} + */ + public static AppDef peakShavingPowerPerPhase() { + return AppDef.copyOfGeneric(peakShavingPower(), + def -> def.setTranslatedLabel("App.PeakShaving.powerPerPhase.label") // + .setTranslatedDescription("App.PeakShaving.powerPerPhase.description")); + } + + /** + * Creates a {@link AppDef} for peak shaving recharge power. + * + * @return the {@link AppDef} + */ + public static AppDef rechargePower() { + return AppDef.copyOfGeneric(defaultDef(), def -> def.setTranslatedLabel("App.PeakShaving.rechargePower.label") // + .setTranslatedDescription("App.PeakShaving.rechargePower.description") // + .setDefaultValue(0) // + .setField(JsonFormlyUtil::buildInputFromNameable, (app, property, l, parameter, field) -> { + field.setInputType(NUMBER) // + .setMin(0) // + .setUnit(WATT, l); + })); + } + + /** + * Creates a {@link AppDef} for peak shaving recharge power per phase. + * + * @return the {@link AppDef} + */ + public static AppDef rechargePowerPerPhase() { + return AppDef.copyOfGeneric(rechargePower(), + def -> def.setTranslatedLabel("App.PeakShaving.rechargePowerPerPhase.label") // + .setTranslatedDescription("App.PeakShaving.rechargePowerPerPhase.description")); + } + + /** + * Creates a {@link AppDef} which groups the {@link #peakShavingPower()} and the + * {@link #rechargePower()} to validate if any of them changes their values. + * + * @param
    the {@link OpenemsApp} type + * @param the type of the parameter + * @param

    the property type + * @param peakShavingPowerProp the {@link #peakShavingPower()} + * @param rechargePowerProp the {@link #rechargePower()} + * @return the {@link AppDef} + */ + public static > // + AppDef peakShavingRechargePowerGroup(// + final P peakShavingPowerProp, // + final P rechargePowerProp // + ) { + return AppDef.copyOfGeneric(defaultDef(), def -> def.setField(JsonFormlyUtil::buildFieldGroupFromNameable, + (app, property, l, parameter, field) -> { + final var validationText = TranslationUtil.getTranslation(parameter.bundle(), + "App.PeakShaving.peakShavingGreaterThanRecharge"); + field.hideKey() // + .setCustomValidation("peakShavingValidation", Exp.currentModelValue(peakShavingPowerProp) // + .greaterThan(Exp.currentModelValue(rechargePowerProp)), validationText, + peakShavingPowerProp) // + .setFieldGroup(JsonUtils.buildJsonArray() // + .add(peakShavingPowerProp.def().getField() + .get(app, peakShavingPowerProp, l, parameter) // + .build()) // + .add(rechargePowerProp.def().getField().get(app, rechargePowerProp, l, parameter) + .build()) // + .build()); + })); + } + + private PeakShavingProps() { + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/app/peakshaving/PhaseAccuratePeakShaving.java b/io.openems.edge.core/src/io/openems/edge/app/peakshaving/PhaseAccuratePeakShaving.java new file mode 100644 index 00000000000..607db919baa --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/app/peakshaving/PhaseAccuratePeakShaving.java @@ -0,0 +1,181 @@ +package io.openems.edge.app.peakshaving; + +import java.util.Map; +import java.util.function.Function; + +import org.osgi.service.cm.ConfigurationAdmin; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +import com.google.common.collect.Lists; +import com.google.gson.JsonElement; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; +import io.openems.common.types.EdgeConfig; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.app.common.props.CommonProps; +import io.openems.edge.app.common.props.ComponentProps; +import io.openems.edge.app.peakshaving.PhaseAccuratePeakShaving.Property; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; +import io.openems.edge.core.appmanager.AbstractOpenemsAppWithProps; +import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.AppDef; +import io.openems.edge.core.appmanager.AppDescriptor; +import io.openems.edge.core.appmanager.ComponentManagerSupplier; +import io.openems.edge.core.appmanager.ComponentUtil; +import io.openems.edge.core.appmanager.ConfigurationTarget; +import io.openems.edge.core.appmanager.Nameable; +import io.openems.edge.core.appmanager.OpenemsApp; +import io.openems.edge.core.appmanager.OpenemsAppCardinality; +import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.Type; +import io.openems.edge.core.appmanager.Type.Parameter; +import io.openems.edge.core.appmanager.Type.Parameter.BundleParameter; + +/** + * Describes a asymmetric peak shaving app. + * + *

    +  {
    +    "appId":"App.PeakShaving.PeakShaving",
    +    "alias":"Phasengenaue Lastspitzenkappung",
    +    "instanceId": UUID,
    +    "image": base64,
    +    "properties":{
    +      "CTRL_PEAK_SHAVING_ID": "ctrlPeakShaving0",
    +      "ESS_ID": "ess0",
    +      "METER_ID":"meter0",
    +      "PEAK_SHAVING_POWER": 7000,
    +      "RECHARGE_POWER": 6000
    +    },
    +    "appDescriptor": {
    +    	"websiteUrl": {@link AppDescriptor#getWebsiteUrl()}
    +    }
    +  }
    + * 
    + */ +@Component(name = "App.PeakShaving.PhaseAccuratePeakShaving") +public class PhaseAccuratePeakShaving + extends AbstractOpenemsAppWithProps + implements OpenemsApp { + + public enum Property implements Type, Nameable { + // Component-IDs + CTRL_PEAK_SHAVING_ID(AppDef.componentId("ctrlPeakShaving0")), // + // Properties + ALIAS(CommonProps.alias()), // + ESS_ID(AppDef.copyOfGeneric(ComponentProps.pickManagedSymmetricEssId(), + def -> def.wrapField((app, property, l, parameter, field) -> field.isRequired(true)) // + .bidirectional(CTRL_PEAK_SHAVING_ID, "ess.id", // + ComponentManagerSupplier::getComponentManager))), // + METER_ID(AppDef.copyOfGeneric(ComponentProps.pickElectricityGridMeterId(), + def -> def.wrapField((app, property, l, parameter, field) -> field.isRequired(true)) // + .bidirectional(CTRL_PEAK_SHAVING_ID, "meter.id", // + ComponentManagerSupplier::getComponentManager))), // + PEAK_SHAVING_POWER(AppDef.copyOfGeneric(PeakShavingProps.peakShavingPowerPerPhase(), def -> def // + .wrapField((app, property, l, parameter, field) -> { + field.isRequired(true); + }) // + .setAutoGenerateField(false) // + .bidirectional(CTRL_PEAK_SHAVING_ID, "peakShavingPower", // + ComponentManagerSupplier::getComponentManager))), // + RECHARGE_POWER(AppDef.copyOfGeneric(PeakShavingProps.rechargePowerPerPhase(), def -> def // + .wrapField((app, property, l, parameter, field) -> { + field.isRequired(true); + }) // + .setAutoGenerateField(false) // + .bidirectional(CTRL_PEAK_SHAVING_ID, "rechargePower", // + ComponentManagerSupplier::getComponentManager))), // + PEAK_SHAVING_RECHARGE_POWER_GROUP( + PeakShavingProps.peakShavingRechargePowerGroup(PEAK_SHAVING_POWER, RECHARGE_POWER)), // + ; + + private final AppDef def; + + private Property(AppDef def) { + this.def = def; + } + + @Override + public Type self() { + return this; + } + + @Override + public AppDef def() { + return this.def; + } + + @Override + public Function, BundleParameter> getParamter() { + return Parameter.functionOf(AbstractOpenemsApp::getTranslationBundle); + } + + } + + @Activate + public PhaseAccuratePeakShaving(// + @Reference ComponentManager componentManager, // + ComponentContext componentContext, // + @Reference ConfigurationAdmin cm, // + @Reference ComponentUtil componentUtil // + ) { + super(componentManager, componentContext, cm, componentUtil); + } + + @Override + public AppDescriptor getAppDescriptor() { + return AppDescriptor.create() // + .build(); + } + + @Override + public OpenemsAppCategory[] getCategories() { + return new OpenemsAppCategory[] { OpenemsAppCategory.PEAK_SHAVING }; + } + + @Override + public OpenemsAppCardinality getCardinality() { + return OpenemsAppCardinality.SINGLE_IN_CATEGORY; + } + + @Override + protected PhaseAccuratePeakShaving getApp() { + return this; + } + + @Override + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appPropertyConfigurationFactory() { + return (t, m, l) -> { + final var ctrlPeakShavingId = this.getId(t, m, Property.CTRL_PEAK_SHAVING_ID); + + final var alias = this.getString(m, l, Property.ALIAS); + final var essId = this.getString(m, l, Property.ESS_ID); + final var meterId = this.getString(m, l, Property.METER_ID); + final var peakShavingPower = this.getInt(m, Property.PEAK_SHAVING_POWER); + final var rechargePower = this.getInt(m, Property.RECHARGE_POWER); + + final var components = Lists.newArrayList(// + new EdgeConfig.Component(ctrlPeakShavingId, alias, "Controller.Asymmetric.PeakShaving", + JsonUtils.buildJsonObject() // + .addProperty("ess.id", essId) // + .addProperty("meter.id", meterId) // + .addProperty("peakShavingPower", peakShavingPower) // + .addProperty("rechargePower", rechargePower) // + .build())); + + return new AppConfiguration(components); + }; + } + + @Override + protected Property[] propertyValues() { + return Property.values(); + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/AbstractPvInverter.java b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/AbstractPvInverter.java index d3655517073..aed29b956bd 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/AbstractPvInverter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/AbstractPvInverter.java @@ -16,13 +16,13 @@ import io.openems.edge.core.appmanager.AbstractEnumOpenemsApp; import io.openems.edge.core.appmanager.AbstractOpenemsApp; import io.openems.edge.core.appmanager.ComponentUtil; -import io.openems.edge.core.appmanager.JsonFormlyUtil; -import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder; -import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder.Type; -import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder.Validation; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsAppCategory; import io.openems.edge.core.appmanager.TranslationUtil; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; +import io.openems.edge.core.appmanager.formly.builder.InputBuilder; +import io.openems.edge.core.appmanager.formly.enums.InputType; +import io.openems.edge.core.appmanager.formly.enums.Validation; public abstract class AbstractPvInverter & Nameable> extends AbstractEnumOpenemsApp { @@ -54,7 +54,7 @@ protected final List getComponents(String factoryId, String pvInverte protected static > InputBuilder buildIp(Language language, PROPERTY property) { final var bundle = AbstractOpenemsApp.getTranslationBundle(language); return JsonFormlyUtil.buildInput(property) // - .setLabel(TranslationUtil.getTranslation(bundle, "ipAddress")) // + .setLabel(TranslationUtil.getTranslation(bundle, "communication.ipAddress")) // .setDescription(TranslationUtil.getTranslation(bundle, "App.PvInverter.ip.description")) // .setDefaultValue("192.168.178.85") // .isRequired(true) // @@ -64,9 +64,9 @@ protected static > InputBuilder buildIp(Language protected static > InputBuilder buildPort(Language language, PROPERTY property) { final var bundle = AbstractOpenemsApp.getTranslationBundle(language); return JsonFormlyUtil.buildInput(Property.PORT) // - .setLabel(TranslationUtil.getTranslation(bundle, "port")) // + .setLabel(TranslationUtil.getTranslation(bundle, "communication.port")) // .setDescription(TranslationUtil.getTranslation(bundle, "App.PvInverter.port.description")) // - .setInputType(Type.NUMBER) // + .setInputType(InputType.NUMBER) // .setDefaultValue(502) // .setMin(0) // .isRequired(true); @@ -76,9 +76,9 @@ protected static > InputBuilder buildModbusUnitI PROPERTY property) { final var bundle = AbstractOpenemsApp.getTranslationBundle(language); return JsonFormlyUtil.buildInput(Property.MODBUS_UNIT_ID) // - .setLabel(TranslationUtil.getTranslation(bundle, "modbusUnitId")) // + .setLabel(TranslationUtil.getTranslation(bundle, "communication.modbusUnitId")) // .setDescription(TranslationUtil.getTranslation(bundle, "App.PvInverter.modbusUnitId.description")) // - .setInputType(Type.NUMBER) // + .setInputType(InputType.NUMBER) // .setMin(0) // .isRequired(true); } diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/CommonPvInverterConfiguration.java b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/CommonPvInverterConfiguration.java index 08eab927ed6..c3932e64c6d 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/CommonPvInverterConfiguration.java +++ b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/CommonPvInverterConfiguration.java @@ -9,35 +9,25 @@ import io.openems.common.types.EdgeConfig.Component; import io.openems.common.utils.JsonUtils; import io.openems.common.utils.JsonUtils.JsonObjectBuilder; +import io.openems.edge.app.common.props.CommunicationProps; import io.openems.edge.core.appmanager.AppDef; -import io.openems.edge.core.appmanager.JsonFormlyUtil; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; -import io.openems.edge.core.appmanager.Type.Parameter.BundleParameter; +import io.openems.edge.core.appmanager.Type.Parameter.BundleProvider; public final class CommonPvInverterConfiguration { private CommonPvInverterConfiguration() { } - private static final AppDef defaultDef() { - return AppDef.of() // - .setTranslationBundleSupplier(BundleParameter::getBundle); - } - /** * Creates a {@link AppDef} for a PV-Inverter ip-address. * * @return the {@link AppDef} */ - public static final AppDef ip() { - return defaultDef() // - .setTranslatedLabel("ipAddress") // - .setTranslatedDescription("App.PvInverter.ip.description") // - .setDefaultValue("192.168.178.85") // - .setField(JsonFormlyUtil::buildInputFromNameable, (app, prop, l, param, f) -> // - f.isRequired(true) // - .setValidation(JsonFormlyUtil.InputBuilder.Validation.IP)); + public static final AppDef ip() { + return AppDef.copyOfGeneric(CommunicationProps.ip(), def -> def // + .setTranslatedDescription("App.PvInverter.ip.description")); } /** @@ -45,14 +35,9 @@ public static final AppDef ip() { * * @return the {@link AppDef} */ - public static final AppDef port() { - return defaultDef() // - .setTranslatedLabel("port") // - .setTranslatedDescription("App.PvInverter.port.description") // - .setDefaultValue(502) // - .setField(JsonFormlyUtil::buildInputFromNameable, (app, prop, l, param, f) -> // - f.setInputType(JsonFormlyUtil.InputBuilder.Type.NUMBER) // - .setMin(0)); + public static final AppDef port() { + return AppDef.copyOfGeneric(CommunicationProps.port(), def -> def // + .setTranslatedDescription("App.PvInverter.port.description")); } /** @@ -60,13 +45,9 @@ public static final AppDef port() { * * @return the {@link AppDef} */ - public static final AppDef modbusUnitId() { - return defaultDef() // - .setTranslatedLabel("modbusUnitId") // - .setTranslatedDescription("App.PvInverter.modbusUnitId.description") // - .setField(JsonFormlyUtil::buildInputFromNameable, (app, prop, l, param, f) -> // - f.setInputType(JsonFormlyUtil.InputBuilder.Type.NUMBER) // - .setMin(0)); + public static final AppDef modbusUnitId() { + return AppDef.copyOfGeneric(CommunicationProps.modbusUnitId(), def -> def // + .setTranslatedDescription("App.PvInverter.modbusUnitId.description")); } /** diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/SmaPvInverter.java b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/SmaPvInverter.java index 58f9f37a4c8..e41c7ba5736 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/SmaPvInverter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/SmaPvInverter.java @@ -24,7 +24,6 @@ import io.openems.edge.core.appmanager.AppDescriptor; import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; -import io.openems.edge.core.appmanager.JsonFormlyUtil; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; @@ -32,6 +31,7 @@ import io.openems.edge.core.appmanager.Type; import io.openems.edge.core.appmanager.Type.Parameter; import io.openems.edge.core.appmanager.Type.Parameter.BundleParameter; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; /** * Describes a App for SMA PV-Inverter. @@ -68,19 +68,19 @@ public static enum Property implements Type def // .wrapField((app, property, l, parameter, field) -> { field.isRequired(true); - })), // - PORT(AppDef.copyOf(Property.class, CommonPvInverterConfiguration.port()) // + }))), // + PORT(AppDef.copyOfGeneric(CommonPvInverterConfiguration.port(), def -> def // .wrapField((app, property, l, parameter, field) -> { field.isRequired(true); - })), // - MODBUS_UNIT_ID(AppDef.copyOf(Property.class, CommonPvInverterConfiguration.modbusUnitId()) // + }))), // + MODBUS_UNIT_ID(AppDef.copyOfGeneric(CommonPvInverterConfiguration.modbusUnitId(), def -> def // .setTranslatedDescriptionWithAppPrefix(".modbusUnitId.description") // .wrapField((app, property, l, parameter, field) -> { field.isRequired(true); - })), // + }))), // PHASE(AppDef.of(SmaPvInverter.class) // .setTranslatedLabelWithAppPrefix(".phase.label") // ) .setTranslatedDescriptionWithAppPrefix(".phase.description") // diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java b/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java index d1a82b76cf5..49e9a44133b 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java +++ b/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java @@ -30,13 +30,14 @@ import io.openems.edge.core.appmanager.AppDescriptor; import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; -import io.openems.edge.core.appmanager.JsonFormlyUtil; -import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder.Type; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; import io.openems.edge.core.appmanager.TranslationUtil; +import io.openems.edge.core.appmanager.formly.Exp; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; +import io.openems.edge.core.appmanager.formly.enums.InputType; /** * Describes a App for a Grid Optimized Charge. @@ -128,10 +129,10 @@ public AppAssistant getAppAssistant(Language language) { this.getAppId() + ".sellToGridLimitEnabled.label")) // .build()) .add(JsonFormlyUtil.buildInput(Property.MAXIMUM_SELL_TO_GRID_POWER) // - .setInputType(Type.NUMBER) // + .setInputType(InputType.NUMBER) // .isRequired(true) // .setMin(0) // - .onlyShowIfChecked(Property.SELL_TO_GRID_LIMIT_ENABLED) // + .onlyShowIf(Exp.currentModelValue(Property.SELL_TO_GRID_LIMIT_ENABLED).notNull()) .setLabel(TranslationUtil.getTranslation(bundle, this.getAppId() + ".maximumSellToGridPower.label")) // .build()) diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/SelfConsumptionOptimization.java b/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/SelfConsumptionOptimization.java index 463d4d044c0..1ecdc08f2cb 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/SelfConsumptionOptimization.java +++ b/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/SelfConsumptionOptimization.java @@ -25,12 +25,13 @@ import io.openems.edge.core.appmanager.AppDescriptor; import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; -import io.openems.edge.core.appmanager.JsonFormlyUtil; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; import io.openems.edge.core.appmanager.TranslationUtil; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; +import io.openems.edge.core.appmanager.formly.builder.SelectBuilder; import io.openems.edge.ess.api.ManagedSymmetricEss; import io.openems.edge.meter.api.ElectricityMeter; @@ -106,8 +107,8 @@ public AppAssistant getAppAssistant(Language language) { TranslationUtil.getTranslation(bundle, this.getAppId() + ".ess.description")) // .isRequired(true) // .setOptions(this.componentManager.getEnabledComponentsOfType(ManagedSymmetricEss.class), - JsonFormlyUtil.SelectBuilder.DEFAULT_COMPONENT_2_LABEL, - JsonFormlyUtil.SelectBuilder.DEFAULT_COMPONENT_2_VALUE) // + SelectBuilder.DEFAULT_COMPONENT_2_LABEL, + SelectBuilder.DEFAULT_COMPONENT_2_VALUE) // .build()) .add(JsonFormlyUtil.buildSelect(Property.METER_ID)// .setLabel(TranslationUtil.getTranslation(bundle, this.getAppId() + ".meter.label")) // @@ -115,8 +116,8 @@ public AppAssistant getAppAssistant(Language language) { TranslationUtil.getTranslation(bundle, this.getAppId() + ".meter.description")) // .isRequired(true) // .setOptions(this.componentManager.getEnabledComponentsOfType(ElectricityMeter.class), - JsonFormlyUtil.SelectBuilder.DEFAULT_COMPONENT_2_LABEL, - JsonFormlyUtil.SelectBuilder.DEFAULT_COMPONENT_2_VALUE) // + SelectBuilder.DEFAULT_COMPONENT_2_LABEL, + SelectBuilder.DEFAULT_COMPONENT_2_VALUE) // .build()) .build()) .build(); diff --git a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StromdaoCorrently.java b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StromdaoCorrently.java index 1ab0f1a6824..b7a913fb0ee 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StromdaoCorrently.java +++ b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StromdaoCorrently.java @@ -25,12 +25,12 @@ import io.openems.edge.core.appmanager.AppDescriptor; import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; -import io.openems.edge.core.appmanager.JsonFormlyUtil; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; import io.openems.edge.core.appmanager.TranslationUtil; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; /** * Describes a App for StromdaoCorrently. diff --git a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/Tibber.java b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/Tibber.java index b06e4691fea..7c42d58de16 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/Tibber.java +++ b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/Tibber.java @@ -1,5 +1,7 @@ package io.openems.edge.app.timeofusetariff; +import static io.openems.edge.core.appmanager.formly.enums.InputType.PASSWORD; + import java.util.Map; import java.util.function.Function; @@ -26,13 +28,12 @@ import io.openems.edge.core.appmanager.AppDescriptor; import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; -import io.openems.edge.core.appmanager.JsonFormlyUtil; -import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; import io.openems.edge.core.appmanager.Type; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; /** * Describes a App for Tibber. @@ -72,7 +73,7 @@ public static enum Property implements Type // - f.setInputType(InputBuilder.Type.PASSWORD) // + f.setInputType(PASSWORD) // .isRequired(true)) // .setAllowedToSave(false)), // ; diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java index d8b16af6224..915d1fcea9b 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java @@ -8,7 +8,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.NoSuchElementException; import java.util.ResourceBundle; import java.util.TreeMap; import java.util.function.Function; @@ -32,6 +31,7 @@ import io.openems.common.types.EdgeConfig.Component; import io.openems.common.utils.JsonUtils; import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.common.user.User; import io.openems.edge.core.appmanager.dependency.Dependency; import io.openems.edge.core.appmanager.dependency.DependencyDeclaration; import io.openems.edge.core.appmanager.validator.CheckCardinality; @@ -39,7 +39,7 @@ import io.openems.edge.core.appmanager.validator.ValidatorConfig; public abstract class AbstractOpenemsApp // - implements OpenemsApp { + implements OpenemsApp, ComponentUtilSupplier, ComponentManagerSupplier { protected final ComponentManager componentManager; protected final ConfigurationAdmin cm; @@ -69,7 +69,7 @@ protected AbstractOpenemsApp(ComponentManager componentManager, ComponentContext OpenemsNamedException> appPropertyConfigurationFactory(); protected final void assertCheckables(ConfigurationTarget t, Checkable... checkables) throws OpenemsNamedException { - if (t != ConfigurationTarget.ADD && t != ConfigurationTarget.UPDATE) { + if (!t.isAddOrUpdate()) { return; } final List errors = new ArrayList<>(); @@ -143,7 +143,7 @@ public AppConfiguration getAppConfiguration(ConfigurationTarget target, JsonObje var configuration = this.configuration(errors, target, language, enumMap); if (!errors.isEmpty()) { - throw new OpenemsException(errors.stream().collect(Collectors.joining("|"))); + throw new OpenemsException(this.getAppId() + ": " + errors.stream().collect(Collectors.joining("|"))); } return configuration; } @@ -422,10 +422,18 @@ private void validateDependecies(List errors, List configDep var appConfigErrors = new LinkedList(); if (appConfig.specificInstanceId != null) { try { - var instance = appManager.findInstanceById(appConfig.specificInstanceId); - checkProperties(errors, instance.properties, appConfig, dependency.key); - } catch (NoSuchElementException e) { - appConfigErrors.add("Specific InstanceId[" + appConfig.specificInstanceId + "] not found!"); + final var instance = appManager.findInstanceByIdOrError(appConfig.specificInstanceId); + final var app = appManager.findAppById(instance.appId); + final var props = app.map(a -> { + try { + return AbstractOpenemsApp.fillUpProperties(a, instance.properties); + } catch (UnsupportedOperationException e) { + return instance.properties; + } + }).orElse(instance.properties); + checkProperties(errors, props, appConfig, dependency.key); + } catch (OpenemsNamedException e) { + appConfigErrors.add(e.getMessage()); } } else { @@ -452,36 +460,82 @@ private void validateDependecies(List errors, List configDep } // check if dependency apps are available for (var dependency : configDependencies) { + final OpenemsAppInstance appInstance; try { - var appInstance = appManager.findInstanceById(dependency.instanceId); - var dd = neededDependencies.stream().filter(d -> d.key.equals(dependency.key)).findAny(); - if (dd.isEmpty()) { - errors.add("Can not get DependencyDeclaration of Dependency[" + dependency.key + "]"); - continue; - } + appInstance = appManager.findInstanceByIdOrError(dependency.instanceId); + } catch (OpenemsNamedException e) { + errors.add(e.getMessage()); + continue; + } + var dd = neededDependencies.stream().filter(d -> d.key.equals(dependency.key)).findAny(); + if (dd.isEmpty()) { + errors.add("Can not get DependencyDeclaration of Dependency[" + dependency.key + "]"); + continue; + } - // get app config - var appConfig = dd.get().appConfigs.stream() // - .filter(c -> c.specificInstanceId != null) // - .filter(c -> c.specificInstanceId.equals(appInstance.instanceId)).findAny(); + // get app config + var appConfig = dd.get().appConfigs.stream() // + .filter(c -> c.specificInstanceId != null) // + .filter(c -> c.specificInstanceId.equals(appInstance.instanceId)).findAny(); - if (appConfig.isEmpty()) { - appConfig = dd.get().appConfigs.stream() // - .filter(c -> c.appId != null) // - .filter(c -> c.appId.equals(appInstance.appId)).findAny(); + if (appConfig.isEmpty()) { + appConfig = dd.get().appConfigs.stream() // + .filter(c -> c.appId != null) // + .filter(c -> c.appId.equals(appInstance.appId)).findAny(); - if (appConfig.isEmpty()) { - errors.add("Can not get DependencyAppConfig of Dependency[" + dependency.key + "]"); - continue; - } + if (appConfig.isEmpty()) { + errors.add("Can not get DependencyAppConfig of Dependency[" + dependency.key + "]"); + continue; } + } + + var copy = appInstance.properties.deepCopy(); + try { + final var app = appManager.findAppByIdOrError(appInstance.appId); + copy = AbstractOpenemsApp.fillUpProperties(app, appInstance.properties); + } catch (OpenemsNamedException e) { + errors.add(e.getMessage()); + } catch (UnsupportedOperationException e) { + // get props not supported + } + // when available check properties + checkProperties(errors, copy, appConfig.get(), dependency.key); + } + } - // when available check properties - checkProperties(errors, appInstance.properties, appConfig.get(), dependency.key); - } catch (NoSuchElementException e) { - errors.add("App with instance[" + dependency.instanceId + "] not available!"); + /** + * Creates a copy of the original configuration and fills up properties which + * are binded bidirectional. + * + *

    + * e. g. a property in a component is the same as one configured in the app so + * it directly gets stored in the component configuration and not twice to avoid + * miss matching errors. + * + * @param original the original configuration + * @param app the app to get the properties from + * @return a copy of the original one with the filled up properties + */ + public static JsonObject fillUpProperties(// + final OpenemsApp app, // + final JsonObject original // + ) { + final var copy = original.deepCopy(); + for (var prop : app.getProperties()) { + if (copy.has(prop.name)) { + continue; + } + if (prop.bidirectionalValue == null) { + continue; + } + var value = prop.bidirectionalValue.apply(copy); + if (value == null) { + continue; } + // add value to configuration + copy.add(prop.name, value); } + return copy; } private static final void checkProperties(List errors, JsonObject actualAppProperties, @@ -516,6 +570,11 @@ public String getName(Language language) { return AbstractOpenemsApp.getTranslation(language, this.getAppId() + ".Name"); } + @Override + public String getShortName(Language language) { + return AbstractOpenemsApp.getNullableTranslation(language, this.getAppId() + ".Name.short"); + } + @Override public String getImage() { var imageName = this.getClass().getSimpleName() + ".png"; @@ -531,6 +590,16 @@ public OpenemsAppPropertyDefinition[] getProperties() { throw new UnsupportedOperationException(); } + @Override + public AppAssistant getAppAssistant(User user) { + return this.getAppAssistant(user.getLanguage()); + } + + protected AppAssistant getAppAssistant(Language l) { + return AppAssistant.create(this.getName(l)) // + .build(); + } + protected abstract PROPERTY[] propertyValues(); protected final PROPERTY getPropertyByName(String name) { @@ -543,6 +612,10 @@ protected static String getTranslation(Language language, String key) { return TranslationUtil.getTranslation(getTranslationBundle(language), key); } + protected static String getNullableTranslation(Language language, String key) { + return TranslationUtil.getNullableTranslation(getTranslationBundle(language), key); + } + /** * Gets the {@link ResourceBundle} based on the given {@link Language}. * @@ -568,6 +641,21 @@ public static ResourceBundle getTranslationBundle(Language language) { return ResourceBundle.getBundle("io.openems.edge.core.appmanager.translation", language.getLocal()); } + /** + * Gets the {@link ResourceBundle} based on the given {@link Language}. + * + *

    + * Used in {@link OpenemsApps} to create their {@link Type#getParamter()}. + * + * @param l the {@link Language} of the translations + * @return the {@link ResourceBundle} + * @implNote just a name alias to + * {@link AbstractOpenemsApp#getTranslationBundle(Language)} + */ + public static ResourceBundle createResourceBundle(Language l) { + return getTranslationBundle(l); + } + protected static final String base64OfImage(URL url) { if (url == null) { return null; @@ -586,4 +674,14 @@ protected static final Component getComponentWithFactoryId(List compo return components.stream().filter(t -> t.getFactoryId().equals(factoryId)).findFirst().orElse(null); } + @Override + public ComponentManager getComponentManager() { + return this.componentManager; + } + + @Override + public ComponentUtil getComponentUtil() { + return this.componentUtil; + } + } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsAppWithProps.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsAppWithProps.java index cb9de0ec35b..398b38b5c40 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsAppWithProps.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsAppWithProps.java @@ -1,5 +1,6 @@ package io.openems.edge.core.appmanager; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -21,13 +22,16 @@ import io.openems.common.utils.JsonUtils; import io.openems.common.utils.StringUtils; import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.common.user.User; import io.openems.edge.core.appmanager.Type.GetParameterValues; import io.openems.edge.core.appmanager.dependency.Dependency; +import io.openems.edge.core.appmanager.flag.Flag; +import io.openems.edge.core.appmanager.flag.Flags; public abstract class AbstractOpenemsAppWithProps, // PROPERTY extends Type & Nameable, // - PARAMETER extends Type.Parameter // + PARAMETER // > extends AbstractOpenemsApp implements OpenemsApp { protected AbstractOpenemsAppWithProps(ComponentManager componentManager, ComponentContext componentContext, @@ -80,6 +84,13 @@ protected String getString(// return this.getString(map, l, property, PROPERTY::def); } + protected String getString(// + final Map map, // + final PROPERTY property // + ) throws OpenemsNamedException { + return this.getString(map, Language.DEFAULT, property); + } + protected JsonArray getJsonArray(// final Map map, // final PROPERTY property // @@ -150,15 +161,20 @@ public OpenemsAppPropertyDefinition[] getProperties() { } @Override - public AppAssistant getAppAssistant(Language language) { + public AppAssistant getAppAssistant(User user) { + final var language = user.getLanguage(); final var parameter = this.singletonParameter(language); final var alias = this.getAlias(language, parameter.get()); return AppAssistant.create(this.getName(language)) // .onlyIf(alias != null, t -> t.setAlias(alias)) // .fields(Arrays.stream(this.propertyValues()) // - .filter(p -> p.def().isAutoGenerateField()) // + .filter(p -> p.def().getIsAllowedToSee() // + .test(this.getApp(), p, language, parameter.get(), user)) // .filter(p -> p.def().getField() != null) // - .map(p -> p.def().getField().get(this.getApp(), p, language, parameter.get()).build()) // + .map(p -> p.def().getField().get(this.getApp(), p, language, parameter.get()) // + .readonly(!p.def().getIsAllowedToEdit() // + .test(this.getApp(), p, language, parameter.get(), user)) // + .build()) // .collect(JsonUtils.toJsonArray())) // .build(); } @@ -182,7 +198,7 @@ public AppConfiguration getAppConfiguration(// ) throws OpenemsNamedException { return super.getAppConfiguration(// target, // - this.fillUpProperties(config), // + AbstractOpenemsApp.fillUpProperties(this, config), // language // ); } @@ -193,44 +209,11 @@ protected List getValidationErrors(// final List dependecies // ) { return super.getValidationErrors(// - this.fillUpProperties(jProperties), // + AbstractOpenemsApp.fillUpProperties(this, jProperties), // dependecies // ); } - /** - * Creates a copy of the original configuration and fills up properties which - * are binded bidirectional. - * - *

    - * e. g. a property in a component is the same as one configured in the app so - * it directly gets stored in the component configuration and not twice to avoid - * miss matching errors. - * - * @param original the original configuration - * @return a copy of the original one with the filled up properties - */ - public JsonObject fillUpProperties(// - final JsonObject original // - ) { - final var copy = original.deepCopy(); - for (var prop : this.getProperties()) { - if (copy.has(prop.name)) { - continue; - } - if (prop.bidirectionalValue == null) { - continue; - } - var value = prop.bidirectionalValue.apply(copy); - if (value == null) { - continue; - } - // add value to configuration - copy.add(prop.name, value); - } - return copy; - } - private Function mapDefaultValue(// final PROPERTY property, // final PARAMETER parameter // @@ -301,4 +284,17 @@ public final T get() { protected abstract APP getApp(); + @Override + public Flag[] flags() { + final var flags = new ArrayList<>(); + if (this.getStatus() == OpenemsAppStatus.BETA) { + flags.add(Flags.SHOW_AFTER_KEY_REDEEM); + } + return flags.toArray(Flag[]::new); + } + + protected OpenemsAppStatus getStatus() { + return OpenemsAppStatus.STABLE; + } + } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppCenterBackendUtil.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppCenterBackendUtil.java index 426d9e64396..7dcc5d3ae99 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppCenterBackendUtil.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppCenterBackendUtil.java @@ -20,7 +20,7 @@ public interface AppCenterBackendUtil { * @param appId the id of the {@link OpenemsApp} * @return true if the key can be applied */ - public boolean isKeyApplicable(User user, String key, String appId); + public boolean isKeyApplicable(User user, String key, String appId) throws OpenemsNamedException; /** * Adds a install app history entry to the given key. diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppCenterBackendUtilImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppCenterBackendUtilImpl.java index 719866513bb..cf9d5c746a8 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppCenterBackendUtilImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppCenterBackendUtilImpl.java @@ -12,6 +12,8 @@ import org.osgi.service.component.annotations.ReferenceCardinality; import org.osgi.service.component.annotations.ReferencePolicy; import org.osgi.service.component.annotations.ReferencePolicyOption; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; @@ -28,27 +30,31 @@ import io.openems.common.jsonrpc.response.AppCenterGetPossibleAppsResponse; import io.openems.common.jsonrpc.response.AppCenterGetPossibleAppsResponse.Bundle; import io.openems.common.jsonrpc.response.AppCenterIsKeyApplicableResponse; +import io.openems.edge.common.component.ComponentManager; import io.openems.edge.common.user.User; import io.openems.edge.controller.api.backend.ControllerApiBackend; @Component public class AppCenterBackendUtilImpl implements AppCenterBackendUtil { + private final Logger log = LoggerFactory.getLogger(this.getClass()); + @Reference(policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.OPTIONAL) private volatile ControllerApiBackend backend; + private final ComponentManager componentManager; + @Activate - public AppCenterBackendUtilImpl() { + public AppCenterBackendUtilImpl(// + @Reference ComponentManager componentManager // + ) { + this.componentManager = componentManager; } @Override - public boolean isKeyApplicable(User user, String key, String appId) { - try { - var response = this.handleRequest(user, new AppCenterIsKeyApplicableRequest(key, appId)); - return AppCenterIsKeyApplicableResponse.from(response).isKeyApplicable; - } catch (OpenemsNamedException e) { - return false; - } + public boolean isKeyApplicable(User user, String key, String appId) throws OpenemsNamedException { + var response = this.handleRequest(user, new AppCenterIsKeyApplicableRequest(key, appId)); + return AppCenterIsKeyApplicableResponse.from(response).isKeyApplicable; } @Override @@ -84,15 +90,16 @@ public List getInstalledApps() throws OpenemsNamedException { @Override public boolean isConnected() { - if (this.backend == null) { + final var backendApi = this.getBackend(); + if (backendApi == null) { return false; } - return this.backend.isConnected(); + return backendApi.isConnected(); } private final CompletableFuture handleRequestAsync(User user, JsonrpcRequest request) throws OpenemsNamedException { - return this.getBackend().handleJsonrpcRequest(user, new AppCenterRequest(request)); + return this.getBackendOrError().handleJsonrpcRequest(user, new AppCenterRequest(request)); } private final JsonrpcResponseSuccess handleRequest(User user, JsonrpcRequest request) throws OpenemsNamedException { @@ -104,11 +111,24 @@ private final JsonrpcResponseSuccess handleRequest(User user, JsonrpcRequest req } } - private final ControllerApiBackend getBackend() throws OpenemsNamedException { - if (!this.isConnected()) { + private final ControllerApiBackend getBackendOrError() throws OpenemsNamedException { + final var backendApi = this.getBackend(); + if (backendApi == null || !backendApi.isConnected()) { throw new OpenemsException("Backend not connected!"); } - return this.backend; + return backendApi; + } + + private final ControllerApiBackend getBackend() { + if (this.backend != null) { + return this.backend; + } + final var backendApis = this.componentManager.getEnabledComponentsOfType(ControllerApiBackend.class); + if (backendApis.isEmpty()) { + return null; + } + this.log.warn("BackendApi Controller exists but was not injected!"); + return backendApis.get(0); } private static final OpenemsNamedException getOpenemsException(Throwable e) { diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppDef.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppDef.java index 6caefe21e83..97aa97ab874 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppDef.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppDef.java @@ -1,5 +1,6 @@ package io.openems.edge.core.appmanager; +import java.util.Arrays; import java.util.Objects; import java.util.Optional; import java.util.ResourceBundle; @@ -14,10 +15,12 @@ import com.google.gson.JsonPrimitive; import io.openems.common.session.Language; +import io.openems.common.session.Role; import io.openems.edge.common.component.ComponentManager; -import io.openems.edge.core.appmanager.JsonFormlyUtil.FormlyBuilder; +import io.openems.edge.common.user.User; import io.openems.edge.core.appmanager.Type.Parameter; -import io.openems.edge.core.appmanager.Type.Parameter.BundleParameter; +import io.openems.edge.core.appmanager.Type.Parameter.BundleProvider; +import io.openems.edge.core.appmanager.formly.builder.FormlyBuilder; /** * AppDef short for definition of a property for an app. @@ -28,7 +31,7 @@ */ public class AppDef // + PARAMETER> // implements OnlyIf>, Self> { private static final Logger LOG = LoggerFactory.getLogger(AppDef.class); @@ -57,6 +60,49 @@ public static interface FieldValuesSupplier { } + @FunctionalInterface + public static interface FieldValuesPredicate { + + public static FieldValuesPredicate ALWAYS_TRUE = (app, property, + l, parameter) -> true; + public static FieldValuesPredicate ALWAYS_FALSE = (app, + property, l, parameter) -> false; + + /** + * A function with the values of the current field. + * + * @param app the current app + * @param property the current property + * @param l the current language + * @param parameter the current provided parameters + * @return true if the test was successful + */ + public boolean test(A app, P property, Language l, M parameter); + + } + + @FunctionalInterface + public static interface FieldValuesBiPredicate { + + public static final FieldValuesBiPredicate TRUE// + = (app, property, l, parameter, user) -> true; + public static final FieldValuesBiPredicate FALSE// + = (app, property, l, parameter, user) -> false; + + /** + * A function with the values of the current field. + * + * @param app the current app + * @param property the current property + * @param l the current language + * @param parameter the current provided parameters + * @param extraParameter the extra parameter to pass to the predicate + * @return true if the test was successful + */ + public boolean test(A app, P property, Language l, M parameter, E extraParameter); + + } + /** * Functional interface with field values and a extra parameter and and return * value. @@ -129,11 +175,6 @@ public static interface FieldValuesConsumer { */ private FieldValuesSupplier> field; - /** - * Determines if the field gets added to the {@link AppAssistant} automatically. - */ - private boolean autoGenerateField = true; - /** * Determines if the property should get visibly saved in the AppManager * configuration. @@ -150,6 +191,17 @@ public static interface FieldValuesConsumer { */ private Function translationBundleSupplier; + /** + * Determines if the property is allowed to be seen. + */ + private FieldValuesBiPredicate isAllowedToSee// + = FieldValuesBiPredicate.TRUE; + /** + * Determines if the property is allowed to be edited. + */ + private FieldValuesBiPredicate isAllowedToEdit// + = FieldValuesBiPredicate.TRUE; + /** * Creates a {@link AppDef} with the componentId as the default value. * @@ -161,7 +213,7 @@ public static interface FieldValuesConsumer { */ public static final AppDef componentId(String componentId) { + PARAMETER> AppDef componentId(String componentId) { return new AppDef() // .setDefaultValue(componentId); } @@ -177,7 +229,7 @@ PARAMETER extends Type.Parameter> AppDef componentId(S */ public static final AppDef of() { + PARAMETER> AppDef of() { return new AppDef(); } @@ -194,11 +246,11 @@ PARAMETER extends Type.Parameter> AppDef of() { */ public static final , // PROPERTY extends Type & Nameable, // - PARAMETER extends Type.Parameter.BundleParameter> AppDef of(// + PARAMETER extends BundleProvider> AppDef of(// final Class clazz // ) { return new AppDef() // - .setTranslationBundleSupplier(BundleParameter::getBundle); + .setTranslationBundleSupplier(BundleProvider::bundle); } /** @@ -230,22 +282,44 @@ PARAMETER extends Type.Parameter> AppDef genericOf(// */ public static final & OpenemsApp, // - PROPERTY extends Enum & Nameable & Type, // + PROPERTY extends Nameable & Type, // PARAMETER extends Type.Parameter.BundleParameter> // AppDef copyOf(// final Class propertyClass, // final AppDef otherDef // ) { - final var def = new AppDef(); - def.translationBundleSupplier = otherDef.translationBundleSupplier; - def.label = otherDef.label; - def.description = otherDef.description; - def.defaultValue = otherDef.defaultValue; - def.field = otherDef.field; - def.autoGenerateField = otherDef.autoGenerateField; - def.isAllowedToSave = otherDef.isAllowedToSave; - def.bidirectionalValue = otherDef.bidirectionalValue; - return def; + return copyOfGeneric(otherDef); + } + + /** + * Creates a copy of the otherDef. This method is often used instead of the + * {@link AppDef#copyOfGeneric(AppDef)} because the return type doesn't have to + * be set explicit if a method call is appended to the result of the method. + * + * @param the type of the app + * @param the type of the property + * @param the type of the parameter + * @param the type of the app from the otherDef + * @param the type of the property from the otherDef + * @param the type of the parameter from the otherDef + * @param otherDef the other {@link AppDef} + * @param consumer the {@link Consumer} to set attributes of the + * {@link AppDef} + * @return the new {@link AppDef} + */ + public static final AppDef copyOfGeneric(// + final AppDef otherDef, // + Consumer> consumer // + ) { + var a = AppDef.copyOfGeneric(otherDef); + consumer.accept(a); + return a; } /** @@ -266,7 +340,7 @@ AppDef copyOf(// PARAMETER extends PARAMETERO, // APPO extends OpenemsApp, // PROPERTYO extends Nameable, // - PARAMETERO extends Type.Parameter> AppDef copyOfGeneric(// + PARAMETERO> AppDef copyOfGeneric(// final AppDef otherDef // ) { final var def = new AppDef(); @@ -275,9 +349,10 @@ PARAMETERO extends Type.Parameter> AppDef copyOfGeneri def.description = otherDef.description; def.defaultValue = otherDef.defaultValue; def.field = otherDef.field; - def.autoGenerateField = otherDef.autoGenerateField; def.isAllowedToSave = otherDef.isAllowedToSave; def.bidirectionalValue = otherDef.bidirectionalValue; + def.isAllowedToSee = otherDef.isAllowedToSee; + def.isAllowedToEdit = otherDef.isAllowedToEdit; return def; } @@ -461,6 +536,19 @@ public final AppDef setDefaultValue(// return this.setDefaultValue(JsonPrimitive::new, (app, prop, l, param) -> s); } + /** + * Wraps the name of the {@link Enum} in a {@link JsonPrimitive} and sets it as + * the default value with {@link AppDef#setDefaultValue(Function)}. + * + * @param e the {@link Enum} as the default value + * @return this + */ + public final AppDef setDefaultValue(// + final Enum e // + ) { + return this.setDefaultValue(e.name()); + } + /** * Wraps the {@link Boolean} in a {@link JsonPrimitive} and sets it as the * default value with {@link AppDef#setDefaultValue(Function)}. @@ -614,12 +702,80 @@ public final > AppDef setFi } public AppDef setAutoGenerateField(boolean autoGenerateField) { - this.autoGenerateField = autoGenerateField; - return this; + this.isAllowedToSee = autoGenerateField ? FieldValuesBiPredicate.TRUE : FieldValuesBiPredicate.FALSE; + return this.self(); + } + + public AppDef setIsAllowedToSee(// + final FieldValuesBiPredicate isAllowedToSee // + ) { + this.isAllowedToSee = isAllowedToSee; + return this.self(); + } + + /** + * Appends the given predicates and collections them into one which checks that + * every predicate returns true to determine if the current field should be + * show. + * + * @param isAllowedToSeePredicate the {@link FieldValuesBiPredicate} + * @return this + */ + @SafeVarargs + public final AppDef appendIsAllowedToSee(// + final FieldValuesBiPredicate... isAllowedToSeePredicate // + ) { + this.isAllowedToSee = appendToExisting(this.isAllowedToSee, isAllowedToSeePredicate); + return this.self(); + } + + /** + * Appends the given predicates and collections them into one which checks that + * every predicate returns true to determine if the current field can be edited. + * + * @param isAllowedToEditPredicates the {@link FieldValuesBiPredicate} + * @return this + */ + @SafeVarargs + public final AppDef appendIsAllowedToEdit(// + final FieldValuesBiPredicate... isAllowedToEditPredicates // + ) { + this.isAllowedToEdit = appendToExisting(this.isAllowedToEdit, isAllowedToEditPredicates); + return this.self(); + } + + @SafeVarargs + private static FieldValuesBiPredicate appendToExisting(// + final FieldValuesBiPredicate existing, // + final FieldValuesBiPredicate... appendPredicates // + ) { + if (appendPredicates.length == 0) { + return existing; + } + if (existing == FieldValuesBiPredicate.FALSE // + || Arrays.stream(appendPredicates).anyMatch(pred -> pred == FieldValuesBiPredicate.FALSE)) { + return FieldValuesBiPredicate.FALSE; + } + FieldValuesBiPredicate singlePredicate = (app, + property, l, parameter, user) -> { + return Arrays.stream(appendPredicates) // + .allMatch(pred -> pred.test(app, property, l, parameter, user)); + }; + if (existing == null || existing == FieldValuesBiPredicate.TRUE) { + return singlePredicate; + } + return (app, property, l, parameter, user) -> { + return existing.test(app, property, l, parameter, user) + && singlePredicate.test(app, property, l, parameter, user); + }; } - public boolean isAutoGenerateField() { - return this.autoGenerateField; + public FieldValuesBiPredicate getIsAllowedToEdit() { + return this.isAllowedToEdit; + } + + public FieldValuesBiPredicate getIsAllowedToSee() { + return this.isAllowedToSee; } /** @@ -769,6 +925,17 @@ public AppDef self() { return this; } + /** + * Executes the given {@link Consumer} with the current instance. + * + * @param consumer the {@link Consumer} to be executed with this instance + * @return this + */ + public AppDef also(Consumer> consumer) { + consumer.accept(this.self()); + return this.self(); + } + /** * Binds a property bidirectional. * @@ -790,6 +957,34 @@ public AppDef bidirectional(// final PROPERTY propOfComponentId, // final String property, // final Function componentManagerFunction // + ) { + return this.bidirectional(propOfComponentId, property, componentManagerFunction, Function.identity()); + } + + /** + * Binds a property bidirectional. + * + *

    + * The property itself will not be stored in the app configuration only in the + * component. If the user doesn't provide the value of a property and there is a + * bidirectional binding for it it will be filled up with the value of the + * bidirectional binding. If there is no component id in the configuration or + * the component doesn't exist or the property of the value is null then null is + * returned inside the bidirectional function. + * + * @param propOfComponentId the key to get the component id from a + * configuration + * @param property the property of the component + * @param componentManagerFunction the function to get the component manager + * @param mapper the mapper {@link Function} from the + * component to the actual value + * @return this + */ + public AppDef bidirectional(// + final PROPERTY propOfComponentId, // + final String property, // + final Function componentManagerFunction, // + final Function mapper // ) { this.bidirectionalValue = (app, prop, l, param, properties) -> { if (properties == null) { @@ -804,11 +999,42 @@ public AppDef bidirectional(// .getComponent(componentId.getAsString()); return optionalComponent.map(component -> { return component.getProperty(property).orElse(null); - }).orElse(null); + }).map(mapper).orElseGet(() -> this.getDefaultValue().get(app, prop, l, param)); }; // set allowedToSave automatically to false this.isAllowedToSave = false; return this.self(); } + /** + * Creates a simple mapper if the input {@link JsonElement} is a number it gets + * multiplied with the given multiplicator. + * + * @param multiplicator the multiplicator to be applied to the + * {@link JsonElement} + * @return the converter {@link Function} + */ + public static Function multiplyWith(double multiplicator) { + return element -> { + if (!element.isJsonPrimitive()) { + return element; + } + if (!element.getAsJsonPrimitive().isNumber()) { + return element; + } + return new JsonPrimitive(element.getAsDouble() * multiplicator); + }; + } + + /** + * Creates a {@link FieldValuesBiPredicate} to check if the {@link Role} of the + * user is at least the given {@link Role}. + * + * @param role the compared Role + * @return the {@link FieldValuesBiPredicate} + */ + public static FieldValuesBiPredicate ofLeastRole(Role role) { + return (app, property, l, parameter, user) -> user.getRole().isAtLeast(role); + } + } \ No newline at end of file diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppInstallWorker.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppInstallWorker.java index 14337f33f4a..ed877bfe1cc 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppInstallWorker.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppInstallWorker.java @@ -68,7 +68,7 @@ private void installFreeApps() { } private void installApp(String appId) throws OpenemsNamedException { - var app = this.parent.findAppById(appId); + final var app = this.parent.findAppByIdOrError(appId); JsonObject requestProperties; try { diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java index 5a66ff9da45..e1564abd57c 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java @@ -8,11 +8,13 @@ import java.util.LinkedList; import java.util.List; import java.util.Map.Entry; -import java.util.NoSuchElementException; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.function.BiFunction; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -39,6 +41,7 @@ import io.openems.common.exceptions.OpenemsError; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.function.ThrowingConsumer; import io.openems.common.function.ThrowingFunction; import io.openems.common.function.ThrowingSupplier; import io.openems.common.jsonrpc.base.GenericJsonrpcResponseSuccess; @@ -48,7 +51,6 @@ import io.openems.common.jsonrpc.request.UpdateComponentConfigRequest.Property; import io.openems.common.session.Role; import io.openems.common.utils.JsonUtils; -import io.openems.common.utils.Mutex; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.ComponentManager; import io.openems.edge.common.component.OpenemsComponent; @@ -111,10 +113,12 @@ public class AppManagerImpl extends AbstractOpenemsComponent * Blocks until all changes to an app have been applied to the list of * instantiatedApps and the appManagerConfiguration. */ - private boolean aquiredMutex = false; - private final Mutex mutexForUpdatingConfig = new Mutex(true); private UpdateValues lastUpdate = null; + protected final Lock lockModifyingApps = new ReentrantLock(); + protected final Condition waitingForModifiedCondition = this.lockModifyingApps.newCondition(); + protected volatile boolean waitingForModified = false; + public AppManagerImpl() { super(// OpenemsComponent.ChannelId.values(), // @@ -238,63 +242,63 @@ private static List parseInstantiatedApps(JsonArray apps) th return result; } - private synchronized void applyConfig(Config config) { + private void applyConfig(Config config) { boolean isResultOfRpcRequest = false; - synchronized (this.instantiatedApps) { - try { - var apps = config.apps(); - if (apps.isBlank()) { - apps = "[]"; // default to empty array - } - try { - var instApps = parseInstantiatedApps(JsonUtils.parseToJsonArray(apps)); - - // always replace old apps with the new ones - var currentApps = new ArrayList<>(this.instantiatedApps); - - // if equal the applyConfig is because of a - // installation/modification/removing of an app via JsonrpcRequest - if (currentApps.containsAll(instApps)// - && instApps.size() == currentApps.size()) { - isResultOfRpcRequest = true; - } else if (this.lastUpdate != null // - && (!instApps.containsAll(this.lastUpdate.modifiedOrCreatedApps) // - || instApps.stream().anyMatch(t -> this.lastUpdate.deletedApps.stream() // - .anyMatch(o -> o.equals(t))))) { - // the last update was not applied - this.logWarn(this.log, "Modified AppManager config properties directly. " // - + "If there was an installation/modification/deinstallation of an App " // - + "running there might be lost configuration changes. Expected: " // - + "Installed/Modified: " // - + JsonUtils.prettyToString(this.lastUpdate.modifiedOrCreatedApps.stream() // - .map(OpenemsAppInstance::toJsonObject) // - .collect(JsonUtils.toJsonArray())) - + Optional.ofNullable(this.lastUpdate.deletedApps) // - .map(deletedApps -> { - return System.lineSeparator() + "Removed: " // - + JsonUtils.prettyToString(this.lastUpdate.deletedApps.stream() // - .map(OpenemsAppInstance::toJsonObject) // - .collect(JsonUtils.toJsonArray())); - }).orElse("")); - } - this.instantiatedApps.clear(); - this.instantiatedApps.addAll(instApps); + this.lockModifyingApps.lock(); - this._setWrongAppConfiguration(false); + try { + var apps = config.apps(); + if (apps == null || apps.isBlank()) { + apps = "[]"; // default to empty array + } + var instApps = parseInstantiatedApps(JsonUtils.parseToJsonArray(apps)); + + // always replace old apps with the new ones + var currentApps = new ArrayList<>(this.instantiatedApps); + + // if equal the applyConfig is because of a + // installation/modification/removing of an app via JsonrpcRequest + if (currentApps.containsAll(instApps)// + && instApps.size() == currentApps.size()) { + isResultOfRpcRequest = true; + } else if (this.lastUpdate != null // + && (!instApps.containsAll(this.lastUpdate.modifiedOrCreatedApps) // + || instApps.stream().anyMatch(t -> this.lastUpdate.deletedApps.stream() // + .anyMatch(o -> o.equals(t))))) { + // the last update was not applied + this.logWarn(this.log, "Modified AppManager config properties directly. " // + + "If there was an installation/modification/deinstallation of an App " // + + "running there might be lost configuration changes. Expected: " // + + "Installed/Modified: " // + + JsonUtils.prettyToString(this.lastUpdate.modifiedOrCreatedApps.stream() // + .map(OpenemsAppInstance::toJsonObject) // + .collect(JsonUtils.toJsonArray())) + + Optional.ofNullable(this.lastUpdate.deletedApps) // + .map(deletedApps -> { + return System.lineSeparator() + "Removed: " // + + JsonUtils.prettyToString(this.lastUpdate.deletedApps.stream() // + .map(OpenemsAppInstance::toJsonObject) // + .collect(JsonUtils.toJsonArray())); + }).orElse("")); + } - } catch (OpenemsNamedException e) { - this._setWrongAppConfiguration(true); - e.printStackTrace(); - return; - } - } finally { - if (isResultOfRpcRequest && this.aquiredMutex) { - this.lastUpdate = null; - this.aquiredMutex = false; - this.mutexForUpdatingConfig.release(); - } + this.instantiatedApps.clear(); + this.instantiatedApps.addAll(instApps); + + this._setWrongAppConfiguration(false); + + } catch (OpenemsNamedException e) { + this._setWrongAppConfiguration(true); + e.printStackTrace(); + } finally { + if (isResultOfRpcRequest) { + this.lastUpdate = null; } + + this.waitingForModified = false; + this.waitingForModifiedCondition.signal(); + this.lockModifyingApps.unlock(); } } @@ -394,18 +398,18 @@ public boolean hasNext() { } try { - var app = AppManagerImpl.this.findAppById(this.nextInstance.appId); - this.nextInstance.properties.addProperty("ALIAS", this.nextInstance.alias); - this.nextConfiguration = app.getAppConfiguration(ConfigurationTarget.VALIDATE, - this.nextInstance.properties, null); + var app = AppManagerImpl.this.findAppById(this.nextInstance.appId).orElse(null); + if (app == null) { + // app not found for instance + // this may happen if the app id gets refactored + // apps which app ids are not known are printed in debug log as 'UNKNOWNAPPS' + continue; + } + final var props = this.nextInstance.properties.deepCopy(); + props.addProperty("ALIAS", this.nextInstance.alias); + this.nextConfiguration = app.getAppConfiguration(ConfigurationTarget.VALIDATE, props, null); } catch (OpenemsNamedException e) { // move to next app - } catch (NoSuchElementException e) { - // app not found for instance - // this may happen if the app id gets refactored - // apps which app ids are not known are printed in debug log as 'UNKNOWNAPPS' - } finally { - this.nextInstance.properties.remove("ALIAS"); } } @@ -416,34 +420,59 @@ public boolean hasNext() { @Override public String debugLog() { - return this.worker.debugLog(); + final var workerLog = this.worker.debugLog(); + if (workerLog == null && !this.waitingForModified) { + return null; + } + return "AppValidateWorker=" + workerLog // + + ", waitingForModified=" + this.waitingForModified; } /** - * finds the app with the matching id. - * - * @param id of the app - * @return the found app + * Finds the {@link OpenemsApp} with the given id. + * + * @param id the {@link OpenemsApp#getAppId()} of the app. + * @return a {@link Optional} of the app */ - protected final OpenemsApp findAppById(String id) throws NoSuchElementException { + public final Optional findAppById(String id) { return this.availableApps.stream() // .filter(t -> t.getAppId().equals(id)) // - .findFirst() // - .get(); + .findFirst(); + } + + /** + * Finds the {@link OpenemsApp} with the given id. + * + * @param id the {@link OpenemsApp#getAppId()} of the app. + * @return the app + * @throws OpenemsNamedException if the app was not found + */ + public final OpenemsApp findAppByIdOrError(String id) throws OpenemsNamedException { + return this.findAppById(id).orElseThrow(() -> new OpenemsException("Unable to find app with id '" + id + "'")); } /** - * Finds the app instance with the matching id. + * Finds the {@link OpenemsAppInstance} with the given {@link UUID}. * - * @param uuid the id of the instance - * @return s the instance - * @throws NoSuchElementException if no instance is present + * @param id the id of the instance + * @return a {@link Optional} of the instance */ - protected final OpenemsAppInstance findInstanceById(UUID uuid) throws NoSuchElementException { + protected final Optional findInstanceById(UUID id) { return this.instantiatedApps.stream() // - .filter(t -> t.instanceId.equals(uuid)) // - .findFirst() // - .get(); + .filter(t -> t.instanceId.equals(id)) // + .findFirst(); + } + + /** + * Finds the {@link OpenemsAppInstance} with the given {@link UUID}. + * + * @param id the {@link UUID} of the instance + * @return the instance + * @throws OpenemsNamedException if not found + */ + protected final OpenemsAppInstance findInstanceByIdOrError(UUID id) throws OpenemsNamedException { + return this.findInstanceById(id) + .orElseThrow(() -> new OpenemsException("Unable to find instance with id '" + id + "'")); } /** @@ -467,7 +496,7 @@ private final OpenemsAppInstance createInstanceWithFilledProperties(// ) { var properties = instance.properties; if (openemsApp instanceof AbstractOpenemsAppWithProps) { - properties = ((AbstractOpenemsAppWithProps) openemsApp).fillUpProperties(properties); + properties = AbstractOpenemsApp.fillUpProperties(openemsApp, properties); } return new OpenemsAppInstance(// instance.appId, instance.alias, instance.instanceId, // @@ -491,8 +520,9 @@ public CompletableFuture handleAddAppInstanceRequest(Us throw new OpenemsException("Key not applicable!"); } - var openemsApp = this.findAppById(request.appId); - final var response = this.lockModifyingApps(() -> { + final var openemsApp = this.findAppByIdOrError(request.appId); + + return this.lockModifyingApps(() -> { List warnings = new ArrayList<>(); var instance = new OpenemsAppInstance(openemsApp.getAppId(), request.alias, UUID.randomUUID(), request.properties, null); @@ -528,18 +558,22 @@ public CompletableFuture handleAddAppInstanceRequest(Us this.instantiatedApps.add(instance); } var instanceWithFilledProperties = this.createInstanceWithFilledProperties(openemsApp, instance); - return CompletableFuture.completedFuture(// - new AddAppInstance.Response(request.id, instanceWithFilledProperties, warnings)); + return new Pair<>(true, CompletableFuture.completedFuture(// + new AddAppInstance.Response(request.id, instanceWithFilledProperties, warnings))); + }, (shouldUpdate) -> { + if (shouldUpdate == null || !shouldUpdate) { + return; + } + try { + // Update App-Manager configuration + this.updateAppManagerConfiguration(user, this.instantiatedApps); + } catch (OpenemsNamedException e) { + this.appSynchronizeWorker.setValidBackendResponse(false); + this.appSynchronizeWorker.triggerNextRun(); + throw new OpenemsException( + "AddAppInstance: unable to update App-Manager configuration: " + e.getMessage()); + } }); - try { - // Update App-Manager configuration - this.updateAppManagerConfiguration(user, this.instantiatedApps); - } catch (OpenemsNamedException e) { - this.appSynchronizeWorker.setValidBackendResponse(false); - this.appSynchronizeWorker.triggerNextRun(); - throw new OpenemsException("AddAppInstance: unable to update App-Manager configuration: " + e.getMessage()); - } - return response; } /** @@ -565,12 +599,10 @@ public CompletableFuture handleAddAppInstanceRequest(Us */ public CompletableFuture handleDeleteAppInstanceRequest(User user, DeleteAppInstance.Request request) throws OpenemsNamedException { - final var updatedResultPair = this.lockModifyingApps(() -> { - final OpenemsAppInstance instance; - try { - instance = this.findInstanceById(request.instanceId); - } catch (NoSuchElementException e) { - return null; + final var updatedResultPair = this.>lockModifyingApps(() -> { + final var instance = this.findInstanceById(request.instanceId).orElse(null); + if (instance == null) { + return new Pair<>(false, null); } final var result = this.lastUpdate = this.useAppManagerAppHelper(appHelper -> { @@ -580,19 +612,25 @@ public CompletableFuture handleDeleteAppInstan // replace modified apps this.instantiatedApps.removeAll(result.modifiedOrCreatedApps); this.instantiatedApps.addAll(result.modifiedOrCreatedApps); - return new Pair<>(result, instance); + return new Pair<>(true, new Pair<>(result, instance)); + }, (shouldUpdate) -> { + if (shouldUpdate == null || !shouldUpdate) { + return; + } + try { + this.updateAppManagerConfiguration(user, this.instantiatedApps); + } catch (OpenemsNamedException e) { + throw new OpenemsException("Unable to update App-Manager configuration for ID [" + request.instanceId + + "]: " + e.getMessage()); + } }); + if (updatedResultPair == null) { + return CompletableFuture.completedFuture(new GenericJsonrpcResponseSuccess(request.id)); + } final var updatedResult = updatedResultPair.first; final var removedInstance = updatedResultPair.second; - try { - this.updateAppManagerConfiguration(user, this.instantiatedApps); - } catch (OpenemsNamedException e) { - throw new OpenemsException("Unable to update App-Manager configuration for ID [" + request.instanceId - + "]: " + e.getMessage()); - } - var backendDeinstallFuture = this.backendUtil.addDeinstallAppInstanceHistory(user, removedInstance.appId, removedInstance.instanceId); backendDeinstallFuture.whenComplete((r, t) -> { @@ -619,12 +657,14 @@ public CompletableFuture handleDeleteAppInstan * @return the Future JSON-RPC Response * @throws OpenemsNamedException on error */ - private CompletableFuture handleGetAppAssistantRequest(User user, - GetAppAssistant.Request request) throws OpenemsNamedException { + private CompletableFuture handleGetAppAssistantRequest(// + final User user, // + final GetAppAssistant.Request request // + ) throws OpenemsNamedException { for (var app : this.availableApps) { if (request.appId.equals(app.getAppId())) { - return CompletableFuture.completedFuture( - new GetAppAssistant.Response(request.id, app.getAppAssistant(user.getLanguage()))); + return CompletableFuture + .completedFuture(new GetAppAssistant.Response(request.id, app.getAppAssistant(user))); } } throw new OpenemsException("App-ID [" + request.appId + "] is unknown"); @@ -640,12 +680,8 @@ private CompletableFuture handleGetAppAssistantRequest(U */ private CompletableFuture handleGetAppDescriptorRequest(User user, GetAppDescriptor.Request request) throws OpenemsNamedException { - try { - var app = this.findAppById(request.appId); - return CompletableFuture.completedFuture(new GetAppDescriptor.Response(request.id, app.getAppDescriptor())); - } catch (NoSuchElementException e) { - throw new OpenemsException("App-ID [" + request.appId + "] is unknown"); - } + final var app = this.findAppByIdOrError(request.appId); + return CompletableFuture.completedFuture(new GetAppDescriptor.Response(request.id, app.getAppDescriptor())); } /** @@ -661,10 +697,10 @@ private CompletableFuture handleGetAppInstancesRequest(U var instances = this.instantiatedApps.stream() // .filter(i -> i.appId.equals(request.appId)) // .map(t -> { - final var app = this.findAppById(t.appId); + final var app = this.findAppById(t.appId).orElse(null); var properties = t.properties; - if (app instanceof AbstractOpenemsAppWithProps) { - properties = ((AbstractOpenemsAppWithProps) app).fillUpProperties(properties); + if (app != null && app instanceof AbstractOpenemsAppWithProps) { + properties = AbstractOpenemsApp.fillUpProperties(app, properties); } return new OpenemsAppInstance(t.appId, t.alias, t.instanceId, properties, t.dependencies); }); @@ -748,16 +784,10 @@ public CompletableFuture handleJsonrpcRequest( */ public CompletableFuture handleUpdateAppInstanceRequest(User user, UpdateAppInstance.Request request) throws OpenemsNamedException { - final var response = this.lockModifyingApps(() -> { - final OpenemsAppInstance oldApp; - final OpenemsApp app; + return this.lockModifyingApps(() -> { + final var oldApp = this.findInstanceByIdOrError(request.instanceId); + final var app = this.findAppByIdOrError(oldApp.appId); - try { - oldApp = this.findInstanceById(request.instanceId); - app = this.findAppById(oldApp.appId); - } catch (NoSuchElementException e) { - throw new OpenemsException("App-Instance-ID [" + request.instanceId + "] is unknown."); - } final var updatedInstance = new OpenemsAppInstance(oldApp.appId, request.alias, oldApp.instanceId, request.properties, oldApp.dependencies); @@ -769,17 +799,20 @@ public CompletableFuture handleUpdateAppInstanceRequ // replace old instances with new ones this.instantiatedApps.removeAll(result.modifiedOrCreatedApps); this.instantiatedApps.addAll(result.modifiedOrCreatedApps); - return CompletableFuture.completedFuture(// + return new Pair<>(true, CompletableFuture.completedFuture(// new UpdateAppInstance.Response(request.id, // - this.createInstanceWithFilledProperties(app, result.rootInstance), result.warnings)); + this.createInstanceWithFilledProperties(app, result.rootInstance), result.warnings))); + }, (shouldUpdate) -> { + if (shouldUpdate == null || !shouldUpdate) { + return; + } + try { + this.updateAppManagerConfiguration(user, this.instantiatedApps); + } catch (OpenemsNamedException e) { + throw new OpenemsException("Unable to update App-Manager configuration for ID [" + request.instanceId + + "]: " + e.getMessage()); + } }); - try { - this.updateAppManagerConfiguration(user, this.instantiatedApps); - } catch (OpenemsNamedException e) { - throw new OpenemsException("Unable to update App-Manager configuration for ID [" + request.instanceId - + "]: " + e.getMessage()); - } - return response; } /** @@ -790,6 +823,7 @@ public CompletableFuture handleUpdateAppInstanceRequ * @throws OpenemsNamedException when the configuration can not be updated */ private void updateAppManagerConfiguration(User user, List apps) throws OpenemsNamedException { + this.waitingForModified = true; AppManagerImpl.sortApps(apps); var p = new Property("apps", getJsonAppsString(apps)); var updateRequest = new UpdateComponentConfigRequest(SINGLETON_COMPONENT_ID, Arrays.asList(p)); @@ -832,16 +866,43 @@ private T useAppManagerAppHelper(ThrowingFunction T lockModifyingApps(ThrowingSupplier supplier) throws OpenemsNamedException { + private T lockModifyingApps(// + ThrowingSupplier, OpenemsNamedException> supplier, // + ThrowingConsumer runFinally // + ) throws OpenemsNamedException { + // try to get lock within 5 minutes otherwise just log a warning try { - this.mutexForUpdatingConfig.await(); - this.aquiredMutex = true; + if (!this.lockModifyingApps.tryLock(5, TimeUnit.MINUTES)) { + this.log.warn("Wait time for 'lockModifyingApps' elapsed."); + } + } catch (InterruptedException e1) { + this.log.error(e1.getMessage(), e1); + throw new OpenemsException(e1); + } + + F firstResult = null; + try { + if (this.waitingForModified) { + // wait if another request is waiting for the modification event to happen + // or continue after 5 minutes of waiting + if (!this.waitingForModifiedCondition.await(5, TimeUnit.MINUTES)) { + this.log.warn("Wait time for 'instantiatedAppsCondition' elapsed."); + } + // continue with executing the supplier + } + synchronized (this.appSynchronizeWorker.getSynchronizationLock()) { + final var resultPair = supplier.get(); + firstResult = resultPair.first; + return resultPair.second; + } } catch (InterruptedException e) { + this.log.error(e.getMessage(), e); throw new OpenemsException(e); - } - synchronized (this.appSynchronizeWorker.getSynchronizationLock()) { - synchronized (this.instantiatedApps) { - return supplier.get(); + } finally { + try { + runFinally.accept(firstResult); + } finally { + this.lockModifyingApps.unlock(); } } } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerUtil.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerUtil.java index 6e52202fb9d..b10f478cba6 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerUtil.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerUtil.java @@ -1,7 +1,8 @@ package io.openems.edge.core.appmanager; import java.util.List; -import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Optional; import java.util.UUID; import com.google.gson.JsonObject; @@ -13,24 +14,65 @@ public interface AppManagerUtil { /** - * Gets the {@link OpenemsApp} for the given appId. - * - * @param appId the appId of the {@link OpenemsApp} - * @return the {@link OpenemsApp} - * @throws NoSuchElementException if there is no {@link OpenemsApp} with the - * given appId + * Gets a {@link List} of the current installed {@link OpenemsAppInstance}. + * + * @return the list of installed apps + */ + public List getInstantiatedApps(); + + /** + * Gets a {@link List} of the current installed {@link OpenemsAppInstance} which + * match the given appId. + * + * @param appId the appId which should match with + * {@link OpenemsAppInstance#appId} + * @return a {@link List} of {@link OpenemsAppInstance} + */ + public default List getInstantiatedAppsOfApp(String appId) { + Objects.requireNonNull(appId); + return this.getInstantiatedApps().stream() // + .filter(instance -> appId.equals(instance.appId)) // + .toList(); + } + + /** + * Finds the {@link OpenemsApp} with the given id. + * + * @param id the {@link OpenemsApp#getAppId()} of the app. + * @return a {@link Optional} of the app */ - public OpenemsApp getAppById(String appId) throws NoSuchElementException; + public Optional findAppById(String id); /** - * Gets the {@link OpenemsAppInstance} for the given instanceId. + * Finds the {@link OpenemsApp} with the given id. + * + * @param id the {@link OpenemsApp#getAppId()} of the app. + * @return the app + * @throws OpenemsNamedException if the app was not found + */ + public default OpenemsApp findAppByIdOrError(String id) throws OpenemsNamedException { + return this.findAppById(id).orElseThrow(() -> new OpenemsException("Unable to find app with id '" + id + "'")); + } + + /** + * Finds the {@link OpenemsAppInstance} with the given {@link UUID}. * - * @param instanceId the instanceId of the {@link OpenemsAppInstance} - * @return the {@link OpenemsAppInstance} - * @throws NoSuchElementException if there is not {@link OpenemsAppInstance} - * with the given instanceId + * @param id the id of the instance + * @return a {@link Optional} of the instance */ - public OpenemsAppInstance getInstanceById(UUID instanceId) throws NoSuchElementException; + public Optional findInstanceById(UUID id); + + /** + * Finds the {@link OpenemsAppInstance} with the given {@link UUID}. + * + * @param id the {@link UUID} of the instance + * @return the instance + * @throws OpenemsNamedException if not found + */ + public default OpenemsAppInstance findInstanceByIdOrError(UUID id) throws OpenemsNamedException { + return this.findInstanceById(id) + .orElseThrow(() -> new OpenemsException("Unable to find instance with id '" + id + "'")); + } /** * Gets the {@link AppConfiguration} with the given parameter. @@ -59,7 +101,7 @@ public AppConfiguration getAppConfiguration(ConfigurationTarget target, OpenemsA */ public default AppConfiguration getAppConfiguration(ConfigurationTarget target, String appId, String alias, JsonObject properties, Language language) throws OpenemsNamedException { - return this.getAppConfiguration(target, this.getAppById(appId), alias, properties, language); + return this.getAppConfiguration(target, this.findAppByIdOrError(appId), alias, properties, language); } /** @@ -88,7 +130,7 @@ public default AppConfiguration getAppConfiguration(ConfigurationTarget target, */ public default AppConfiguration getAppConfiguration(ConfigurationTarget target, OpenemsAppInstance instance, Language language) throws OpenemsNamedException { - return this.getAppConfiguration(target, this.getAppById(instance.appId), instance, language); + return this.getAppConfiguration(target, this.findAppByIdOrError(instance.appId), instance, language); } /** diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerUtilImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerUtilImpl.java index c59f95917ec..bb001e09b66 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerUtilImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerUtilImpl.java @@ -1,7 +1,7 @@ package io.openems.edge.core.appmanager; import java.util.List; -import java.util.NoSuchElementException; +import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; @@ -26,13 +26,18 @@ public AppManagerUtilImpl(@Reference ComponentManager componentManager) { } @Override - public OpenemsApp getAppById(String appId) throws NoSuchElementException { - return this.getAppManagerImpl().findAppById(appId); + public List getInstantiatedApps() { + return this.getAppManagerImpl().getInstantiatedApps(); } @Override - public OpenemsAppInstance getInstanceById(UUID instanceId) throws NoSuchElementException { - return this.getAppManagerImpl().findInstanceById(instanceId); + public Optional findAppById(String id) { + return this.getAppManagerImpl().findAppById(id); + } + + @Override + public Optional findInstanceById(UUID id) { + return this.getAppManagerImpl().findInstanceById(id); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerUtilSupplier.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerUtilSupplier.java new file mode 100644 index 00000000000..c394d899806 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerUtilSupplier.java @@ -0,0 +1,12 @@ +package io.openems.edge.core.appmanager; + +public interface AppManagerUtilSupplier { + + /** + * Gets a {@link AppManagerUtil}. + * + * @return the {@link AppManagerUtil} + */ + AppManagerUtil getAppManagerUtil(); + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/ComponentManagerSupplier.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/ComponentManagerSupplier.java new file mode 100644 index 00000000000..fc3ca3aad47 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/ComponentManagerSupplier.java @@ -0,0 +1,14 @@ +package io.openems.edge.core.appmanager; + +import io.openems.edge.common.component.ComponentManager; + +public interface ComponentManagerSupplier { + + /** + * Gets a {@link ComponentManager}. + * + * @return the {@link ComponentManager} + */ + ComponentManager getComponentManager(); + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/ComponentUtil.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/ComponentUtil.java index ad82b7a87b7..40207301508 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/ComponentUtil.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/ComponentUtil.java @@ -6,13 +6,73 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.types.EdgeConfig; import io.openems.common.types.EdgeConfig.Component; +import io.openems.edge.common.component.ComponentManager; import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.cycle.Cycle; +import io.openems.edge.common.host.Host; +import io.openems.edge.common.meta.Meta; +import io.openems.edge.common.sum.Sum; import io.openems.edge.common.user.User; -import io.openems.edge.core.appmanager.ComponentUtilImpl.Relay; import io.openems.edge.core.host.NetworkInterface; +import io.openems.edge.io.api.DigitalOutput; +import io.openems.edge.predictor.api.manager.PredictorManager; public interface ComponentUtil { + public static final List CORE_COMPONENT_IDS = List.of(// + AppManager.SINGLETON_COMPONENT_ID, // + ComponentManager.SINGLETON_COMPONENT_ID, // + Cycle.SINGLETON_COMPONENT_ID, // + Host.SINGLETON_COMPONENT_ID, // + Meta.SINGLETON_COMPONENT_ID, // + PredictorManager.SINGLETON_COMPONENT_ID, // + Sum.SINGLETON_COMPONENT_ID // + ); + + public record RelayInfo(// + String id, // + String alias, // + List channels // + ) { + + /** + * Returns the alias if not blank otherwise return the id. + * + * @return the string to display + */ + public String getDisplayName() { + return this.alias().isBlank() ? this.id() : this.alias(); + } + + } + + public record RelayContactInfo(// + String channel, // + String alias, // + List usingComponents // + ) { + + /** + * Returns the alias if not blank otherwise return the id. + * + * @return the string to display + */ + public String getDisplayName() { + return this.alias().isBlank() ? this.channel() : this.alias(); + } + + } + + public record PreferredRelay(// + int numberOfRelays, // + /** + * Indices of the relay contacts. + */ + int[] preferredRelays // + ) { + + } + /** * Gets the interfaces of the currently active network settings. * @@ -31,52 +91,97 @@ public interface ComponentUtil { public boolean anyComponentUses(String value, List ignoreIds); /** - * Gets a list of current Relays. e. g. 'io0/Relay1' - * - * @return a list of Relays - * @throws OpenemsNamedException on error + * Gets all {@link RelayInfo RelayInfos} of all {@link DigitalOutput + * DigitalOutputs}. + * + * @param ignoreIds the Component-IDs that should be ignored to check if they + * use any relay + * @return a list of {@link RelayInfo RelayInfos} + */ + public List getAllRelayInfos(List ignoreIds); + + /** + * Gets all {@link RelayInfo RelayInfos} of all {@link DigitalOutput + * DigitalOutputs}. + * + * @return a list of {@link RelayInfo RelayInfos} + * @implNote calls {@link ComponentUtil#getAllRelayInfos(List)} with + * {@link ComponentUtil#CORE_COMPONENT_IDS} to ignore. + */ + public default List getAllRelayInfos() { + return this.getAllRelayInfos(CORE_COMPONENT_IDS); + } + + /** + * Gets a list of the current relays where every relay has atleast one available + * relay contact. + * + * @param ignoreIds the id s of components which configuration should be ignored + * @return a list of the {@link RelayInfo RelayInfos} */ - public List getAllRelays(); + public default List getAvailableRelayInfos(List ignoreIds) { + return this.getAllRelayInfos(ignoreIds).stream() // + .map(ri -> new RelayInfo(ri.id(), ri.alias(), ri.channels().stream()// + .filter(ci -> ci.usingComponents().isEmpty())// + .toList())) // + .filter(ri -> !ri.channels().isEmpty()) // + .toList(); + } + + /** + * Gets a list of the current relays where every relay has atleast one available + * relay contact. + * + * @return a list of the {@link RelayInfo RelayInfos} + */ + public default List getAvailableRelayInfos() { + return this.getAvailableRelayInfos(CORE_COMPONENT_IDS); + } /** - * Gets a list of currently available Relays of IOs which are not used by any - * component. e. g. 'io0/Relay1' + * Gets a list of current available relay contacts of a relay. * - * @return a list of available Relays - * @throws OpenemsNamedException on error + * @param ignoreIds the id s of components which configuration should be ignored + * @param ioName the id of the id + * @return a list of the available {@link RelayContactInfo} */ - public List getAvailableRelays(); + public default List getAvailableRelayContactInfos(List ignoreIds, String ioName) { + return this.getAvailableRelayInfos(ignoreIds).stream() // + .filter(ri -> ri.id().equals(ioName)) // + .flatMap(ri -> ri.channels().stream()) // + .toList(); + } /** - * Gets a list of currently available Relays of IOs which are not used by any - * component. like 'io0/Relay1' + * Gets a list of current available relay contacts of a relay. * - * @param ignoreIds the Component-IDs that should be ignored - * @return a list of available Relays - * @throws OpenemsNamedException on error + * @param ioName the id of the id + * @return a list of the available {@link RelayContactInfo} */ - public List getAvailableRelays(List ignoreIds); + public default List getAvailableRelayContactInfos(String ioName) { + return this.getAvailableRelayContactInfos(CORE_COMPONENT_IDS, ioName); + } /** - * Gets a list of currently available Relays of given IO which are not used by - * any component. like 'io0/Relay1' + * Gets a list of current available relay contacts of all found relays. * - * @param ioId the Component-ID of the DigitalOutput - * @return a list of available Relays - * @throws OpenemsNamedException if the io was not found + * @param ignoreIds the id s of components which configuration should be ignored + * @return a list of the available {@link RelayContactInfo} */ - public List getAvailableRelays(String ioId) throws OpenemsNamedException; + public default List getAvailableRelayContactInfos(List ignoreIds) { + return this.getAvailableRelayInfos(ignoreIds).stream() // + .flatMap(ri -> ri.channels().stream()) // + .toList(); + } /** - * Gets a list of currently available Relays of given IO which are not used by - * any component. e. g. 'io0/Relay1' + * Gets a list of current available relay contacts of all found relays. * - * @param ioId the Component-ID of the DigitalOutput - * @param ignoreIds the Component-IDs that should be ignored - * @return a list of available Relays - * @throws OpenemsNamedException if the io was not found + * @return a list of the available {@link RelayContactInfo} */ - public List getAvailableRelays(String ioId, List ignoreIds) throws OpenemsNamedException; + public default List getAvailableRelayContactInfos() { + return this.getAvailableRelayContactInfos(CORE_COMPONENT_IDS); + } /** * Searches a component with the given component configuration. @@ -137,17 +242,46 @@ public default String getNextAvailableId(String baseName, List component */ public String getNextAvailableId(String baseName, int startingNumber, List componentIds); + /** + * Gets the preferred relays. If the default ports are already taken the next + * available in a row are taken. If not enough in a row are available the first + * available relays of any relay are returned. + * + * @param ignoreIds the ids of the components that should be ignored + * @param cnt the number of the result number of relays + * @param first the first {@link PreferredRelay} to ensure + * at least one + * @param inputPreferredRelays the other {@link PreferredRelay} options + * @return the first found preferred relays + */ + public String[] getPreferredRelays(// + List ignoreIds, // + int cnt, // + PreferredRelay first, // + PreferredRelay... inputPreferredRelays // + ); + /** * Gets the preferred relays. If the default ports are are already taken the * next available in a row are taken. If not enough in a row are available the - * first available relays of any relayboard are returned. - * - * @param ignoreIds the ids of the components that should be ignored - * @param relays4Channel the default ports on a 4-Channel Relay - * @param relays8Channel the default ports on a 8-Channel Relay - * @return the relays + * first available relays of any relay are returned. + * + * @param cnt the number of the result number of relays + * @param first the first {@link PreferredRelay} to ensure at + * least one + * @param inputPreferredRelays the other {@link PreferredRelay} options + * @return the first found preferred relays + * @implNote calls + * {@link ComponentUtil#getPreferredRelays(List, int, PreferredRelay, PreferredRelay...)} + * with {@link ComponentUtil#CORE_COMPONENT_IDS} to ignore. */ - public String[] getPreferredRelays(List ignoreIds, int[] relays4Channel, int[] relays8Channel); + public default String[] getPreferredRelays(// + int cnt, // + PreferredRelay first, // + PreferredRelay... inputPreferredRelays // + ) { + return this.getPreferredRelays(CORE_COMPONENT_IDS, cnt, first, inputPreferredRelays); + } /** * updates the interfaces in the Host configuration. @@ -235,4 +369,14 @@ public void updateHosts(User user, List ips, List getComponent(String id, String factoryId); + + /** + * Gets an array of modbus unit ids which are already used by other components. + * The result of this method may not contain all modbus unit ids. + * + * @param modbusComponent the id of the modbus component + * @return an array of used modbus unit ids + */ + public int[] getUsedModbusUnitIds(String modbusComponent); + } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/ComponentUtilImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/ComponentUtilImpl.java index b6aea8a3b3d..241b3adc6c1 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/ComponentUtilImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/ComponentUtilImpl.java @@ -4,16 +4,24 @@ import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.Dictionary; +import java.util.Enumeration; import java.util.HashSet; import java.util.Hashtable; -import java.util.LinkedList; import java.util.List; import java.util.Map.Entry; import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Consumer; +import java.util.function.Predicate; import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.annotations.Activate; @@ -30,6 +38,7 @@ import io.openems.common.types.EdgeConfig; import io.openems.common.types.EdgeConfig.Component; import io.openems.common.utils.JsonUtils; +import io.openems.edge.common.channel.BooleanWriteChannel; import io.openems.edge.common.component.ComponentManager; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.host.Host; @@ -42,19 +51,6 @@ @org.osgi.service.component.annotations.Component() public class ComponentUtilImpl implements ComponentUtil { - public static class Relay { - public final String id; - public final List relays; - public final int channels; - - public Relay(String id, List relays, int channels) { - this.id = id; - this.relays = relays; - this.channels = channels; - } - - } - private final ComponentManager componentManager; private final ConfigurationAdmin cm; @@ -362,80 +358,65 @@ public static List order(List components) { return copy; } - @Override - public boolean anyComponentUses(String value, List ignoreIds) { + private List getComponentUseing(String value, List ignoreIds) { return this.componentManager.getAllComponents().stream() // - .filter(t -> !ignoreIds.stream().anyMatch(id -> t.id().equals(id))).anyMatch(c -> { // + .filter(t -> !ignoreIds.stream().anyMatch(id -> t.id().equals(id))) // + .filter(c -> { // var t = c.getComponentContext().getProperties(); - var iterator = t.keys().asIterator(); - while (iterator.hasNext()) { - var key = iterator.next(); + return enumerationAsStream(t.keys()).anyMatch(key -> { var element = t.get(key).toString(); - if (element.contains(value)) { - return true; - } - } - return false; - }); - } - - @Override - public List getAllRelays() { - List allDigitalOutputs = this.getEnabledComponentsOfType(DigitalOutput.class); - List relays = new LinkedList<>(); - for (DigitalOutput digitalOutput : allDigitalOutputs) { - List availableIos = new LinkedList<>(); - for (var i = 0; i < digitalOutput.digitalOutputChannels().length; i++) { - var ioName = digitalOutput.id() + "/Relay" + (i + 1); - availableIos.add(ioName); - } - relays.add(new Relay(digitalOutput.id(), availableIos, digitalOutput.digitalOutputChannels().length)); - } - return relays; - } - - @Override - public List getAvailableRelays() { - return this.getAvailableRelays(new ArrayList<>()); + return element.contains(value); + }); + }).toList(); } @Override - public List getAvailableRelays(List ignoreIds) { - List allDigitalOutputs = this.getEnabledComponentsOfType(DigitalOutput.class); - List relays = new LinkedList<>(); - for (DigitalOutput digitalOutput : allDigitalOutputs) { - List availableIos = new LinkedList<>(); - for (var i = 0; i < digitalOutput.digitalOutputChannels().length; i++) { - var ioName = digitalOutput.id() + "/Relay" + (i + 1); - if (!this.anyComponentUses(ioName, ignoreIds)) { - availableIos.add(ioName); - } - } - relays.add(new Relay(digitalOutput.id(), availableIos, digitalOutput.digitalOutputChannels().length)); - } - return relays; - } - - @Override - public List getAvailableRelays(String ioId) throws OpenemsNamedException { - return this.getAvailableRelays(ioId, new ArrayList<>()); + public boolean anyComponentUses(String value, List ignoreIds) { + return this.componentManager.getAllComponents().stream() // + .filter(t -> !ignoreIds.stream().anyMatch(id -> t.id().equals(id))) // + .anyMatch(c -> { // + var t = c.getComponentContext().getProperties(); + return enumerationAsStream(t.keys()).anyMatch(key -> { + var element = t.get(key).toString(); + return element.contains(value); + }); + }); } @Override - public List getAvailableRelays(String ioId, List ignoreIds) throws OpenemsNamedException { - var digitalOutput = this.componentManager.getComponent(ioId); - if (!(digitalOutput instanceof DigitalOutput)) { - return Collections.emptyList(); - } - List availableIos = new LinkedList<>(); - for (var i = 0; i < ((DigitalOutput) digitalOutput).digitalOutputChannels().length; i++) { - var ioName = digitalOutput.id() + "/Relay" + (i + 1); - if (!this.anyComponentUses(ioName, ignoreIds)) { - availableIos.add(ioName); + public List getAllRelayInfos(List ignoreIds) { + return this.getEnabledComponentsOfType(DigitalOutput.class).stream() // + .map(digitalOutput -> { + return new RelayInfo(digitalOutput.id(), digitalOutput.alias(), + Arrays.stream(digitalOutput.digitalOutputChannels()) // + .map(t -> new RelayContactInfo(t.address().toString(), relayAliasMapper(t), + this.getComponentUseing(t.address().toString(), ignoreIds))) + .toList()); + }) // + .sorted((o1, o2) -> o1.id().compareTo(o2.id())) // + .toList(); + } + + // TODO remove when channels have their own alias + private static String relayAliasMapper(BooleanWriteChannel booleanWriteChannel) { + // TODO add translation + for (final var iface : booleanWriteChannel.getComponent().getClass().getInterfaces()) { + var alias = switch (iface.getCanonicalName()) { + case "io.openems.edge.io.kmtronic.four.IoKmtronicRelay4Port" -> + switch (booleanWriteChannel.address().getChannelId()) { + case "Relay1" -> "Relais 1 (Pin 11/12)"; + case "Relay2" -> "Relais 2 (Pin 13/14)"; + case "Relay3" -> "Relais 3 (Pin 15/16)"; + case "Relay4" -> "Relais 4"; + default -> null; + }; + default -> null; + }; + if (alias != null) { + return alias; } } - - return availableIos; + return booleanWriteChannel.address().toString(); } @Override @@ -487,31 +468,55 @@ public String getNextAvailableId(String baseName, int startingNumber, List ignoreIds, int[] relays4Channel, int[] relays8Channel) { - if (relays8Channel == null) { - return null; - } + public String[] getPreferredRelays(// + List ignoreIds, // + int cnt, // + PreferredRelay first, // + PreferredRelay... inputPreferredRelays // + ) { + final var combinedArray = new PreferredRelay[inputPreferredRelays.length + 1]; + combinedArray[0] = first; + System.arraycopy(inputPreferredRelays, 0, combinedArray, 1, inputPreferredRelays.length); + return this.getPreferredRelays(ignoreIds, cnt, combinedArray); + } + + private String[] getPreferredRelays(List ignoreIds, int cnt, PreferredRelay... inputPreferredRelays) { String[] fallBackInARowRelays = null; - var fallBackFirstAvailableRelays = new String[relays8Channel.length]; + var fallBackFirstAvailableRelays = new String[cnt]; var firstAvailableNextIndex = 0; - for (var relayBoard : this.getAvailableRelays(ignoreIds)) { - var relays = relayBoard.channels == 4 ? relays4Channel : relays8Channel; - if (relays == null) { - continue; - } - var containsAllPreferredRelays = true; - var preferredRelays = new String[relays.length]; + for (var relayInfo : this.getAllRelayInfos(ignoreIds)) { + var relays = Arrays.stream(inputPreferredRelays) // + .filter(t -> t.numberOfRelays() == relayInfo.channels().size()) // + .findAny().map(t -> t.preferredRelays()).orElse(null); + var containsAllPreferredRelays = true && relays != null; + var preferredRelays = new String[cnt]; var count = 0; - for (var number : relays) { - var relay = relayBoard.id + "/Relay" + number; - preferredRelays[count++] = relay; - if (!relayBoard.relays.contains(relay)) { - containsAllPreferredRelays = false; - break; + if (relays != null) { + for (var number : relays) { + if (number < 0) { + containsAllPreferredRelays = false; + break; + } + if (number >= relayInfo.channels().size()) { + containsAllPreferredRelays = false; + break; + } + final var channel = relayInfo.channels().get(number - 1); + if (!channel.usingComponents().isEmpty()) { + containsAllPreferredRelays = false; + break; + } + preferredRelays[count++] = channel.channel(); } } - for (var i = 0; i < relayBoard.relays.size() && firstAvailableNextIndex < relays.length; i++) { - fallBackFirstAvailableRelays[firstAvailableNextIndex++] = relayBoard.relays.get(i); + final var availableChannels = relayInfo.channels().stream() // + .filter(t -> t.usingComponents().isEmpty()) // + .toList(); + for (var availableChannel : availableChannels) { + if (firstAvailableNextIndex >= cnt) { + break; + } + fallBackFirstAvailableRelays[firstAvailableNextIndex++] = availableChannel.channel(); } if (containsAllPreferredRelays) { return preferredRelays; @@ -519,27 +524,24 @@ public String[] getPreferredRelays(List ignoreIds, int[] relays4Channel, if (fallBackInARowRelays == null) { count = 0; var startIndex = 1; - for (String string : relayBoard.relays) { - if (!string.equals(relayBoard.id + "/Relay" + (startIndex + count++))) { + for (var channelInfo : relayInfo.channels()) { + if (!channelInfo.usingComponents().isEmpty()) { startIndex += count; count = 1; } - if (count >= relays.length) { + if (count >= cnt) { break; } } - if (count >= relays.length) { - fallBackInARowRelays = new String[relays.length]; + if (count >= cnt) { + fallBackInARowRelays = new String[cnt]; for (var i = 0; i < fallBackInARowRelays.length; i++) { - fallBackInARowRelays[i] = relayBoard.id + "/Relay" + (startIndex + i); + fallBackInARowRelays[i] = relayInfo.channels().get(startIndex + i).channel(); } } } } - if (firstAvailableNextIndex < relays8Channel.length) { - return null; - } return fallBackInARowRelays != null ? fallBackInARowRelays : fallBackFirstAvailableRelays; } @@ -772,4 +774,72 @@ public Optional getComponent(String id, String factoryId) return comp; } + @Override + public int[] getUsedModbusUnitIds(// + final String modbusComponent // + ) { + final var components = this.componentManager.getAllComponents(); + + final var usedModbusUnitIds = new ArrayList(); + for (var component : components) { + final var props = component.getComponentContext().getProperties(); + + if (find(props, t -> "modbus.id".equals(t), // + t -> modbusComponent.equals(t)) == null) { + continue; + } + + final var modbusUnitIdObj = find(props, t -> "modbusUnitId".equals(t), t -> true); + if (modbusUnitIdObj == null) { + continue; + } + if (modbusUnitIdObj instanceof Integer modbusUnitId) { + usedModbusUnitIds.add(modbusUnitId); + } + } + + return usedModbusUnitIds.stream() // + .mapToInt(value -> value) // + .toArray(); + } + + private static Object find(// + Dictionary dict, // + Predicate keyPredicate, // + Predicate objPredicate // + ) { + return enumerationAsStream(dict.keys()) // + .filter(key -> { + if (!keyPredicate.test(key)) { + return false; + } + var element = dict.get(key); + if (!objPredicate.test(element)) { + return false; + } + return true; + }) // + .findFirst() // + .orElse(null); + } + + // TODO move to utility class + private static Stream enumerationAsStream(Enumeration e) { + return StreamSupport.stream(new Spliterators.AbstractSpliterator(Long.MAX_VALUE, Spliterator.ORDERED) { + public boolean tryAdvance(Consumer action) { + if (e.hasMoreElements()) { + action.accept(e.nextElement()); + return true; + } + return false; + } + + public void forEachRemaining(Consumer action) { + while (e.hasMoreElements()) { + action.accept(e.nextElement()); + } + } + }, false); + } + } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/ComponentUtilSupplier.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/ComponentUtilSupplier.java new file mode 100644 index 00000000000..9d398762c58 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/ComponentUtilSupplier.java @@ -0,0 +1,12 @@ +package io.openems.edge.core.appmanager; + +public interface ComponentUtilSupplier { + + /** + * Gets a {@link ComponentUtil}. + * + * @return the {@link ComponentUtil} + */ + ComponentUtil getComponentUtil(); + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/JsonFormlyUtil.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/JsonFormlyUtil.java deleted file mode 100644 index cd3e551bc67..00000000000 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/JsonFormlyUtil.java +++ /dev/null @@ -1,1212 +0,0 @@ -package io.openems.edge.core.appmanager; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.TreeSet; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -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.OpenemsError.OpenemsNamedException; -import io.openems.common.session.Language; -import io.openems.common.utils.JsonUtils; -import io.openems.edge.app.enums.OptionsFactory; -import io.openems.edge.common.component.OpenemsComponent; -import io.openems.edge.core.host.NetworkConfiguration; - -/** - * Source https://formly.dev/examples/introduction. - */ -public class JsonFormlyUtil { - - private JsonFormlyUtil() { - } - - /** - * Creates a JsonObject Formly Checkbox Builder for the given enum. - * - * @param the type of the enum - * @param property the enum property - * @return a {@link CheckboxBuilder} - */ - public static > CheckboxBuilder buildCheckbox(T property) { - return new CheckboxBuilder(toNameable(property)); - } - - /** - * Creates a JsonObject Formly Checkbox Builder for the given enum. - * - * @param nameable the {@link Nameable} property - * @return a {@link CheckboxBuilder} - */ - public static CheckboxBuilder buildCheckboxFromNameable(Nameable nameable) { - return new CheckboxBuilder(nameable); - } - - /** - * Creates a JsonObject Formly Input Builder for the given enum. - * - * @param the type of the enum - * @param property the enum property - * @return a {@link InputBuilder} - */ - public static > InputBuilder buildInput(T property) { - return new InputBuilder(toNameable(property)); - } - - /** - * Creates a JsonObject Formly Input Builder for the given enum. - * - * @param nameable the {@link Nameable} property - * @return a {@link InputBuilder} - */ - public static InputBuilder buildInputFromNameable(Nameable nameable) { - return new InputBuilder(nameable); - } - - /** - * Creates a JsonObject Formly Input Builder for the given enum. - * - * @param the type of the enum - * @param property the enum property - * @return a {@link InputBuilder} - */ - public static > FieldGroupBuilder buildFieldGroup(T property) { - return new FieldGroupBuilder(toNameable(property)); - } - - /** - * Creates a JsonObject Formly Input Builder for the given enum. - * - * @param nameable the {@link Nameable} property - * @return a {@link InputBuilder} - */ - public static FieldGroupBuilder buildFieldGroupFromNameable(Nameable nameable) { - return new FieldGroupBuilder(nameable); - } - - /** - * Creates a JsonObject Formly Select Builder for the given enum. - * - * @param the type of the enum - * @param property the enum property - * @return a {@link SelectBuilder} - */ - public static > SelectBuilder buildSelect(T property) { - return new SelectBuilder(toNameable(property)); - } - - /** - * Creates a JsonObject Formly Select Builder for the given enum. - * - * @param nameable the {@link Nameable} property - * @return a {@link SelectBuilder} - */ - public static SelectBuilder buildSelectFromNameable(Nameable nameable) { - return new SelectBuilder(nameable); - } - - /** - * Creates a JsonObject Formly Range Builder for the given enum. - * - * @param the type of the enum - * @param property the enum property - * @return a {@link RangeBuilder} - */ - public static > RangeBuilder buildRange(T property) { - return new RangeBuilder(toNameable(property)); - } - - /** - * Creates a JsonObject Formly Range Builder for the given enum. - * - * @param nameable the {@link Nameable} property - * @return a {@link RangeBuilder} - */ - public static RangeBuilder buildRangeFromNameable(Nameable nameable) { - return new RangeBuilder(nameable); - } - - /** - * Creates a JsonObject Formly Repeat Builder for the given enum. - * - * @param the type of the enum - * @param property the enum property - * @return a {@link RepeatBuilder} - */ - public static > RepeatBuilder buildRepeat(T property) { - return new RepeatBuilder(toNameable(property)); - } - - /** - * Creates a JsonObject Formly Repeat Builder for the given enum. - * - * @param nameable the {@link Nameable} property - * @return a {@link RepeatBuilder} - */ - public static RepeatBuilder buildRepeat(Nameable nameable) { - return new RepeatBuilder(nameable); - } - - private static > Nameable toNameable(T property) { - return new StaticNameable(property.name()); - } - - public static final class StaticNameable implements Nameable { - - private final String name; - - public StaticNameable(String name) { - super(); - this.name = name; - } - - @Override - public String name() { - return this.name; - } - - } - - public enum Wrappers { - /** - * Wrapper for setting the default value dynamically based on the different - * {@link Case Cases}. - */ - DEFAULT_OF_CASES("formly-wrapper-default-of-cases"), // - - /** - * Wrapper for a panel. - */ - PANEL("panel"), // - ; - - private final String wrapperClass; - - private Wrappers(String wrapperClass) { - this.wrapperClass = wrapperClass; - } - - public String getWrapperClass() { - return this.wrapperClass; - } - - } - - public static class DefaultValueOptions { - - private final Nameable field; - private final List cases; - - public DefaultValueOptions(Nameable field, Case... cases) { - super(); - this.field = field; - this.cases = Arrays.stream(cases).collect(Collectors.toList()); - } - - /** - * Creates a {@link JsonObject} from this {@link DefaultValueOptions}. - * - * @return the {@link JsonObject} - */ - public JsonObject toJsonObject() { - return JsonUtils.buildJsonObject() // - .addProperty("field", this.field.name()) // - .add("cases", this.cases.stream().map(Case::toJsonObject).collect(JsonUtils.toJsonArray())) // - .build(); - } - - } - - public static class Case { - private final JsonElement value; - private final JsonElement defaultValue; - - public Case(JsonElement value, JsonElement defaultValue) { - super(); - this.value = value; - this.defaultValue = defaultValue; - } - - public Case(String value, String defaultValue) { - this(new JsonPrimitive(value), new JsonPrimitive(defaultValue)); - } - - public Case(Number value, String defaultValue) { - this(new JsonPrimitive(value), new JsonPrimitive(defaultValue)); - } - - /** - * Creates a {@link JsonObject} from this {@link Case}. - * - * @return the {@link JsonObject} - */ - public JsonObject toJsonObject() { - return JsonUtils.buildJsonObject() // - .add("case", this.value) // - .add("defaultValue", this.defaultValue) // - .build(); - } - - } - - /** - * A Builder for a Formly field. - * - *
    -	 * {
    -	 * 	"key": "key",
    -	 * 	"type": "input",
    -	 * 	"templateOptions": {
    -	 * 		"label": "label",
    -	 * 		"required": true
    -	 * 	},
    -	 * 	"expressionProperties": {
    -	 * 		"templateOptions.required": "model.PROPERTY"
    -	 * 	},
    -	 * 	"hideExpression": "!model.PROPERTY",
    -	 * 	"defaultValue": "defaultValue",
    -	 *  "wrappers": []{@link Wrappers}
    -	 * }
    -	 * 
    - * - */ - public abstract static class FormlyBuilder> implements OnlyIf, Self { - - protected final JsonObject jsonObject = new JsonObject(); - protected final JsonObject templateOptions = new JsonObject(); - private JsonObject expressionProperties = null; - private final List wrappers = new ArrayList<>(); - - private FormlyBuilder(Nameable property) { - this.setKey(property.name()); - this.setType(this.getType()); - this.setLabel(property.name()); - } - - private FormlyBuilder(DefaultEnum property) { - this.setKey(property.name()); - this.setType(this.getType()); - this.setDefaultValue(property.getDefaultValue()); - this.setLabel(property.name()); - } - - private final T setType(String type) { - if (type == null) { - this.jsonObject.remove("type"); - return this.self(); - } - this.jsonObject.addProperty("type", type); - return this.self(); - } - - public final T setKey(String key) { - if (key != null) { - this.jsonObject.addProperty("key", key); - } else if (this.jsonObject.has("key")) { - this.jsonObject.remove("key"); - } - return this.self(); - } - - public final T setDefaultValue(String defaultValue) { - if (defaultValue != null) { - this.jsonObject.addProperty("defaultValue", defaultValue); - } else if (this.jsonObject.has("defaultValue")) { - this.jsonObject.remove("defaultValue"); - } - - return this.self(); - } - - public final T setDefaultValue(Boolean defaultValue) { - if (defaultValue != null) { - this.jsonObject.addProperty("defaultValue", defaultValue); - } else if (this.jsonObject.has("defaultValue")) { - this.jsonObject.remove("defaultValue"); - } - - return this.self(); - } - - public final T setDefaultValue(Number defaultValue) { - if (defaultValue != null) { - this.jsonObject.addProperty("defaultValue", defaultValue); - } else if (this.jsonObject.has("defaultValue")) { - this.jsonObject.remove("defaultValue"); - } - - return this.self(); - } - - public final T setDefaultValue(JsonElement defaultValue) { - if (defaultValue != null) { - this.jsonObject.add("defaultValue", defaultValue); - } else if (this.jsonObject.has("defaultValue")) { - this.jsonObject.remove("defaultValue"); - } - - return this.self(); - } - - public final T setDefaultValueWithStringSupplier(Supplier supplieDefaultValue) { - return this.setDefaultValue(supplieDefaultValue.get()); - } - - public final T setDefaultValueWithBooleanSupplier(Supplier supplieDefaultValue) { - return this.setDefaultValue(supplieDefaultValue.get()); - } - - /** - * Sets if the input is required. Default: 'false' - * - * @param isRequired if the input is required - * @return this - */ - public final T isRequired(boolean isRequired) { - if (isRequired) { - this.templateOptions.addProperty("required", isRequired); - } else if (this.templateOptions.has("required")) { - this.templateOptions.remove("required"); - } - return this.self(); - } - - public final T setLabel(String label) { - if (label != null) { - this.templateOptions.addProperty("label", label); - } else if (this.templateOptions.has("label")) { - this.templateOptions.remove("label"); - } - return this.self(); - } - - public final T setDescription(String description) { - this.templateOptions.addProperty("description", description); - return this.self(); - } - - /** - * Only shows the current input if the input of the given property is checked. - * - * @param nameable the {@link Nameable} - * @return this - */ - public final T onlyShowIfChecked(Nameable nameable) { - return this.onlyShowIf(ExpressionBuilder.of(nameable)); - } - - /** - * Only shows the current input if the input of the given property is not - * checked. - * - * @param nameable the {@link Nameable} - * @return this - */ - public final T onlyShowIfNotChecked(Nameable nameable) { - return this.onlyShowIf(ExpressionBuilder.of(nameable).negotiate()); - } - - /** - * Only shows the current input if the value of the input of the given property - * is the same as the given value. - * - * @param nameable the {@link Nameable} - * @param value the value to validate against - * @return this - */ - public final T onlyShowIfValueEquals(Nameable nameable, String value) { - return this.onlyShowIf(ExpressionBuilder.of(nameable, ExpressionBuilder.Operator.EQ, value)); - } - - private final T onlyShowIf(String expression) { - this.getExpressionProperties().addProperty("templateOptions.required", expression); - this.jsonObject.addProperty("hideExpression", "!(" + expression + ")"); - return this.self(); - } - - /** - * Only shows the current input if the given {@link ExpressionBuilder} returns - * true. - * - * @param expressionBuilder the {@link ExpressionBuilder} to set - * @return this - */ - public final T onlyShowIf(ExpressionBuilder expressionBuilder) { - return this.onlyShowIf(expressionBuilder.toString()); - } - - public final T setLabelExpression(ExpressionBuilder expression, String trueLabel, String falseLabel) { - this.getExpressionProperties().addProperty("templateOptions.label", - expression.toString() + " ? '" + trueLabel + "' : '" + falseLabel + "'"); - return this.self(); - } - - public final T setDefaultValueCases(DefaultValueOptions... defaultValueOptions) { - this.templateOptions.add("defaultValueOptions", Arrays.stream(defaultValueOptions) - .map(DefaultValueOptions::toJsonObject).collect(JsonUtils.toJsonArray())); - return this.addWrapper(Wrappers.DEFAULT_OF_CASES); - } - - /** - * Adds a wrapper to the current input. - * - * @param wrapper the {@link Wrappers} to add - * @return this - */ - public final T addWrapper(Wrappers wrapper) { - this.wrappers.add(wrapper.getWrapperClass()); - return this.self(); - } - - public JsonObject build() { - this.jsonObject.add("templateOptions", this.templateOptions); - if (this.expressionProperties != null && this.expressionProperties.size() > 0) { - this.jsonObject.add("expressionProperties", this.expressionProperties); - } - if (!this.wrappers.isEmpty()) { - this.jsonObject.add("wrappers", - this.wrappers.stream().map(JsonPrimitive::new).collect(JsonUtils.toJsonArray())); - } - return this.jsonObject; - } - - protected abstract String getType(); - - protected final JsonObject getExpressionProperties() { - if (this.expressionProperties == null) { - this.expressionProperties = new JsonObject(); - } - return this.expressionProperties; - } - - @Override - @SuppressWarnings("unchecked") - public T self() { - return (T) this; - } - - } - - public static final class ExpressionBuilder { - - public static enum Operator { - EQ("=="), // - NEQ("!="), // - ; - - private final String operation; - - private Operator(String operation) { - this.operation = operation; - } - - public String getOperation() { - return this.operation; - } - } - - private StringBuilder sb; - - /** - * Creates a {@link ExpressionBuilder} where the input of the given property - * gets validated against the given value. - * - * @param nameable the {@link Nameable} - * @param operator the {@link Operator} to validate against the value - * @param value the value to validate against - * @return the {@link ExpressionBuilder} - */ - public static final ExpressionBuilder of(Nameable nameable, Operator operator, String value) { - return new ExpressionBuilder(expressionOf(nameable, operator, value)); - } - - /** - * Creates a {@link ExpressionBuilder} where the value of the input of the given - * property gets validated. - * - * @param nameable the {@link Nameable} - * @return the {@link ExpressionBuilder} - */ - public static final ExpressionBuilder of(Nameable nameable) { - return new ExpressionBuilder(expressionOf(nameable)); - } - - private ExpressionBuilder(String baseExpression) { - this.sb = new StringBuilder(baseExpression); - } - - /** - * Combines the current expression with the given expression with an and. - * - * @param nameable the {@link Nameable} - * @param operator the {@link Operator} - * @param value the value to validate the input of the property - * @return this - */ - public ExpressionBuilder and(Nameable nameable, Operator operator, String value) { - return this.and(expressionOf(nameable, operator, value)); - } - - /** - * Combines the current expression with the given expression with an and. - * - * @param nameable the {@link Nameable} - * @return this - */ - public ExpressionBuilder and(Nameable nameable) { - return this.and(expressionOf(nameable)); - } - - /** - * Combines the current expression with the given expression with an and. - * - * @param builder the other expression - * @return this - */ - public ExpressionBuilder and(ExpressionBuilder builder) { - return this.and(builder.toString()); - } - - private final ExpressionBuilder and(String expression) { - this.sb.append(" && "); - this.sb.append(expression); - return this; - } - - /** - * Combines the current expression with the given expression with an or. - * - * @param nameable the {@link Nameable} - * @param operator the {@link Operator} - * @param value the value to validate the input of the property - * @return this - */ - public ExpressionBuilder or(Nameable nameable, Operator operator, String value) { - return this.or(expressionOf(nameable, operator, value)); - } - - /** - * Combines the current expression with the given expression with an or. - * - * @param nameable the {@link Nameable} - * @return this - */ - public ExpressionBuilder or(Nameable nameable) { - return this.or(expressionOf(nameable)); - } - - /** - * Combines the current expression with the given expression with an or. - * - * @param builder the other expression - * @return this - */ - public ExpressionBuilder or(ExpressionBuilder builder) { - return this.or(builder.toString()); - } - - private final ExpressionBuilder or(String expression) { - this.sb.append(" || "); - this.sb.append(expression); - return this; - } - - private static final String expressionOf(Nameable nameable, Operator operator, String value) { - return "model." + nameable.name() + " " + operator.getOperation() + " '" + value + "'"; - } - - private static final String expressionOf(Nameable nameable) { - return "model." + nameable.name(); - } - - private ExpressionBuilder addToFront(String string) { - final var nextBuilder = new StringBuilder(string); - this.sb = nextBuilder.append(this.sb); - return this; - } - - /** - * Puts the current statement in brackets. - * - * @return this - */ - public ExpressionBuilder inBrackets() { - this.sb.append(")"); - return this.addToFront("("); - } - - /** - * Negotiates the whole expression. - * - * @return this - */ - public ExpressionBuilder negotiate() { - this.sb.append(")"); - return this.addToFront("!("); - } - - @Override - public String toString() { - return this.sb.toString(); - } - - } - - public static final class FieldGroupBuilder extends FormlyBuilder { - - private JsonArray fieldGroup; - - private FieldGroupBuilder(Nameable property) { - super(property); - } - - private FieldGroupBuilder(DefaultEnum property) { - super(property); - } - - public FieldGroupBuilder setFieldGroup(JsonArray fieldGroup) { - this.fieldGroup = fieldGroup; - return this.self(); - } - - /** - * Hides the current key of the input. Results are all child inputs are not in - * the model as a JsonObject value of this key instead the are on the same level - * saved as this field. - * - * @return this - */ - public FieldGroupBuilder hideKey() { - this.setKey(null); - return this; - } - - @Override - protected String getType() { - return null; - } - - @Override - public JsonObject build() { - final var object = super.build(); - final var templateOptions = object.get("templateOptions").getAsJsonObject(); - templateOptions.remove("required"); - JsonUtils.getAsOptionalJsonObject(object, "expressionProperties") // - .map(t -> t.remove("templateOptions.required")); - object.add("fieldGroup", this.fieldGroup); - return JsonUtils.buildJsonObject() // - .add("hideExpression", object.remove("hideExpression")) // - .add("fieldGroup", JsonUtils.buildJsonArray() // - .add(object) // - .build()) - .build(); - } - - } - - /** - * A Builder for a Formly Input. - * - *
    -	 * {
    -	 * 	"key": "key",
    -	 * 	"type": "input",
    -	 * 	"templateOptions": {
    -	 * 		"type": "number",
    -	 * 		"label": "label",
    -	 * 		"placeholder": "placeholder",
    -	 * 		"required": true,
    -	 * 		"min": 0,
    -	 * 		"max": 100,
    -	 * 		"minLenght": 6,
    -	 * 		"maxLenght": 18,
    -	 * 		"pattern": /(\d{1,3}\.){3}\d{1,3}/
    -	 * 	},
    -	 * 	"validation": {
    -	 * 		"messages": {
    -	 * 			"pattern": "Input is not a valid IP Address!",
    -	 * 		},
    -	 * 	},
    -	 * 	"expressionProperties": {
    -	 * 		"templateOptions.required": "model.PROPERTY"
    -	 * 	},
    -	 * 	"hideExpression": "!model.PROPERTY",
    -	 * 	"defaultValue": "defaultValue"
    -	 * }
    -	 * 
    - * - */ - public static final class InputBuilder extends FormlyBuilder { - - public static enum Type { - TEXT("text"), // - PASSWORD("password"), // - NUMBER("number"), // - ; - - private String formlyTypeName; - - private Type(String type) { - this.formlyTypeName = type; - } - - public String getFormlyTypeName() { - return this.formlyTypeName; - } - } - - public static enum Validation { - // TODO translation - IP(NetworkConfiguration.PATTERN_INET4ADDRESS, "Input is not a valid IP Address!"), // - ; - - private String pattern; - private String errorMsg; - - private Validation(String pattern, String errorMsg) { - this.pattern = pattern; - this.errorMsg = errorMsg; - } - - public String getErrorMsg() { - return this.errorMsg; - } - - public String getPattern() { - return this.pattern; - } - - } - - private JsonObject validation = null; - private Type type = Type.TEXT; - - private InputBuilder(Nameable property) { - super(property); - } - - private InputBuilder(DefaultEnum property) { - super(property); - } - - /** - * Sets the type of the input. - * - *

    - * Default: {@link Type#TEXT} - * - * @param type to be set - * @return this - */ - public InputBuilder setInputType(Type type) { - this.type = type; - return this; - } - - public InputBuilder setPlaceholder(String placeholder) { - if (placeholder != null && !placeholder.isBlank()) { - this.templateOptions.addProperty("placeholder", placeholder); - } else if (this.templateOptions.has("placeholder")) { - this.templateOptions.remove("placeholder"); - } - return this; - } - - /** - * Sets the min value of the input. - * - * @param min the min number that can be set - * @return this - * @throws IllegalArgumentException if the type is not set to number - */ - public InputBuilder setMin(int min) { - if (this.type != Type.NUMBER) { - throw new IllegalArgumentException("Value min can only be set on Number inputs!"); - } - this.templateOptions.addProperty("min", min); - return this; - } - - /** - * Sets the max value of the input. - * - * @param max the max number that can be set - * @return this - * @throws IllegalArgumentException if the type is not set to number - */ - public InputBuilder setMax(int max) { - if (this.type != Type.NUMBER) { - throw new IllegalArgumentException("Value max can only be set on Number inputs!"); - } - this.templateOptions.addProperty("max", max); - return this; - } - - /** - * Sets the minLength of the input. - * - * @param minLength the min length the input needs - * @return this - * @throws IllegalArgumentException if the type is not set to password or text - */ - public InputBuilder setMinLenght(int minLength) { - if (this.type == Type.NUMBER) { - throw new IllegalArgumentException("Value minLength can only be set on Password or Text inputs!"); - } - this.templateOptions.addProperty("minLength", minLength); - return this; - } - - /** - * Sets the minLength of the input. - * - * @param maxLength the max length the input needs - * @return this - * @throws IllegalArgumentException if the type is not set to password or text - */ - public InputBuilder setMaxLenght(int maxLength) { - if (this.type == Type.NUMBER) { - throw new IllegalArgumentException("Value maxLength can only be set on Password or Text inputs!"); - } - this.templateOptions.addProperty("maxLength", maxLength); - return this; - } - - /** - * Sets the validation of the Input. - *

    - * e. g. to set the validation of an IP use {@link Validation#IP} - *

    - * - * @param validation the validation to be set - * @return this - */ - public InputBuilder setValidation(Validation validation) { - this.setPattern(validation.getPattern()); - this.setValidationMessage("pattern", validation.getErrorMsg()); - return this; - } - - private InputBuilder setPattern(String pattern) { - if (this.type != Type.TEXT) { - throw new IllegalArgumentException("Pattern can only be set on Text inputs!"); - } - this.templateOptions.addProperty("pattern", pattern); - this.setValidationMessage("pattern", "Input is not a valid IP Address!"); - return this; - } - - private InputBuilder setValidationMessage(String field, String msg) { - var validatonObject = this.getValidation(); - var messages = validatonObject.get("messages"); - if (messages == null) { - messages = new JsonObject(); - validatonObject.add("messages", messages); - } - JsonObject messagesObject; - try { - messagesObject = JsonUtils.getAsJsonObject(messages); - if (msg == null) { - messagesObject.remove(field); - } else { - messagesObject.addProperty(field, msg); - } - } catch (OpenemsNamedException e) { - e.printStackTrace(); - } - return this; - } - - @Override - protected String getType() { - return "input"; - } - - @Override - public JsonObject build() { - if (this.type != Type.TEXT) { - this.templateOptions.addProperty("type", this.type.getFormlyTypeName()); - } - if (this.validation != null && this.validation.size() > 0) { - this.jsonObject.add("validation", this.validation); - } - return super.build(); - } - - protected final JsonObject getValidation() { - if (this.validation == null) { - this.validation = new JsonObject(); - } - return this.validation; - } - - } - - public static final class RangeBuilder extends FormlyBuilder { - - private RangeBuilder(Nameable property) { - super(property); - } - - private RangeBuilder(DefaultEnum property) { - super(property); - } - - /** - * Sets the min value of the input. - * - * @param min the min number that can be set - * @return this - */ - public RangeBuilder setMin(int min) { - this.templateOptions.addProperty("min", min); - return this; - } - - /** - * Sets the max value of the input. - * - * @param max the max number that can be set - * @return this - */ - public RangeBuilder setMax(int max) { - this.templateOptions.addProperty("max", max); - return this; - } - - @Override - public JsonObject build() { - this.templateOptions.add("attributes", JsonUtils.buildJsonObject() // - .addProperty("pin", true) // - .build()); - return super.build(); - } - - @Override - protected String getType() { - return "range"; - } - - } - - /** - * A Builder for a Formly Checkbox. - * - *
    -	 * {
    -	 * 	"key": "key",
    -	 * 	"type": "checkbox",
    -	 * 	"templateOptions": {
    -	 * 		"label": "label",
    -	 * 		"required": true
    -	 * 	},
    -	 * 	"expressionProperties": {
    -	 * 		"templateOptions.required": "model.PROPERTY"
    -	 * 	},
    -	 * 	"hideExpression": "!model.PROPERTY",
    -	 * 	"defaultValue": "defaultValue"
    -	 * }
    -	 * 
    - * - */ - public static final class CheckboxBuilder extends FormlyBuilder { - - private CheckboxBuilder(Nameable property) { - super(property); - } - - private CheckboxBuilder(DefaultEnum property) { - super(property); - } - - @Override - protected String getType() { - return "checkbox"; - } - - } - - /** - * A Builder for a Formly Select. - * - *
    -	 * {
    -	 * 	"key": "key",
    -	 * 	"type": "select",
    -	 * 	"templateOptions": {
    -	 * 		"label": "label",
    -	 * 		"required": true,
    -	 * 		"multiple": true,
    -	 * 		"options": [
    -	 * 			{
    -	 * 				"label": "label",
    -	 * 				"value": "value"
    -	 * 			}, ...
    -	 * 		]
    -	 * 	},
    -	 * 	"expressionProperties": {
    -	 * 		"templateOptions.required": "model.PROPERTY"
    -	 * 	},
    -	 * 	"hideExpression": "!model.PROPERTY",
    -	 * 	"defaultValue": "defaultValue"
    -	 * }
    -	 * 
    - * - */ - public static final class SelectBuilder extends FormlyBuilder { - - public static final Function DEFAULT_COMPONENT_2_LABEL = t -> new JsonPrimitive( - t.alias() == null || t.alias().isEmpty() ? t.id() : t.id() + ": " + t.alias()); - public static final Function DEFAULT_COMPONENT_2_VALUE = t -> new JsonPrimitive( - t.id()); - - private SelectBuilder(Nameable property) { - super(property); - } - - private SelectBuilder(DefaultEnum property) { - super(property); - } - - public SelectBuilder setOptions(JsonArray options) { - this.templateOptions.add("options", options); - return this; - } - - /** - * Note the {@link Map#entry(Object, Object)} does not return a - * {@link Comparable} Object so the {@link Set} can not be a {@link TreeSet}. - * - * @param items the options - * @return this - */ - public SelectBuilder setOptions(Set> items) { - return this.setOptions(items, t -> t, t -> t); - } - - public SelectBuilder setOptions(Set> items, Function item2Label, - Function item2Value) { - var options = JsonUtils.buildJsonArray(); - items.stream().forEach(t -> { - options.add(JsonUtils.buildJsonObject() // - .addProperty("label", item2Label.apply(t.getKey())) // - .addProperty("value", item2Value.apply(t.getValue())) // - .build()); - }); - return this.setOptions(options.build()); - } - - public SelectBuilder setOptions(List items) { - return this.setOptions(items, JsonPrimitive::new, JsonPrimitive::new); - } - - public SelectBuilder setOptions(List items, Function item2Label, - Function item2Value) { - var options = JsonUtils.buildJsonArray(); - for (var item : items) { - options.add(JsonUtils.buildJsonObject() // - .add("label", item2Label.apply(item)) // - .add("value", item2Value.apply(item)) // - .build()); - } - return this.setOptions(options.build()); - } - - public SelectBuilder setOptions(OptionsFactory factory, Language l) { - return this.setOptions(factory.options(l)); - } - - /** - * Sets if more than one options can be selected. - * - * @param isMulti if more options can be selected - * @return this - */ - public SelectBuilder isMulti(boolean isMulti) { - if (isMulti) { - this.templateOptions.addProperty("multiple", isMulti); - } else if (this.templateOptions.has("multiple")) { - this.templateOptions.remove("multiple"); - } - return this; - } - - @Override - protected String getType() { - return "select"; - } - - } - - /** - * A Builder for a Formly Checkbox. - * - *
    -	 * {
    -	 * 	"key": "key",
    -	 * 	"type": "repeat",
    -	 * 	"templateOptions": {
    -	 * 		"label": "label",
    -	 * 		"required": true
    -	 * 	},
    -	 * 	"expressionProperties": {
    -	 * 		"templateOptions.required": "model.PROPERTY"
    -	 * 	},
    -	 * 	"hideExpression": "!model.PROPERTY",
    -	 * 	"defaultValue": "defaultValue"
    -	 * }
    -	 * 
    - * - */ - public static final class RepeatBuilder extends FormlyBuilder { - - private JsonObject fieldArray; - - private RepeatBuilder(Nameable property) { - super(property); - } - - private RepeatBuilder(DefaultEnum property) { - super(property); - } - - public RepeatBuilder setAddText(String addText) { - if (addText != null && !addText.isBlank()) { - this.templateOptions.addProperty("addText", addText); - } else if (this.templateOptions.has("addText")) { - this.templateOptions.remove("addText"); - } - return this; - } - - public RepeatBuilder setFieldArray(JsonObject object) { - this.fieldArray = object; - return this; - } - - @Override - protected String getType() { - return "repeat"; - } - - @Override - public JsonObject build() { - if (this.fieldArray != null) { - this.jsonObject.add("fieldArray", this.fieldArray); - } - return super.build(); - } - - } - -} diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/Nameable.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/Nameable.java index 6e811538c42..3902c78d988 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/Nameable.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/Nameable.java @@ -1,7 +1,20 @@ package io.openems.edge.core.appmanager; +import java.util.Objects; + public interface Nameable { + /** + * Creates a {@link Nameable} which returns the given name on the + * {@link Nameable#name()} method. + * + * @param name the name of the {@link Nameable} + * @return the created {@link Nameable} + */ + public static Nameable of(String name) { + return new StaticNameable(name); + } + /** * Gets the name of the current instance. * @@ -9,4 +22,45 @@ public interface Nameable { */ public String name(); + public static final class StaticNameable implements Nameable, Comparable { + + private final String name; + + public StaticNameable(String name) { + super(); + this.name = name; + } + + @Override + public int compareTo(StaticNameable o) { + return this.name.compareTo(o.name); + } + + @Override + public int hashCode() { + return Objects.hash(this.name); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (this.getClass() != obj.getClass()) { + return false; + } + final var other = (StaticNameable) obj; + return Objects.equals(this.name, other.name); + } + + @Override + public String name() { + return this.name; + } + + } + } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsApp.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsApp.java index 3458e6a3f64..4e731d1a48d 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsApp.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsApp.java @@ -6,6 +6,9 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.session.Language; +import io.openems.edge.common.user.User; +import io.openems.edge.core.appmanager.flag.Flag; +import io.openems.edge.core.appmanager.flag.Flags; import io.openems.edge.core.appmanager.validator.ValidatorConfig; public interface OpenemsApp { @@ -13,10 +16,10 @@ public interface OpenemsApp { /** * Gets the {@link AppAssistant} for this {@link OpenemsApp}. * - * @param language the language of the {@link AppAssistant} + * @param user the {@link User} * @return the AppAssistant */ - public AppAssistant getAppAssistant(Language language); + public AppAssistant getAppAssistant(User user); /** * Gets the {@link AppConfiguration} needed for the {@link OpenemsApp}. @@ -66,6 +69,14 @@ public AppConfiguration getAppConfiguration(ConfigurationTarget target, JsonObje */ public String getName(Language language); + /** + * Gets the short name of the {@link OpenemsApp}. + * + * @param language the language of the name + * @return a human readable short name; can be null + */ + public String getShortName(Language language); + /** * Gets the {@link OpenemsAppCardinality} of the {@link OpenemsApp}. * @@ -96,6 +107,20 @@ public AppConfiguration getAppConfiguration(ConfigurationTarget target, JsonObje */ public OpenemsAppPermissions getAppPermissions(); + /** + * Gets {@link Flag Flags} for this {@link OpenemsApp}. A Flag could be anything + * that would describe the app more. + * + *

    + * Flags may be specific for Monitoring e. g. only show the app after a key was + * entered ({@link Flags#SHOW_AFTER_KEY_REDEEM}). + * + * @return an array of {@link Flag Flags} + */ + public default Flag[] flags() { + return new Flag[] {}; + } + /** * Validate the {@link OpenemsApp}. * diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppCategory.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppCategory.java index 4d4d3e7958c..d6508f37f9b 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppCategory.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppCategory.java @@ -44,6 +44,11 @@ public enum OpenemsAppCategory { */ HARDWARE("hardware"), + /** + * Peak-Shaving. + */ + PEAK_SHAVING("peakShaving"), // + /** * PV-Inverter. */ diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppStatus.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppStatus.java new file mode 100644 index 00000000000..7568ac62524 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppStatus.java @@ -0,0 +1,20 @@ +package io.openems.edge.core.appmanager; + +/** + * The status of the current {@link OpenemsApp}. + * + * @see OpenemsAppStatus#STABLE + * @see OpenemsAppStatus#BETA + */ +public enum OpenemsAppStatus { + /** + * Default value for {@link OpenemsApp OpenemsApps}. Usually indicates that the + * {@link OpenemsApp} is in a stable state. + */ + STABLE, + /** + * Has to be set explicit to indicate that the {@link OpenemsApp} is in a beta + * testing state. + */ + BETA; +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/ResolveDependencies.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/ResolveDependencies.java index 945127fa4f7..b46163eaf5d 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/ResolveDependencies.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/ResolveDependencies.java @@ -57,11 +57,11 @@ public void run() { * @param appManagerImpl the {@link AppManagerImpl} * @param appManagerUtil the {@link AppManagerUtil} */ - protected static void resolveDependencies(User user, AppManagerImpl appManagerImpl, AppManagerUtil appManagerUtil) { + public static void resolveDependencies(User user, AppManagerImpl appManagerImpl, AppManagerUtil appManagerUtil) { final var instances = appManagerImpl.getInstantiatedApps(); for (var instance : instances) { try { - var configuration = appManagerUtil.getAppConfiguration(ConfigurationTarget.VALIDATE, instance, + var configuration = appManagerUtil.getAppConfiguration(ConfigurationTarget.UPDATE, instance, Language.DEFAULT); // check if instance should have dependencies @@ -89,23 +89,17 @@ protected static void resolveDependencies(User user, AppManagerImpl appManagerIm continue; } - var shouldInstallConfig = false; + if (// checkstyle requires new line switch (dependency.createPolicy) { - case NEVER: + case NEVER -> { // can not resolve dependency automatically LOG.warn(String.format("Unable to automatically add dependency for %s and key %s.", instance.instanceId, dependency.key)); - continue; - case IF_NOT_EXISTING: - if (!instances.stream().anyMatch(t -> t.appId.equals(config.appId))) { - shouldInstallConfig = true; - } - break; - case ALWAYS: - shouldInstallConfig = true; - break; + yield false; } - if (!shouldInstallConfig) { + case IF_NOT_EXISTING -> instances.stream().anyMatch(t -> t.appId.equals(config.appId)); + case ALWAYS -> false; + }) { continue; } @@ -118,6 +112,8 @@ protected static void resolveDependencies(User user, AppManagerImpl appManagerIm config.initialProperties), true); future.get(); + resolveDependencies(user, appManagerImpl, appManagerUtil); + return; } catch (OpenemsNamedException | InterruptedException | ExecutionException e) { e.printStackTrace(); } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/TranslationUtil.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/TranslationUtil.java index 5926d9f1e95..ac07214cca6 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/TranslationUtil.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/TranslationUtil.java @@ -17,15 +17,37 @@ public class TranslationUtil { * the format is invalid */ public static String getTranslation(ResourceBundle translationBundle, String key, Object... params) { + final var translation = getNullableTranslation(translationBundle, key, params); + if (translation == null) { + return key; + } + return translation; + } + + /** + * Gets the value for the given key from the translationBundle. + * + * @param translationBundle the translation bundle + * @param key the key of the translation + * @param params the parameter of the translation + * @return the translated string or null if the translation was not found or the + * format is invalid + */ + public static String getNullableTranslation(// + final ResourceBundle translationBundle, // + final String key, // + final Object... params // + ) { try { - var string = Objects.requireNonNull(translationBundle).getString(Objects.requireNonNull(key)); + final var string = Objects.requireNonNull(translationBundle) // + .getString(Objects.requireNonNull(key)); if (params == null || params.length == 0) { return string; } return MessageFormat.format(string, params); } catch (MissingResourceException | IllegalArgumentException e) { e.printStackTrace(); - return key; + return null; } } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/Type.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/Type.java index d6132f07bc9..3bf4fb6585d 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/Type.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/Type.java @@ -9,19 +9,69 @@ public interface Type

    // - extends Self> { + M> // + extends Self>, Nameable { + + public static class AbstractType

    implements Type { + + private final String name; + private final AppDef def; + private final Function, M> getParameterFunction; + + public AbstractType(String name, AppDef def, + Function, M> getParameterFunction) { + super(); + this.name = name; + this.def = def; + this.getParameterFunction = getParameterFunction; + } + + @Override + public Type self() { + return this; + } + + @Override + public String name() { + return this.name; + } + + @Override + public AppDef def() { + return this.def; + } + + @Override + public Function, M> getParamter() { + return this.getParameterFunction; + } + + } public class Parameter { - public static class BundleParameter extends Parameter { + public static interface BundleProvider { + + /** + * Gets the {@link ResourceBundle} to get the translations from. + * + * @return the {@link ResourceBundle} + */ + public ResourceBundle bundle(); + + } + + public static class BundleParameter extends Parameter implements BundleProvider { public final ResourceBundle bundle; public BundleParameter(ResourceBundle bundle) { this.bundle = bundle; } - public final ResourceBundle getBundle() { + @Override + public ResourceBundle bundle() { return this.bundle; } @@ -77,13 +127,6 @@ public static final Parameter empty() { */ public AppDef def(); - /** - * Gets the name of the property. - * - * @return the name - */ - public String name(); - public static final class GetParameterValues { public final APP app; public final Language language; diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java index 6fe18b54ef0..a810150f805 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java @@ -9,7 +9,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.NoSuchElementException; import java.util.Objects; import java.util.Optional; import java.util.ResourceBundle; @@ -32,6 +31,7 @@ import org.slf4j.LoggerFactory; import com.google.common.collect.Lists; +import com.google.common.collect.Sets; import com.google.gson.JsonObject; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; @@ -216,7 +216,7 @@ private UpdateValues updateAppInternal(final User user, OpenemsAppInstance oldIn continue; } - var dependencyApp = this.appManagerUtil.getInstanceById(dd.get().instanceId); + var dependencyApp = this.appManagerUtil.findInstanceById(dd.get().instanceId).orElse(null); var appConfig = this.getAppDependencyConfig(dependencyApp, dependencieDeclaration.appConfigs); @@ -267,40 +267,8 @@ private UpdateValues updateAppInternal(final User user, OpenemsAppInstance oldIn ConfigurationTarget.UPDATE, language, // this::determineDependencyConfig, // this.includeDependency(oldInstances, toCreateInstances, language), // - dc -> { - // get old instance if existing - ExistingDependencyConfig oldAppConfig = null; - if (oldInstance != null) { - // TODO make sure not the parent is a dependency - if (dc.isDependency() && oldInstance.appId.equals(app.getAppId())) { - oldAppConfig = oldInstances.remove(new AppIdKey(dc.parent.getAppId(), dc.sub.key)); - if (oldAppConfig != null) { - for (var entry : oldAppConfig.appDependencyConfig.properties.entrySet()) { - // add old values which are not set by the DependencyDeclaration - if (!dc.appDependencyConfig.properties.has(entry.getKey())) { - dc.appDependencyConfig.properties.add(entry.getKey(), entry.getValue()); - } - } - } - } else { - AppConfiguration oldAppConfiguration = null; - try { - oldAppConfiguration = this.appManagerUtil - .getAppConfiguration(ConfigurationTarget.UPDATE, dc.app, oldInstance, language); - - } catch (OpenemsNamedException e) { - this.log.error(e.getMessage()); - } - var appDependencyConfig = DependencyDeclaration.AppDependencyConfig.create() // - .setAppId(app.getAppId()) // - .setAlias(oldInstance.alias) // - .setProperties(oldInstance.properties) // - .build(); - oldAppConfig = new ExistingDependencyConfig(app, null, null, oldAppConfiguration, - appDependencyConfig, null, null, oldInstance); - } - } - + oldInstance, oldInstances, // + (dc, oldAppConfig) -> { // map dependencies if this is the parent List dependencies = new ArrayList<>(dependencieInstances.size()); var removeKeys = new LinkedList(); @@ -348,7 +316,7 @@ private UpdateValues updateAppInternal(final User user, OpenemsAppInstance oldIn } } catch (OpenemsNamedException e) { - this.log.error(e.getMessage()); + this.log.error(e.getMessage(), e); errors.add(TranslationUtil.getTranslation(bundle, "canNotGetAppConfiguration")); } propertiesOfNewInstance = dc.appDependencyConfig.properties; @@ -436,8 +404,9 @@ private UpdateValues updateAppInternal(final User user, OpenemsAppInstance oldIn } } - var newAppInstance = new OpenemsAppInstance(dc.app.getAppId(), aliasOfNewInstance, instanceId, - propertiesOfNewInstance, dependencies); + final var newAppInstance = new OpenemsAppInstance(dc.app.getAppId(), aliasOfNewInstance, + instanceId, propertiesOfNewInstance, dependencies); + lastCreatedOrModifiedApp.set(newAppInstance); this.temporaryApps.currentlyModifiedApps().removeIf(t -> t.equals(newAppInstance)); this.temporaryApps.currentlyCreatingApps().removeIf(t -> t.equals(newAppInstance)); @@ -462,12 +431,13 @@ private UpdateValues updateAppInternal(final User user, OpenemsAppInstance oldIn var newConfig = this.getNewAppConfigWithReplacedIds(dc.app, oldInstanceOfCurrentApp, newAppInstance, AppManagerAppHelperImpl.getComponentsFromConfigs(otherAppConfigs), language); - this.removeNotAllowedToSavedProperties(newAppInstance); this.aggregateAllTasks(newConfig, oldConfig); } catch (OpenemsNamedException e) { - this.log.error(e.getMessage()); + this.log.error(e.getMessage(), e); errors.add(TranslationUtil.getTranslation(bundle, "canNotGetAppConfiguration")); + } finally { + this.removeNotAllowedToSavedProperties(newAppInstance); } return true; } @@ -558,13 +528,12 @@ private UpdateValues updateAppInternal(final User user, OpenemsAppInstance oldIn newAppInstance, AppManagerAppHelperImpl.getComponentsFromConfigs(otherAppConfigs), language); - this.removeNotAllowedToSavedProperties(newAppInstance); - this.aggregateAllTasks(newAppConfig, oldAppConfig.config); - } catch (OpenemsNamedException e) { - this.log.error(e.getMessage()); + this.log.error(e.getMessage(), e); errors.add(TranslationUtil.getTranslation(bundle, "canNotGetAppConfiguration")); + } finally { + this.removeNotAllowedToSavedProperties(newAppInstance); } return true; @@ -649,20 +618,20 @@ private BiFunction includeDepende && (d.createPolicy == DependencyDeclaration.CreatePolicy.ALWAYS // || (d.createPolicy == DependencyDeclaration.CreatePolicy.IF_NOT_EXISTING && possibleInstance.isEmpty()))) { - var config = this.determineDependencyConfig(d.appConfigs); - String appId; - UUID id = null; - List dependencies = null; - if (config.appId != null) { - appId = config.appId; - id = UUID.randomUUID(); - } else { - var instance = this.appManagerUtil.getInstanceById(config.specificInstanceId); - appId = instance.appId; - id = instance.instanceId; - dependencies = instance.dependencies; - } try { + var config = this.determineDependencyConfig(d.appConfigs); + String appId; + UUID id = null; + List dependencies = null; + if (config.appId != null) { + appId = config.appId; + id = UUID.randomUUID(); + } else { + var instance = this.appManagerUtil.findInstanceById(config.specificInstanceId).orElse(null); + appId = instance.appId; + id = instance.instanceId; + dependencies = instance.dependencies; + } // check if an instance can be created this.appManagerUtil.getAppConfiguration(ConfigurationTarget.ADD, config.appId, config.alias, config.initialProperties, language); @@ -671,7 +640,7 @@ private BiFunction includeDepende this.temporaryApps.currentlyCreatingApps().add(instance); toCreateInstances.add(instance); return IncludeApp.INCLUDE_WITH_DEPENDENCIES; - } catch (NoSuchElementException | OpenemsNamedException ex) { + } catch (OpenemsNamedException ex) { // app not found or config cant be get return IncludeApp.NOT_INCLUDED; } @@ -714,12 +683,13 @@ private static void removeAppsWithParent(// */ private void removeNotAllowedToSavedProperties(OpenemsAppInstance instance) { try { - final var app = this.appManagerUtil.getAppById(instance.appId); + final var app = this.appManagerUtil.findAppById(instance.appId).orElse(null); + if (app == null) { + return; + } Arrays.stream(app.getProperties()) // .filter(t -> !t.isAllowedToSave) // .forEach(t -> instance.properties.remove(t.name)); - } catch (NoSuchElementException e) { - // app not found } catch (UnsupportedOperationException e) { // getting properties not supported } @@ -855,7 +825,7 @@ private UpdateValues deleteAppInternal(User user, OpenemsAppInstance instance) t break; } - } catch (OpenemsNamedException | NoSuchElementException e) { + } catch (OpenemsNamedException e) { // don't include instance if broken return false; } @@ -1119,25 +1089,76 @@ private static enum IncludeApp { * @param includeResult the includeResult of the last iteration to * know if only the app without its * dependencies should be included + * @param oldMasterInstance the previous {@link OpenemsAppInstance} + * which was updated by the user + * @param oldInstances all previous {@link OpenemsAppInstance + * OpenemsAppInstances} * @return the last {@link DependencyConfig} * @throws OpenemsNamedException on error */ - private DependencyConfig foreachDependency(List errors, OpenemsApp app, AppDependencyConfig appConfig, - ConfigurationTarget target, Function addConfig, DependencyDeclaration sub, - Language l, OpenemsApp parent, Set alreadyIteratedInstances, - Function, AppDependencyConfig> determineDependencyConfig, - BiFunction includeDependency, IncludeApp includeResult) - throws OpenemsNamedException { + private DependencyConfig foreachDependency(// + List errors, // + OpenemsApp app, // + AppDependencyConfig appConfig, // + ConfigurationTarget target, // + BiFunction addConfig, // + DependencyDeclaration sub, // + Language l, // + OpenemsApp parent, // + Set alreadyIteratedInstances, // + Function, AppDependencyConfig> determineDependencyConfig, // + BiFunction includeDependency, // + IncludeApp includeResult, // + OpenemsAppInstance oldMasterInstance, // + Map oldInstances // + ) throws OpenemsNamedException { if (alreadyIteratedInstances == null) { alreadyIteratedInstances = new HashSet<>(); } AppConfiguration config = null; + var oldConfig = this.getOldAppConfig(// + new DependencyConfig(app, parent, sub, null, appConfig, null), // + app, oldMasterInstance, oldInstances, l // + ); + try { - config = this.appManagerUtil.getAppConfiguration(target, app, appConfig.alias, appConfig.initialProperties, - l); + if (oldConfig == null) { + final var comps = this.getAppManagerImpl() + .getOtherAppConfigurations(alreadyIteratedInstances.stream().toArray(UUID[]::new)) // + .stream().flatMap(c -> c.components.stream()).collect(Collectors.toList()); + OpenemsAppInstance a = null; + if (sub != null && appConfig.specificInstanceId != null) { + a = this.getInstance(appConfig.specificInstanceId); + } + config = this.getNewAppConfigWithReplacedIds(app, a, // + new OpenemsAppInstance(app.getAppId(), appConfig.alias, + appConfig.specificInstanceId == null ? UUID.randomUUID() : appConfig.specificInstanceId, + appConfig.initialProperties, null), // + comps, l); + } else { + // add old properties + final var newProps = appConfig.initialProperties.deepCopy(); + for (var prop : oldConfig.instance.properties.entrySet()) { + if (newProps.has(prop.getKey())) { + continue; + } + newProps.add(prop.getKey(), prop.getValue()); + } + final var newInstanceId = appConfig.specificInstanceId == null ? oldConfig.instance.instanceId + : appConfig.specificInstanceId; + + final var skipIds = Sets.newHashSet(alreadyIteratedInstances); + skipIds.add(newInstanceId); + final var comps = this.getAppManagerImpl() + .getOtherAppConfigurations(skipIds.stream().toArray(UUID[]::new)) // + .stream().flatMap(c -> c.components.stream()).collect(Collectors.toList()); + config = this.getNewAppConfigWithReplacedIds(app, oldConfig.instance, // + new OpenemsAppInstance(app.getAppId(), appConfig.alias, newInstanceId, newProps, null), // + comps, l); + } } catch (OpenemsNamedException e) { // can not get config of app - this.log.error(e.getMessage()); + this.log.error(e.getMessage(), e); errors.add(TranslationUtil.getTranslation(getTranslationBundle(l), "canNotGetAppConfigurationOfApp", app.getName(l))); } @@ -1153,56 +1174,62 @@ private DependencyConfig foreachDependency(List errors, OpenemsApp app, // can not determine one out of many configs continue; } - try { - OpenemsApp dependencyApp; - if (nextAppConfig.appId != null) { - dependencyApp = this.appManagerUtil.getAppById(nextAppConfig.appId); - } else { - if (alreadyIteratedInstances.contains(nextAppConfig.specificInstanceId)) { - continue; - } - alreadyIteratedInstances.add(nextAppConfig.specificInstanceId); - var specificApp = this.getInstance(nextAppConfig.specificInstanceId); - dependencyApp = this.appManagerUtil.getAppById(specificApp.appId); - // fill up properties of existing app to make sure the appConfig can be get - specificApp.properties.entrySet().forEach(entry -> { - if (nextAppConfig.properties.has(entry.getKey())) { - return; - } - nextAppConfig.properties.add(entry.getKey(), entry.getValue()); - }); - } - - var include = includeDependency.apply(app, dependency); - if (include == IncludeApp.NOT_INCLUDED) { + OpenemsApp dependencyApp; + if (nextAppConfig.appId != null) { + dependencyApp = this.appManagerUtil.findAppById(nextAppConfig.appId).orElse(null); + } else { + if (alreadyIteratedInstances.contains(nextAppConfig.specificInstanceId)) { continue; } + alreadyIteratedInstances.add(nextAppConfig.specificInstanceId); + var specificApp = this.getInstance(nextAppConfig.specificInstanceId); + dependencyApp = this.appManagerUtil.findAppById(specificApp.appId).orElse(null); + // fill up properties of existing app to make sure the appConfig can be get + specificApp.properties.entrySet().forEach(entry -> { + if (nextAppConfig.properties.has(entry.getKey())) { + return; + } + nextAppConfig.properties.add(entry.getKey(), entry.getValue()); + }); + } + if (dependencyApp == null) { + continue; + } - var addingConfig = this.foreachDependency(errors, dependencyApp, nextAppConfig, target, addConfig, - dependency, l, app, alreadyIteratedInstances, determineDependencyConfig, includeDependency, - include); - if (addingConfig != null) { - dependencies.add(addingConfig); - } - } catch (NoSuchElementException e) { - // can not find app - e.printStackTrace(); + var include = includeDependency.apply(app, dependency); + if (include == IncludeApp.NOT_INCLUDED) { + continue; + } + + var addingConfig = this.foreachDependency(errors, dependencyApp, nextAppConfig, target, addConfig, + dependency, l, app, alreadyIteratedInstances, determineDependencyConfig, includeDependency, + include, oldMasterInstance, oldInstances); + if (addingConfig != null) { + dependencies.add(addingConfig); } } } var newConfig = new DependencyConfig(app, parent, sub, config, appConfig, dependencies); - if (addConfig.apply(newConfig)) { + if (addConfig.apply(newConfig, oldConfig)) { return newConfig; } return null; } - private void foreachDependency(List errors, OpenemsApp app, String alias, JsonObject defaultProperties, - ConfigurationTarget target, Language l, - Function, AppDependencyConfig> determineDependencyConfig, - BiFunction includeDependency, - Function consumer) throws OpenemsNamedException { + private void foreachDependency(// + List errors, // + OpenemsApp app, // + String alias, // + JsonObject defaultProperties, // + ConfigurationTarget target, // + Language l, // + Function, AppDependencyConfig> determineDependencyConfig, // + BiFunction includeDependency, // + OpenemsAppInstance oldMasterInstance, // + Map oldInstances, // + BiFunction consumer // + ) throws OpenemsNamedException { var appConfig = DependencyDeclaration.AppDependencyConfig.create() // .setAppId(app.getAppId()) // .setAlias(alias) // @@ -1210,7 +1237,54 @@ private void foreachDependency(List errors, OpenemsApp app, String alias .build(); this.foreachDependency(errors, app, appConfig, target, consumer, null, l, null, null, determineDependencyConfig, - includeDependency, IncludeApp.INCLUDE_WITH_DEPENDENCIES); + includeDependency, IncludeApp.INCLUDE_WITH_DEPENDENCIES, oldMasterInstance, oldInstances); + } + + private ExistingDependencyConfig getOldAppConfig(// + DependencyConfig dc, // + OpenemsApp app, // + OpenemsAppInstance oldMasterInstance, // + Map oldInstances, // + Language language // + ) { + if (oldMasterInstance == null) { + return null; + } + ExistingDependencyConfig oldAppConfig = null; + if (dc.isDependency()) { + oldAppConfig = oldInstances.remove(new AppIdKey(dc.parent.getAppId(), dc.sub.key)); + + // if not found check if there is a old instance with the given app id + if (oldAppConfig == null) { + oldAppConfig = oldInstances.remove(new AppIdKey(dc.parent.getAppId(), dc.sub.key, dc.app.getAppId())); + } + + if (oldAppConfig != null) { + for (var entry : oldAppConfig.appDependencyConfig.properties.entrySet()) { + // add old values which are not set by the DependencyDeclaration + if (!dc.appDependencyConfig.properties.has(entry.getKey())) { + dc.appDependencyConfig.properties.add(entry.getKey(), entry.getValue()); + } + } + } + } else { + AppConfiguration oldAppConfiguration = null; + try { + oldAppConfiguration = this.appManagerUtil.getAppConfiguration(ConfigurationTarget.UPDATE, dc.app, + oldMasterInstance, language); + + } catch (OpenemsNamedException e) { + this.log.error(e.getMessage()); + } + var appDependencyConfig = DependencyDeclaration.AppDependencyConfig.create() // + .setAppId(app.getAppId()) // + .setAlias(oldMasterInstance.alias) // + .setProperties(oldMasterInstance.properties) // + .build(); + oldAppConfig = new ExistingDependencyConfig(app, null, null, oldAppConfiguration, appDependencyConfig, null, + null, oldMasterInstance); + } + return oldAppConfig; } private OpenemsAppInstance getInstance(UUID id) { @@ -1221,11 +1295,7 @@ private OpenemsAppInstance getInstance(UUID id) { return instance.get(); } } - try { - return this.appManagerUtil.getInstanceById(id); - } catch (NoSuchElementException e) { - return null; - } + return this.appManagerUtil.findInstanceById(id).orElse(null); } /** @@ -1297,7 +1367,7 @@ private DependencyConfig foreachExistingDependency(OpenemsAppInstance instance, alreadyIteratedApps = new HashSet<>(); } alreadyIteratedApps.add(instance); - var app = this.appManagerUtil.getAppById(instance.appId); + var app = this.appManagerUtil.findAppById(instance.appId).orElse(null); var config = this.appManagerUtil.getAppConfiguration(target, app, instance, l); var dependecies = new ArrayList(); @@ -1306,27 +1376,26 @@ private DependencyConfig foreachExistingDependency(OpenemsAppInstance instance, if (instance.dependencies != null) { dependecies = new ArrayList<>(instance.dependencies.size()); for (var dependency : instance.dependencies) { - try { - var dependencyApp = this.appManagerUtil.getInstanceById(dependency.instanceId); - if (alreadyIteratedApps.contains(dependencyApp)) { - continue; - } - var subApp = config.dependencies.stream().filter(t -> t.key.equals(dependency.key)).findFirst() - .get(); - var dependencyConfig = this.foreachExistingDependency(dependencyApp, target, consumer, instance, - subApp, l, alreadyIteratedApps, includeInstance); - if (dependencyConfig != null) { - dependecies.add(dependencyConfig); - } - } catch (NoSuchElementException e) { - // can not find app + var dependencyApp = this.appManagerUtil.findInstanceById(dependency.instanceId).orElse(null); + if (dependencyApp == null) { + continue; + } + if (alreadyIteratedApps.contains(dependencyApp)) { + continue; + } + var subApp = config.dependencies.stream().filter(t -> t.key.equals(dependency.key)).findFirst() + .get(); + var dependencyConfig = this.foreachExistingDependency(dependencyApp, target, consumer, instance, + subApp, l, alreadyIteratedApps, includeInstance); + if (dependencyConfig != null) { + dependecies.add(dependencyConfig); } } } } OpenemsApp parentApp = null; if (parent != null) { - parentApp = this.appManagerUtil.getAppById(parent.appId); + parentApp = this.appManagerUtil.findAppById(parent.appId).orElse(null); } DependencyDeclaration.AppDependencyConfig dependencyAppConfig; @@ -1436,13 +1505,30 @@ public ReplacableIds(String predefinedId, String defaultId, String key) { * @return the AppConfiguration with the replaced ID s of the components * @throws OpenemsNamedException on error */ - private AppConfiguration getNewAppConfigWithReplacedIds(OpenemsApp app, OpenemsAppInstance oldAppInstance, - OpenemsAppInstance newAppInstance, List otherAppComponents, Language language) - throws OpenemsNamedException { + private AppConfiguration getNewAppConfigWithReplacedIds(// + OpenemsApp app, // + OpenemsAppInstance oldAppInstance, // + OpenemsAppInstance newAppInstance, // + List otherAppComponents, // + Language language // + ) throws OpenemsNamedException { - var target = oldAppInstance == null ? ConfigurationTarget.ADD : ConfigurationTarget.UPDATE; + final var target = oldAppInstance == null ? ConfigurationTarget.ADD : ConfigurationTarget.UPDATE; final var replacableIds = this.getReplaceableComponentIds(app, newAppInstance.properties); + + // set old ids from configuration + if (oldAppInstance != null) { + final var oldIds = this.getReplaceableComponentIds(app, oldAppInstance.properties); + for (var repId : oldIds) { + final var found = replacableIds.stream().filter(t -> t.key.equals(repId.key)).findAny().orElse(null); + if (found == null) { + continue; + } + replacableIds.remove(found); + replacableIds.add(repId); + } + } final var propertiesCopy = newAppInstance.properties.deepCopy(); var indexToId = new HashMap(); @@ -1465,6 +1551,8 @@ private AppConfiguration getNewAppConfigWithReplacedIds(OpenemsApp app, OpenemsA final var replacableId = indexToId.get(comp.getId()); final var canBeReplaced = replacableId != null; final var originalId = canBeReplaced ? replacableId.predefinedId : comp.getId(); + final var expliciteSet = canBeReplaced && oldAppInstance == null + && newAppInstance.properties.get(replacableId.key) != null; var id = originalId; EdgeConfig.Component foundComponent = null; @@ -1538,6 +1626,8 @@ private AppConfiguration getNewAppConfigWithReplacedIds(OpenemsApp app, OpenemsA if (foundComponent == null && !sameIdInComponents && !usedInPreviousConfig) { id = originalId; + } else if (foundComponent != null && expliciteSet) { + id = originalId; } else { // replace number at the end and get the next available id id = canBeReplaced ? replacableId.defaultId : id; diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTaskImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTaskImpl.java index 0840cdec480..7f31b74e202 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTaskImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTaskImpl.java @@ -162,9 +162,13 @@ public void delete(User user, List otherAppConfigurations) thr } try { - // user can be null using internal method - ((ComponentManagerImpl) this.componentManager).handleDeleteComponentConfigRequest(user, - new DeleteComponentConfigRequest(comp.getId())); + final var request = new DeleteComponentConfigRequest(comp.getId()); + if (user != null) { + this.componentManager.handleJsonrpcRequest(user, request); + } else { + // user can be null using internal method + ((ComponentManagerImpl) this.componentManager).handleDeleteComponentConfigRequest(user, request); + } this.deletedComponents.add(comp.getId()); } catch (OpenemsNamedException e) { errors.add(e.toString()); diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyUtil.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyUtil.java index 8b22600491e..bae01298115 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyUtil.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyUtil.java @@ -12,6 +12,7 @@ import io.openems.edge.core.appmanager.AppConfiguration; import io.openems.edge.core.appmanager.AppManager; import io.openems.edge.core.appmanager.AppManagerImpl; +import io.openems.edge.core.appmanager.OpenemsAppInstance; public class DependencyUtil { @@ -64,8 +65,21 @@ public static final UUID getInstanceIdOfAppWhichHasComponent(ComponentManager co } /** - * Gets the instanceId of the first found app that has the given componentId in - * its {@link AppConfiguration}. + * Gets the instance of the app which has the given Component. + * + * @param componentManager the {@link ComponentManager} + * @param componentId the ComponentId + * @return the {@link OpenemsAppInstance} or null if no instance has this + * component + */ + public static final OpenemsAppInstance getAppWhichHasComponent(ComponentManager componentManager, + String componentId) { + return using(t -> t.getAppWhichHasComponentInternal(componentManager, componentId)); + } + + /** + * Gets the {@link OpenemsAppInstance} of the first found instance that has the + * given componentId in its {@link AppConfiguration}. * *

    * NOTE: when calling this inside an app configuration it can lead to an endless @@ -75,8 +89,10 @@ public static final UUID getInstanceIdOfAppWhichHasComponent(ComponentManager co * @param componentId the component id that the app should have * @return the found instanceId or null if no app has this component */ - public final UUID getInstanceIdOfAppWhichHasComponentInternal(ComponentManager componentManager, - String componentId) { + public final OpenemsAppInstance getAppWhichHasComponentInternal(// + ComponentManager componentManager, // + String componentId // + ) { synchronized (this.getInstanceIdLock) { if (this.isCurrentlyRunning) { return null; @@ -96,13 +112,36 @@ public final UUID getInstanceIdOfAppWhichHasComponentInternal(ComponentManager c for (var entry : appManagerImpl.appConfigs(instances, null)) { if (entry.getValue().components.stream().anyMatch(c -> c.getId().equals(componentId))) { this.setCurrentlyRunning(false); - return entry.getKey().instanceId; + return entry.getKey(); } } this.setCurrentlyRunning(false); return null; } + /** + * Gets the instanceId of the first found instance that has the given + * componentId in its {@link AppConfiguration}. + * + *

    + * NOTE: when calling this inside an app configuration it can lead to an endless + * loop + * + * @param componentManager a componentManager to get the appManager + * @param componentId the component id that the app should have + * @return the found instanceId or null if no app has this component + */ + public UUID getInstanceIdOfAppWhichHasComponentInternal(// + ComponentManager componentManager, // + String componentId // + ) { + final var foundApp = this.getAppWhichHasComponentInternal(componentManager, componentId); + if (foundApp != null) { + return foundApp.instanceId; + } + return null; + } + private void setCurrentlyRunning(boolean isCurrentlyRunning) { synchronized (this.getInstanceIdLock) { this.isCurrentlyRunning = isCurrentlyRunning; diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/UpdateValues.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/UpdateValues.java index 57ae5f41f12..30645bbab33 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/UpdateValues.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/UpdateValues.java @@ -1,5 +1,6 @@ package io.openems.edge.core.appmanager.dependency; +import java.util.Collections; import java.util.List; import io.openems.edge.core.appmanager.OpenemsAppInstance; @@ -20,9 +21,9 @@ public UpdateValues(OpenemsAppInstance rootInstance, List mo public UpdateValues(OpenemsAppInstance rootInstance, List modifiedOrCreatedApps, List deletedApps, List warnings) { this.rootInstance = rootInstance; - this.modifiedOrCreatedApps = modifiedOrCreatedApps; - this.deletedApps = deletedApps; - this.warnings = warnings; + this.modifiedOrCreatedApps = Collections.unmodifiableList(modifiedOrCreatedApps); + this.deletedApps = Collections.unmodifiableList(deletedApps); + this.warnings = Collections.unmodifiableList(warnings); } } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/flag/Flag.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/flag/Flag.java new file mode 100644 index 00000000000..8c80c310e75 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/flag/Flag.java @@ -0,0 +1,27 @@ +package io.openems.edge.core.appmanager.flag; + +import com.google.gson.JsonElement; + +import io.openems.common.utils.JsonUtils; + +public interface Flag { + + /** + * Gets the name of the flag. + * + * @return the name + */ + String name(); + + /** + * Serializes this flag to a {@link JsonElement}. + * + * @return the {@link JsonElement} + */ + default JsonElement toJson() { + return JsonUtils.buildJsonObject() // + .addProperty("name", this.name()) // + .build(); + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/flag/Flags.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/flag/Flags.java new file mode 100644 index 00000000000..bb85f9bbcf2 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/flag/Flags.java @@ -0,0 +1,15 @@ +package io.openems.edge.core.appmanager.flag; + +public final class Flags { + + public static final Flag SHOW_AFTER_KEY_REDEEM = new FlagRecord("showAfterKeyRedeem"); + + private Flags() { + super(); + } + + private static record FlagRecord(String name) implements Flag { + + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/Case.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/Case.java new file mode 100644 index 00000000000..806d21ed333 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/Case.java @@ -0,0 +1,31 @@ +package io.openems.edge.core.appmanager.formly; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + +import io.openems.common.utils.JsonUtils; + +public record Case(JsonElement value, JsonElement defaultValue) { + + public Case(String value, String defaultValue) { + this(new JsonPrimitive(value), new JsonPrimitive(defaultValue)); + } + + public Case(Number value, String defaultValue) { + this(new JsonPrimitive(value), new JsonPrimitive(defaultValue)); + } + + /** + * Creates a {@link JsonObject} from this {@link Case}. + * + * @return the {@link JsonObject} + */ + public JsonObject toJsonObject() { + return JsonUtils.buildJsonObject() // + .add("case", this.value) // + .add("defaultValue", this.defaultValue) // + .build(); + } + +} \ No newline at end of file diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/DefaultValueOptions.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/DefaultValueOptions.java new file mode 100644 index 00000000000..91b62dcf582 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/DefaultValueOptions.java @@ -0,0 +1,35 @@ +package io.openems.edge.core.appmanager.formly; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import com.google.gson.JsonObject; + +import io.openems.common.utils.JsonUtils; +import io.openems.edge.core.appmanager.Nameable; + +public class DefaultValueOptions { + + private final Nameable field; + private final List cases; + + public DefaultValueOptions(Nameable field, Case... cases) { + super(); + this.field = field; + this.cases = Arrays.stream(cases).collect(Collectors.toList()); + } + + /** + * Creates a {@link JsonObject} from this {@link DefaultValueOptions}. + * + * @return the {@link JsonObject} + */ + public JsonObject toJsonObject() { + return JsonUtils.buildJsonObject() // + .addProperty("field", this.field.name()) // + .add("cases", this.cases.stream().map(Case::toJsonObject).collect(JsonUtils.toJsonArray())) // + .build(); + } + +} \ No newline at end of file diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/Exp.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/Exp.java new file mode 100644 index 00000000000..786cdad77c8 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/Exp.java @@ -0,0 +1,149 @@ +package io.openems.edge.core.appmanager.formly; + +import java.util.ArrayList; +import java.util.stream.Collector; +import java.util.stream.Stream; + +import io.openems.edge.app.enums.TranslatableEnum; +import io.openems.edge.core.appmanager.Nameable; +import io.openems.edge.core.appmanager.formly.expression.ArrayExpression; +import io.openems.edge.core.appmanager.formly.expression.BooleanExpression; +import io.openems.edge.core.appmanager.formly.expression.StringExpression; +import io.openems.edge.core.appmanager.formly.expression.Variable; + +public final class Exp { + + /** + * Creates a {@link Variable} of the given property of the current value in the + * form control. + * + *

    + * The difference between this method and the + * {@link Exp#currentModelValue(Nameable)} method is, that the value of the + * {@link Exp#currentValue(Nameable)} method may be null when a default value + * was set and the field is readonly. + * + * @param property the property to access the value + * @return the {@link Variable} + */ + public static Variable currentValue(Nameable property) { + return new Variable("control.value?." + property.name()); + } + + /** + * Creates a {@link Variable} of the given property of the current value in the + * model. + * + *

    + * The difference between this method and the {@link Exp#currentValue(Nameable)} + * method is, that the value of the {@link Exp#currentValue(Nameable)} method + * may be null when a default value was set and the field is readonly. + * + * @param property the property of the value + * @return the {@link Variable} + */ + public static Variable currentModelValue(Nameable property) { + return new Variable("model." + property.name()); + } + + /** + * Creates a {@link Variable} to access the initial value of a property. Only + * helpful for already installed instances, otherwise this value is undefined. + * + * @param property the property of the value + * @return the {@link Variable} + */ + public static Variable initialModelValue(Nameable property) { + return new Variable("initialModel." + property.name()); + } + + /** + * Creates a {@link Variable} for a static {@link String} value. + * + * @param value the value of the variable + * @return the {@link Variable} + */ + public static Variable staticValue(String value) { + return new Variable("'" + value + "'"); + } + + /** + * Creates a {@link Variable} for a static {@link String} value. + * + * @param the type of the enum + * @param enumValue the value of the variable + * @return the {@link Variable} + */ + public static & TranslatableEnum> Variable staticValue(E enumValue) { + return new Variable("'" + enumValue.getValue() + "'"); + } + + /** + * Creates a {@link Variable} for a static {@link Number} value. + * + * @param value the value of the variable + * @return the {@link Variable} + */ + public static Variable staticValue(Number value) { + return new Variable(value.toString()); + } + + /** + * Creates a dynamic {@link Variable}. + * + * @param name the name of the variable + * @return the created {@link Variable} + */ + public static Variable dynamic(String name) { + return new Variable(name); + } + + /** + * Creates a array of the given values. + * + * @param variable the variables of the array + * @return a {@link ArrayExpression} + */ + public static ArrayExpression array(Variable... variable) { + return ArrayExpression.of(variable); + } + + /** + * Creates a collector which collects a {@link Stream} of {@link String Strings} + * to an {@link ArrayExpression}. + * + * @return the {@link Collector} + */ + public static Collector toArrayExpression() { + return Collector.of(ArrayList::new, ArrayList::add, (t, u) -> { + t.addAll(u); + return t; + }, t -> { + return ArrayExpression.of(t.toArray(Variable[]::new)); + }); + } + + /** + * Creates a combined {@link StringExpression} of the given + * {@link BooleanExpression} and {@link StringExpression}, which returns the + * first {@link StringExpression} if the given {@link BooleanExpression} returns + * true otherwise the second {@link StringExpression} gets returned. + * + * @param statement the {@link BooleanExpression} to determine which + * {@link StringExpression} should be used + * @param ifTrue the {@link StringExpression} to use when the statement + * returns true + * @param ifFalse the {@link StringExpression} to use when the statement + * returns false + * @return the final {@link StringExpression} + */ + public static StringExpression ifElse(BooleanExpression statement, StringExpression ifTrue, + StringExpression ifFalse) { + return new StringExpression( + statement.expression() + " ? " + ifTrue.expression() + " : " + ifFalse.expression()); + } + + private Exp() { + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/JsonFormlyUtil.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/JsonFormlyUtil.java new file mode 100644 index 00000000000..6f5398e62b9 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/JsonFormlyUtil.java @@ -0,0 +1,197 @@ +package io.openems.edge.core.appmanager.formly; + +import com.google.gson.JsonObject; + +import io.openems.edge.core.appmanager.Nameable; +import io.openems.edge.core.appmanager.formly.builder.CheckboxBuilder; +import io.openems.edge.core.appmanager.formly.builder.FieldGroupBuilder; +import io.openems.edge.core.appmanager.formly.builder.InputBuilder; +import io.openems.edge.core.appmanager.formly.builder.RangeBuilder; +import io.openems.edge.core.appmanager.formly.builder.RepeatBuilder; +import io.openems.edge.core.appmanager.formly.builder.SelectBuilder; +import io.openems.edge.core.appmanager.formly.builder.SelectGroupBuilder; +import io.openems.edge.core.appmanager.formly.builder.TextBuilder; + +/** + * Source https://formly.dev/examples/introduction. + */ +public class JsonFormlyUtil { + + private JsonFormlyUtil() { + } + + /** + * Creates a JsonObject Formly Checkbox Builder for the given enum. + * + * @param the type of the enum + * @param property the enum property + * @return a {@link CheckboxBuilder} + */ + public static > CheckboxBuilder buildCheckbox(T property) { + return new CheckboxBuilder(toNameable(property)); + } + + /** + * Creates a JsonObject Formly Checkbox Builder for the given enum. + * + * @param nameable the {@link Nameable} property + * @return a {@link CheckboxBuilder} + */ + public static CheckboxBuilder buildCheckboxFromNameable(Nameable nameable) { + return new CheckboxBuilder(nameable); + } + + /** + * Creates a JsonObject Formly Input Builder for the given enum. + * + * @param the type of the enum + * @param property the enum property + * @return a {@link InputBuilder} + */ + public static > InputBuilder buildInput(T property) { + return new InputBuilder(toNameable(property)); + } + + /** + * Creates a JsonObject Formly Input Builder for the given enum. + * + * @param nameable the {@link Nameable} property + * @return a {@link InputBuilder} + */ + public static InputBuilder buildInputFromNameable(Nameable nameable) { + return new InputBuilder(nameable); + } + + /** + * Creates a JsonObject Formly Input Builder for the given enum. + * + * @param the type of the enum + * @param property the enum property + * @return a {@link InputBuilder} + */ + public static > FieldGroupBuilder buildFieldGroup(T property) { + return new FieldGroupBuilder(toNameable(property)); + } + + /** + * Creates a JsonObject Formly Input Builder for the given enum. + * + * @param nameable the {@link Nameable} property + * @return a {@link InputBuilder} + */ + public static FieldGroupBuilder buildFieldGroupFromNameable(Nameable nameable) { + return new FieldGroupBuilder(nameable); + } + + /** + * Creates a JsonObject Formly Select Builder for the given enum. + * + * @param the type of the enum + * @param property the enum property + * @return a {@link SelectBuilder} + */ + public static > SelectBuilder buildSelect(T property) { + return new SelectBuilder(toNameable(property)); + } + + /** + * Creates a JsonObject Formly Select Builder for the given enum. + * + * @param nameable the {@link Nameable} property + * @return a {@link SelectBuilder} + */ + public static SelectBuilder buildSelectFromNameable(Nameable nameable) { + return new SelectBuilder(nameable); + } + + /** + * Creates a JsonObject Formly Select Group Builder for the given enum. + * + * @param the type of the enum + * @param property the enum property + * @return a {@link SelectGroupBuilder} + */ + public static > SelectGroupBuilder buildSelectGroup(T property) { + return new SelectGroupBuilder(toNameable(property)); + } + + /** + * Creates a JsonObject Formly Select Group Builder for the given enum. + * + * @param nameable the {@link Nameable} property + * @return a {@link SelectGroupBuilder} + */ + public static SelectGroupBuilder buildSelectGroupFromNameable(Nameable nameable) { + return new SelectGroupBuilder(nameable); + } + + /** + * Creates a JsonObject Formly Range Builder for the given enum. + * + * @param the type of the enum + * @param property the enum property + * @return a {@link RangeBuilder} + */ + public static > RangeBuilder buildRange(T property) { + return new RangeBuilder(toNameable(property)); + } + + /** + * Creates a JsonObject Formly Range Builder for the given enum. + * + * @param nameable the {@link Nameable} property + * @return a {@link RangeBuilder} + */ + public static RangeBuilder buildRangeFromNameable(Nameable nameable) { + return new RangeBuilder(nameable); + } + + /** + * Creates a JsonObject Formly Repeat Builder for the given enum. + * + * @param the type of the enum + * @param property the enum property + * @return a {@link RepeatBuilder} + */ + public static > RepeatBuilder buildRepeat(T property) { + return new RepeatBuilder(toNameable(property)); + } + + /** + * Creates a JsonObject Formly Repeat Builder for the given enum. + * + * @param nameable the {@link Nameable} property + * @return a {@link RepeatBuilder} + */ + public static RepeatBuilder buildRepeatFromNameable(Nameable nameable) { + return new RepeatBuilder(nameable); + } + + /** + * Creates a JsonObject Formly Text Builder for the given enum. + * + * @return a {@link TextBuilder} + */ + public static TextBuilder buildText() { + return new TextBuilder(); + } + + private static > Nameable toNameable(T property) { + return Nameable.of(property.name()); + } + + /** + * Creates a new {@link JsonObject} or returns the given {@link JsonObject} if + * it is not null. + * + * @param o the existing {@link JsonObject}; can be null + * @return the existing or created {@link JsonObject}; never null + */ + public static final JsonObject single(JsonObject o) { + if (o != null) { + return o; + } + return new JsonObject(); + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/CheckboxBuilder.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/CheckboxBuilder.java new file mode 100644 index 00000000000..5856bcc3922 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/CheckboxBuilder.java @@ -0,0 +1,73 @@ +package io.openems.edge.core.appmanager.formly.builder; + +import com.google.gson.JsonObject; + +import io.openems.common.session.Language; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; +import io.openems.edge.core.appmanager.Nameable; +import io.openems.edge.core.appmanager.TranslationUtil; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; + +/** + * A Builder for a Formly Checkbox. + * + *

    + * {
    + * 	"key": "key",
    + * 	"type": "checkbox",
    + * 	"templateOptions": {
    + * 		"label": "label",
    + * 		"required": true
    + * 	},
    + * 	"expressionProperties": {
    + * 		"templateOptions.required": "model.PROPERTY"
    + * 	},
    + * 	"hideExpression": "!model.PROPERTY",
    + * 	"defaultValue": "defaultValue"
    + * }
    + * 
    + * + */ +public final class CheckboxBuilder extends FormlyBuilder { + + private JsonObject validation; + + public CheckboxBuilder(Nameable property) { + super(property); + } + + /** + * Requires the checkbox to be checked. + * + * @param l the language of the message + * @return this + */ + public CheckboxBuilder requireTrue(Language l) { + this.templateOptions.addProperty("pattern", "true"); + final var message = TranslationUtil.getTranslation(AbstractOpenemsApp.getTranslationBundle(l), + "formly.validation.requireChecked"); + this.getValidation().add("messages", JsonUtils.buildJsonObject() // + .addProperty("pattern", message) // + .build()); + + return this; + } + + private JsonObject getValidation() { + return this.validation = JsonFormlyUtil.single(this.validation); + } + + @Override + public JsonObject build() { + final var result = super.build(); + result.add("validation", this.validation); + return result; + } + + @Override + protected String getType() { + return "checkbox"; + } + +} \ No newline at end of file diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/FieldGroupBuilder.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/FieldGroupBuilder.java new file mode 100644 index 00000000000..a4d88a8ef72 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/FieldGroupBuilder.java @@ -0,0 +1,67 @@ +package io.openems.edge.core.appmanager.formly.builder; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import io.openems.common.utils.JsonUtils; +import io.openems.edge.core.appmanager.Nameable; +import io.openems.edge.core.appmanager.formly.enums.DisplayType; +import io.openems.edge.core.appmanager.formly.enums.Wrappers; + +public final class FieldGroupBuilder extends FormlyBuilder { + + private JsonArray fieldGroup; + + private boolean setChildrenInProps; + + public FieldGroupBuilder(Nameable property) { + super(property); + } + + public FieldGroupBuilder setFieldGroup(JsonArray fieldGroup) { + this.fieldGroup = fieldGroup; + return this.self(); + } + + public FieldGroupBuilder setPopupInput(Nameable displayValue, DisplayType displayType) { + this.addWrapper(Wrappers.SAFE_INPUT); + this.templateOptions.addProperty("pathToDisplayValue", displayValue.name()); + this.templateOptions.addProperty("displayType", displayType.getTypeName()); + this.setSetChildrenInProps(true); + return this; + } + + private FieldGroupBuilder setSetChildrenInProps(boolean setChildrenInProps) { + this.setChildrenInProps = setChildrenInProps; + return this; + } + + @Override + protected String getType() { + return null; + } + + @Override + public JsonObject build() { + final var object = super.build(); + final var templateOptions = object.get("templateOptions").getAsJsonObject(); + templateOptions.remove("required"); + JsonUtils.getAsOptionalJsonObject(object, "expressionProperties") // + .map(t -> t.remove("templateOptions.required")); + + if (this.setChildrenInProps) { + templateOptions.add("fields", this.fieldGroup); + return object; + } + + object.add("fieldGroup", this.fieldGroup); + + return JsonUtils.buildJsonObject() // + .add("hideExpression", object.remove("hideExpression")) // + .add("fieldGroup", JsonUtils.buildJsonArray() // + .add(object) // + .build()) + .build(); + } + +} \ No newline at end of file diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/FormlyBuilder.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/FormlyBuilder.java new file mode 100644 index 00000000000..8f9f2777b24 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/FormlyBuilder.java @@ -0,0 +1,354 @@ +package io.openems.edge.core.appmanager.formly.builder; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Supplier; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + +import io.openems.common.utils.JsonUtils; +import io.openems.edge.core.appmanager.Nameable; +import io.openems.edge.core.appmanager.OnlyIf; +import io.openems.edge.core.appmanager.Self; +import io.openems.edge.core.appmanager.formly.DefaultValueOptions; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; +import io.openems.edge.core.appmanager.formly.enums.Wrappers; +import io.openems.edge.core.appmanager.formly.expression.BooleanExpression; +import io.openems.edge.core.appmanager.formly.expression.StringExpression; + +/** + * A Builder for a Formly field. + * + *
    + * {
    + * 	"key": "key",
    + * 	"type": "input",
    + * 	"templateOptions": {
    + * 		"label": "label",
    + * 		"required": true
    + * 	},
    + * 	"expressionProperties": {
    + * 		"templateOptions.required": "model.PROPERTY"
    + * 	},
    + * 	"hideExpression": "!model.PROPERTY",
    + * 	"defaultValue": "defaultValue",
    + *  "wrappers": []{@link Wrappers}
    + * }
    + * 
    + * + */ +public abstract class FormlyBuilder> implements OnlyIf, Self { + + protected final JsonObject jsonObject = new JsonObject(); + protected final JsonObject templateOptions = new JsonObject(); + private JsonObject expressionProperties = null; + private final List wrappers = new ArrayList<>(); + private JsonObject validators = null; + + protected FormlyBuilder(Nameable property) { + this.setType(this.getType()); + if (property == null) { + return; + } + this.setKey(property.name()); + this.setLabel(property.name()); + } + + private final T setType(String type) { + if (type == null) { + this.jsonObject.remove("type"); + return this.self(); + } + this.jsonObject.addProperty("type", type); + return this.self(); + } + + public final T setKey(String key) { + if (key != null) { + this.jsonObject.addProperty("key", key); + } else if (this.jsonObject.has("key")) { + this.jsonObject.remove("key"); + } + return this.self(); + } + + public final T setDefaultValue(String defaultValue) { + if (defaultValue != null) { + this.jsonObject.addProperty("defaultValue", defaultValue); + } else if (this.jsonObject.has("defaultValue")) { + this.jsonObject.remove("defaultValue"); + } + + return this.self(); + } + + public final T setDefaultValue(Boolean defaultValue) { + if (defaultValue != null) { + this.jsonObject.addProperty("defaultValue", defaultValue); + } else if (this.jsonObject.has("defaultValue")) { + this.jsonObject.remove("defaultValue"); + } + + return this.self(); + } + + public final T setDefaultValue(Number defaultValue) { + if (defaultValue != null) { + this.jsonObject.addProperty("defaultValue", defaultValue); + } else if (this.jsonObject.has("defaultValue")) { + this.jsonObject.remove("defaultValue"); + } + + return this.self(); + } + + public final T setDefaultValue(JsonElement defaultValue) { + if (defaultValue != null) { + this.jsonObject.add("defaultValue", defaultValue); + } else if (this.jsonObject.has("defaultValue")) { + this.jsonObject.remove("defaultValue"); + } + + return this.self(); + } + + public final T setDefaultValueWithStringSupplier(Supplier supplieDefaultValue) { + return this.setDefaultValue(supplieDefaultValue.get()); + } + + public final T setDefaultValueWithBooleanSupplier(Supplier supplieDefaultValue) { + return this.setDefaultValue(supplieDefaultValue.get()); + } + + public final JsonElement getDefaultValue() { + return this.jsonObject.get("defaultValue"); + } + + /** + * Sets if the input is required. Default: 'false' + * + * @param isRequired if the input is required + * @return this + */ + public final T isRequired(boolean isRequired) { + if (isRequired) { + this.templateOptions.addProperty("required", isRequired); + } else if (this.templateOptions.has("required")) { + this.templateOptions.remove("required"); + } + return this.self(); + } + + public final T setLabel(String label) { + if (label != null) { + this.templateOptions.addProperty("label", label); + } else if (this.templateOptions.has("label")) { + this.templateOptions.remove("label"); + } + return this.self(); + } + + public final T setDescription(String description) { + this.templateOptions.addProperty("description", description); + return this.self(); + } + + private final T onlyShowIf(String expression) { + this.getExpressionProperties().addProperty("templateOptions.required", expression); + this.jsonObject.addProperty("hideExpression", "!(" + expression + ")"); + return this.self(); + } + + /** + * Only shows the current input if the given {@link ExpressionBuilder} returns + * true. + * + * @param expression the {@link BooleanExpression} to set + * @return this + */ + public final T onlyShowIf(BooleanExpression expression) { + return this.onlyShowIf(expression.expression()); + } + + /** + * Sets if input is hidden by default. + * + * @param hide true if the input should be hidden + * @return this + */ + public final T hide(boolean hide) { + if (!hide) { + this.jsonObject.remove("hide"); + return this.self(); + } + this.jsonObject.addProperty("hide", true); + return this.self(); + } + + /** + * Sets if input is disabled by default. + * + * @param disabled true if the input should be disabled + * @return this + */ + public final T disabled(boolean disabled) { + if (!disabled) { + this.templateOptions.remove("disabled"); + return this.self(); + } + this.templateOptions.addProperty("disabled", true); + return this.self(); + } + + /** + * Sets if input is readonly. + * + * @param readonly true if the input should be readonly + * @return this + */ + public final T readonly(boolean readonly) { + if (!readonly) { + this.templateOptions.remove("readonly"); + return this.self(); + } + this.templateOptions.addProperty("readonly", true); + return this.self(); + } + + public final T setLabelExpression(StringExpression expression) { + this.getExpressionProperties().addProperty("templateOptions.label", expression.expression()); + return this.self(); + } + + public final T setDefaultValueCases(DefaultValueOptions... defaultValueOptions) { + this.templateOptions.add("defaultValueOptions", Arrays.stream(defaultValueOptions) + .map(DefaultValueOptions::toJsonObject).collect(JsonUtils.toJsonArray())); + return this.addWrapper(Wrappers.DEFAULT_OF_CASES); + } + + /** + * Hides the current key of the input. Results are all child inputs are not in + * the model as a JsonObject value of this key instead the are on the same level + * saved as this field. + * + * @return this + */ + public T hideKey() { + this.setKey(null); + return this.self(); + } + + /** + * Adds a wrapper to the current input. + * + * @param wrapper the {@link Wrappers} to add + * @return this + */ + public final T addWrapper(Wrappers wrapper) { + this.wrappers.add(wrapper.getWrapperClass()); + return this.self(); + } + + public T setCustomValidation(// + String name, // + BooleanExpression validationExpression, // + String errorMessage, // + Nameable propertyToShowError // + ) { + this.getValidators().add(name, JsonUtils.buildJsonObject() // + .addProperty("expressionString", validationExpression.expression()) // + .addProperty("message", errorMessage) // + .onlyIf(propertyToShowError != null, // + b -> b.addProperty("errorPath", propertyToShowError.name())) // + .build()); + return this.self(); + } + + /** + * Sets a custom validation of the input. + * + *

    + * This sets a formly validation like explained in the formly + * documentation with the exception, that the validation is not directly + * passed as a function instead it needs to be a string which is converted into + * a validation function from the ui. If you want detailed information about how + * the string gets converted to a function in the ui have a look at the post + * process function here. + * + *

    + * Inside the string expression you have access to: + *

      + *
    • model: the current values
    • + *
    • formState: the state of the form
    • + *
    • field: the current field
    • + *
    • control: the form control
    • + *
    • initialModel: the initial model (only set when modifying an existing + * instance)
    • + *
    + * + * @param name the name of the validation + * @param validationExpression the expression of the validation + * @param messageExpression the expression of the error message + * @param propertyToShowError the path property to show the error message + * @return this + */ + public T setCustomValidation(// + String name, // + BooleanExpression validationExpression, // + StringExpression messageExpression, // + Nameable propertyToShowError // + ) { + this.getValidators().add(name, JsonUtils.buildJsonObject() // + .addProperty("expressionString", validationExpression.expression()) // + .addProperty("messageString", messageExpression.expression()) // + .onlyIf(propertyToShowError != null, // + b -> b.addProperty("errorPath", propertyToShowError.name())) // + .build()); + return this.self(); + } + + public T setCustomValidation(// + String name, // + BooleanExpression validationExpression, // + String errorMessage // + ) { + return this.setCustomValidation(name, validationExpression, errorMessage, null); + } + + public JsonObject build() { + this.jsonObject.add("templateOptions", this.templateOptions); + if (this.expressionProperties != null && this.expressionProperties.size() > 0) { + this.jsonObject.add("expressionProperties", this.expressionProperties); + } + if (!this.wrappers.isEmpty()) { + this.jsonObject.add("wrappers", + this.wrappers.stream().map(JsonPrimitive::new).collect(JsonUtils.toJsonArray())); + } + if (this.validators != null) { + this.jsonObject.add("validators", this.validators); + } + return this.jsonObject; + } + + protected abstract String getType(); + + protected final JsonObject getExpressionProperties() { + return this.expressionProperties = JsonFormlyUtil.single(this.expressionProperties); + } + + protected final JsonObject getValidators() { + return this.validators = JsonFormlyUtil.single(this.validators); + } + + @Override + @SuppressWarnings("unchecked") + public T self() { + return (T) this; + } + +} \ No newline at end of file diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/InputBuilder.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/InputBuilder.java new file mode 100644 index 00000000000..bfe25ace009 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/InputBuilder.java @@ -0,0 +1,235 @@ +package io.openems.edge.core.appmanager.formly.builder; + +import com.google.gson.JsonObject; + +import io.openems.common.channel.Unit; +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.session.Language; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; +import io.openems.edge.core.appmanager.Nameable; +import io.openems.edge.core.appmanager.TranslationUtil; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; +import io.openems.edge.core.appmanager.formly.enums.InputType; +import io.openems.edge.core.appmanager.formly.enums.Validation; +import io.openems.edge.core.appmanager.formly.enums.Wrappers; + +/** + * A Builder for a Formly Input. + * + *
    + * {
    + * 	"key": "key",
    + * 	"type": "input",
    + * 	"templateOptions": {
    + * 		"type": "number",
    + * 		"label": "label",
    + * 		"placeholder": "placeholder",
    + * 		"required": true,
    + * 		"min": 0,
    + * 		"max": 100,
    + * 		"minLenght": 6,
    + * 		"maxLenght": 18,
    + * 		"pattern": /(\d{1,3}\.){3}\d{1,3}/
    + * 	},
    + * 	"validation": {
    + * 		"messages": {
    + * 			"pattern": "Input is not a valid IP Address!",
    + * 		},
    + * 	},
    + * 	"expressionProperties": {
    + * 		"templateOptions.required": "model.PROPERTY"
    + * 	},
    + * 	"hideExpression": "!model.PROPERTY",
    + * 	"defaultValue": "defaultValue"
    + * }
    + * 
    + * + */ +public final class InputBuilder extends FormlyBuilder { + + private JsonObject validation = null; + private InputType type = InputType.TEXT; + + public InputBuilder(Nameable property) { + super(property); + } + + /** + * Sets the type of the input. + * + *

    + * Default: {@link InputType#TEXT} + * + * @param type to be set + * @return this + */ + public InputBuilder setInputType(InputType type) { + this.type = type; + return this; + } + + public InputBuilder setPlaceholder(String placeholder) { + if (placeholder != null && !placeholder.isBlank()) { + this.templateOptions.addProperty("placeholder", placeholder); + } else if (this.templateOptions.has("placeholder")) { + this.templateOptions.remove("placeholder"); + } + return this; + } + + /** + * Sets the min value of the input. + * + * @param min the min number that can be set + * @return this + * @throws IllegalArgumentException if the type is not set to number + */ + public InputBuilder setMin(int min) { + if (this.type != InputType.NUMBER) { + throw new IllegalArgumentException("Value min can only be set on Number inputs!"); + } + this.templateOptions.addProperty("min", min); + return this; + } + + /** + * Sets the max value of the input. + * + * @param max the max number that can be set + * @return this + * @throws IllegalArgumentException if the type is not set to number + */ + public InputBuilder setMax(int max) { + if (this.type != InputType.NUMBER) { + throw new IllegalArgumentException("Value max can only be set on Number inputs!"); + } + this.templateOptions.addProperty("max", max); + return this; + } + + /** + * Sets the minLength of the input. + * + * @param minLength the min length the input needs + * @return this + * @throws IllegalArgumentException if the type is not set to password or text + */ + public InputBuilder setMinLenght(int minLength) { + if (this.type == InputType.NUMBER) { + throw new IllegalArgumentException("Value minLength can only be set on Password or Text inputs!"); + } + this.templateOptions.addProperty("minLength", minLength); + return this; + } + + /** + * Sets the minLength of the input. + * + * @param maxLength the max length the input needs + * @return this + * @throws IllegalArgumentException if the type is not set to password or text + */ + public InputBuilder setMaxLenght(int maxLength) { + if (this.type == InputType.NUMBER) { + throw new IllegalArgumentException("Value maxLength can only be set on Password or Text inputs!"); + } + this.templateOptions.addProperty("maxLength", maxLength); + return this; + } + + /** + * Sets the validation of the Input. + *

    + * e. g. to set the validation of an IP use {@link Validation#IP} + *

    + * + * @param validation the validation to be set + * @return this + */ + public InputBuilder setValidation(Validation validation) { + this.setPattern(validation.getPattern()); + this.setValidationMessage("pattern", validation.getErrorMsg()); + return this; + } + + /** + * Only allows positive number as a input. + * + * @return this + * @throws IllegalArgumentException if this {@link InputBuilder} has not been + * set to a {@link InputType#NUMBER} input via the + * {@link InputBuilder}{@link #setInputType(InputType)} + * method. + */ + public InputBuilder onlyPositiveNumbers() { + if (this.type != InputType.NUMBER) { + throw new IllegalArgumentException("OnlyPositiveNumbers can only be set on number inputs!"); + } + this.getValidators().add("validation", JsonUtils.buildJsonArray() // + .add("onlyPositiveInteger") // + .build()); + return this; + } + + public InputBuilder setUnit(Unit unit, Language l) { + var unitString = switch (unit) { + case WATT -> TranslationUtil.getTranslation(AbstractOpenemsApp.getTranslationBundle(l), "watt"); + default -> unit.symbol; + }; + this.templateOptions.addProperty("unit", unitString); + this.addWrapper(Wrappers.INPUT_WITH_UNIT); + return this; + } + + private InputBuilder setPattern(String pattern) { + if (this.type != InputType.TEXT) { + throw new IllegalArgumentException("Pattern can only be set on Text inputs!"); + } + this.templateOptions.addProperty("pattern", pattern); + this.setValidationMessage("pattern", "Input is not a valid IP Address!"); + return this; + } + + private InputBuilder setValidationMessage(String field, String msg) { + var validatonObject = this.getValidation(); + var messages = validatonObject.get("messages"); + if (messages == null) { + messages = new JsonObject(); + validatonObject.add("messages", messages); + } + JsonObject messagesObject; + try { + messagesObject = JsonUtils.getAsJsonObject(messages); + if (msg == null) { + messagesObject.remove(field); + } else { + messagesObject.addProperty(field, msg); + } + } catch (OpenemsNamedException e) { + e.printStackTrace(); + } + return this; + } + + @Override + protected String getType() { + return "input"; + } + + @Override + public JsonObject build() { + if (this.type != InputType.TEXT) { + this.templateOptions.addProperty("type", this.type.getFormlyTypeName()); + } + if (this.validation != null && this.validation.size() > 0) { + this.jsonObject.add("validation", this.validation); + } + return super.build(); + } + + protected final JsonObject getValidation() { + return this.validation = JsonFormlyUtil.single(this.validation); + } + +} \ No newline at end of file diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/RangeBuilder.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/RangeBuilder.java new file mode 100644 index 00000000000..11b659079b6 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/RangeBuilder.java @@ -0,0 +1,49 @@ +package io.openems.edge.core.appmanager.formly.builder; + +import com.google.gson.JsonObject; + +import io.openems.common.utils.JsonUtils; +import io.openems.edge.core.appmanager.Nameable; + +public final class RangeBuilder extends FormlyBuilder { + + public RangeBuilder(Nameable property) { + super(property); + } + + /** + * Sets the min value of the input. + * + * @param min the min number that can be set + * @return this + */ + public RangeBuilder setMin(int min) { + this.templateOptions.addProperty("min", min); + return this; + } + + /** + * Sets the max value of the input. + * + * @param max the max number that can be set + * @return this + */ + public RangeBuilder setMax(int max) { + this.templateOptions.addProperty("max", max); + return this; + } + + @Override + public JsonObject build() { + this.templateOptions.add("attributes", JsonUtils.buildJsonObject() // + .addProperty("pin", true) // + .build()); + return super.build(); + } + + @Override + protected String getType() { + return "range"; + } + +} \ No newline at end of file diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/RepeatBuilder.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/RepeatBuilder.java new file mode 100644 index 00000000000..af17609ebad --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/RepeatBuilder.java @@ -0,0 +1,62 @@ +package io.openems.edge.core.appmanager.formly.builder; + +import com.google.gson.JsonObject; + +import io.openems.edge.core.appmanager.Nameable; + +/** + * A Builder for a Formly Checkbox. + * + *
    + * {
    + * 	"key": "key",
    + * 	"type": "repeat",
    + * 	"templateOptions": {
    + * 		"label": "label",
    + * 		"required": true
    + * 	},
    + * 	"expressionProperties": {
    + * 		"templateOptions.required": "model.PROPERTY"
    + * 	},
    + * 	"hideExpression": "!model.PROPERTY",
    + * 	"defaultValue": "defaultValue"
    + * }
    + * 
    + * + */ +public final class RepeatBuilder extends FormlyBuilder { + + private JsonObject fieldArray; + + public RepeatBuilder(Nameable property) { + super(property); + } + + public RepeatBuilder setAddText(String addText) { + if (addText != null && !addText.isBlank()) { + this.templateOptions.addProperty("addText", addText); + } else if (this.templateOptions.has("addText")) { + this.templateOptions.remove("addText"); + } + return this; + } + + public RepeatBuilder setFieldArray(JsonObject object) { + this.fieldArray = object; + return this; + } + + @Override + protected String getType() { + return "repeat"; + } + + @Override + public JsonObject build() { + if (this.fieldArray != null) { + this.jsonObject.add("fieldArray", this.fieldArray); + } + return super.build(); + } + +} \ No newline at end of file diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/SelectBuilder.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/SelectBuilder.java new file mode 100644 index 00000000000..773947ba7d6 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/SelectBuilder.java @@ -0,0 +1,126 @@ +package io.openems.edge.core.appmanager.formly.builder; + +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeSet; +import java.util.function.Function; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; + +import io.openems.common.session.Language; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.app.enums.OptionsFactory; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.core.appmanager.Nameable; + +/** + * A Builder for a Formly Select. + * + *
    + * {
    + * 	"key": "key",
    + * 	"type": "select",
    + * 	"templateOptions": {
    + * 		"label": "label",
    + * 		"required": true,
    + * 		"multiple": true,
    + * 		"options": [
    + * 			{
    + * 				"label": "label",
    + * 				"value": "value"
    + * 			}, ...
    + * 		]
    + * 	},
    + * 	"expressionProperties": {
    + * 		"templateOptions.required": "model.PROPERTY"
    + * 	},
    + * 	"hideExpression": "!model.PROPERTY",
    + * 	"defaultValue": "defaultValue"
    + * }
    + * 
    + * + */ +public final class SelectBuilder extends FormlyBuilder { + + public static final Function DEFAULT_COMPONENT_2_LABEL = t -> new JsonPrimitive( + t.alias() == null || t.alias().isEmpty() ? t.id() : t.id() + ": " + t.alias()); + public static final Function DEFAULT_COMPONENT_2_VALUE = t -> new JsonPrimitive( + t.id()); + + public SelectBuilder(Nameable property) { + super(property); + } + + public SelectBuilder setOptions(JsonArray options) { + this.templateOptions.add("options", options); + return this; + } + + /** + * Note the {@link Map#entry(Object, Object)} does not return a + * {@link Comparable} Object so the {@link Set} can not be a {@link TreeSet}. + * + * @param items the options + * @return this + */ + public SelectBuilder setOptions(Set> items) { + return this.setOptions(items, t -> t, t -> t); + } + + public SelectBuilder setOptions(Set> items, Function item2Label, + Function item2Value) { + var options = JsonUtils.buildJsonArray(); + items.stream().forEach(t -> { + options.add(JsonUtils.buildJsonObject() // + .addProperty("label", item2Label.apply(t.getKey())) // + .addProperty("value", item2Value.apply(t.getValue())) // + .build()); + }); + return this.setOptions(options.build()); + } + + public SelectBuilder setOptions(List items) { + return this.setOptions(items, JsonPrimitive::new, JsonPrimitive::new); + } + + public SelectBuilder setOptions(List items, Function item2Label, + Function item2Value) { + var options = JsonUtils.buildJsonArray(); + for (var item : items) { + options.add(JsonUtils.buildJsonObject() // + .add("label", item2Label.apply(item)) // + .add("value", item2Value.apply(item)) // + .build()); + } + return this.setOptions(options.build()); + } + + public SelectBuilder setOptions(OptionsFactory factory, Language l) { + return this.setOptions(factory.options(l)); + } + + /** + * Sets if more than one options can be selected. + * + * @param isMulti if more options can be selected + * @return this + */ + public SelectBuilder isMulti(boolean isMulti) { + if (isMulti) { + this.templateOptions.addProperty("multiple", isMulti); + } else if (this.templateOptions.has("multiple")) { + this.templateOptions.remove("multiple"); + } + return this; + } + + @Override + protected String getType() { + return "select"; + } + +} \ No newline at end of file diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/SelectGroupBuilder.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/SelectGroupBuilder.java new file mode 100644 index 00000000000..5a62f2b5d1f --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/SelectGroupBuilder.java @@ -0,0 +1,86 @@ +package io.openems.edge.core.appmanager.formly.builder; + +import static io.openems.common.utils.JsonUtils.toJsonArray; + +import java.util.ArrayList; +import java.util.List; + +import com.google.gson.JsonObject; + +import io.openems.common.utils.JsonUtils; +import io.openems.edge.core.appmanager.Nameable; +import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; +import io.openems.edge.core.appmanager.formly.builder.selectgroup.OptionGroup; +import io.openems.edge.core.appmanager.formly.enums.DisplayType; + +/** + * A Builder for a Formly Select Group. + * + *
    + * {
    + * 	"key": "key",
    + * 	"type": "formly-option-group-picker",
    + * 	"templateOptions": {
    + * 		"label": "label",
    + * 		"required": true,
    + * 		"options": OptionGroup[]
    + * 	},
    + * 	"expressionProperties": {
    + * 		"templateOptions.required": "model.PROPERTY"
    + * 	},
    + * 	"hideExpression": "!model.PROPERTY",
    + * 	"defaultValue": "defaultValue"
    + * }
    + * 
    + * + */ +public final class SelectGroupBuilder extends FormlyBuilder { + + private final Nameable property; + private final List optionGroups = new ArrayList<>(); + + public SelectGroupBuilder(Nameable property) { + super(property); + this.property = property; + } + + @Override + protected String getType() { + return "formly-option-group-picker"; + } + + /** + * Adds a {@link OptionGroup} to this {@link SelectGroupBuilder}. + * + * @param optionGroup the {@link OptionGroup} to add + * @return this + */ + public SelectGroupBuilder addOption(OptionGroup optionGroup) { + this.optionGroups.add(optionGroup); + return this; + } + + @Override + public JsonObject build() { + // wrap input field into a popup input + final var fieldGroup = JsonFormlyUtil.buildFieldGroupFromNameable(this.property); + // copy my settings into parent field + this.templateOptions.entrySet() + .forEach(entry -> fieldGroup.templateOptions.add(entry.getKey(), entry.getValue())); + this.jsonObject.entrySet().forEach(entry -> fieldGroup.jsonObject.add(entry.getKey(), entry.getValue())); + + // set options + this.templateOptions.add("options", this.optionGroups.stream() // + .map(OptionGroup::toJson) // + .collect(toJsonArray())); + fieldGroup.setFieldGroup(JsonUtils.buildJsonArray() // + .add(super.build()) // + .build()) // + .setDefaultValue(this.getDefaultValue()); + + fieldGroup.setPopupInput(this.property, DisplayType.OPTION_GROUP); + + return fieldGroup.build(); + } + +} \ No newline at end of file diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/TextBuilder.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/TextBuilder.java new file mode 100644 index 00000000000..13824d032fe --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/TextBuilder.java @@ -0,0 +1,19 @@ +package io.openems.edge.core.appmanager.formly.builder; + +public final class TextBuilder extends FormlyBuilder { + + public TextBuilder() { + // null because no key is needed if no input happens + super(null); + } + + public TextBuilder setText(String text) { + return this.setDescription(text); + } + + @Override + protected String getType() { + return "text"; + } + +} \ No newline at end of file diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/selectgroup/Option.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/selectgroup/Option.java new file mode 100644 index 00000000000..823b8d1e979 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/selectgroup/Option.java @@ -0,0 +1,50 @@ +package io.openems.edge.core.appmanager.formly.builder.selectgroup; + +import com.google.gson.JsonElement; + +import io.openems.common.utils.JsonUtils; + +public record Option(// + /** + * Non-null. + */ + String value, // + /** + * Nullable. + */ + String title, // + /** + * Nullable. + */ + Boolean hide, // + /** + * Nullable. + */ + OptionExpressions expressions // +) { + + /** + * Creates a {@link OptionBuilder}. + * + * @param value the value of the option + * @return the {@link OptionBuilder} + */ + public static OptionBuilder buildOption(String value) { + return new OptionBuilder(value); + } + + /** + * Creates a {@link JsonElement} from this {@link Option}. + * + * @return the {@link JsonElement} + */ + public JsonElement toJson() { + return JsonUtils.buildJsonObject() // + .addProperty("value", this.value) // + .addPropertyIfNotNull("title", this.title) // + .addPropertyIfNotNull("hide", this.hide) // + .onlyIf(this.expressions != null, b -> b.add("expressions", this.expressions.toJson())) // + .build(); + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/selectgroup/OptionBuilder.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/selectgroup/OptionBuilder.java new file mode 100644 index 00000000000..2601622f614 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/selectgroup/OptionBuilder.java @@ -0,0 +1,66 @@ +package io.openems.edge.core.appmanager.formly.builder.selectgroup; + +import java.util.Objects; +import java.util.stream.Stream; + +import io.openems.edge.core.appmanager.OnlyIf; +import io.openems.edge.core.appmanager.Self; +import io.openems.edge.core.appmanager.formly.expression.BooleanExpression; +import io.openems.edge.core.appmanager.formly.expression.StringExpression; + +public class OptionBuilder implements Self, OnlyIf { + + private final String value; + private String title; + private Boolean hide; + + private BooleanExpression hideExpression; + private BooleanExpression disabledExpression; + private StringExpression titleExpression; + + public OptionBuilder(String value) { + super(); + this.value = Objects.requireNonNull(value); + } + + public OptionBuilder setTitle(String title) { + this.title = title; + return this; + } + + public OptionBuilder setHide(Boolean hide) { + this.hide = hide; + return this; + } + + public OptionBuilder setHideExpression(BooleanExpression hideExpression) { + this.hideExpression = hideExpression; + return this; + } + + public OptionBuilder setDisabledExpression(BooleanExpression disabledExpression) { + this.disabledExpression = disabledExpression; + return this; + } + + public OptionBuilder setTitleExpression(StringExpression titleExpression) { + this.titleExpression = titleExpression; + return this; + } + + public Option build() { + OptionExpressions optionExpressions = null; + if (Stream.of(this.hideExpression, this.titleExpression, this.disabledExpression) // + .anyMatch(Objects::nonNull)) { + optionExpressions = new OptionExpressions(this.hideExpression, this.disabledExpression, + this.titleExpression); + } + return new Option(this.value, this.title, this.hide, optionExpressions); + } + + @Override + public OptionBuilder self() { + return this; + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/selectgroup/OptionExpressions.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/selectgroup/OptionExpressions.java new file mode 100644 index 00000000000..6f3011dde73 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/selectgroup/OptionExpressions.java @@ -0,0 +1,37 @@ +package io.openems.edge.core.appmanager.formly.builder.selectgroup; + +import com.google.gson.JsonElement; + +import io.openems.common.utils.JsonUtils; +import io.openems.edge.core.appmanager.formly.expression.BooleanExpression; +import io.openems.edge.core.appmanager.formly.expression.StringExpression; + +public record OptionExpressions(// + /** + * Nullable. + */ + BooleanExpression hide, // + /** + * Nullable. + */ + BooleanExpression disabled, // + /** + * Nullable. + */ + StringExpression title // +) { + + /** + * Creates a {@link JsonElement} from this {@link OptionExpressions}. + * + * @return the {@link JsonElement} + */ + public JsonElement toJson() { + return JsonUtils.buildJsonObject() // + .onlyIf(this.hide() != null, b -> b.addProperty("hideString", this.hide().expression())) + .onlyIf(this.disabled() != null, b -> b.addProperty("disabledString", this.disabled().expression())) + .onlyIf(this.title() != null, b -> b.addProperty("titleString", this.title().expression())) // + .build(); + } + +} \ No newline at end of file diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/selectgroup/OptionGroup.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/selectgroup/OptionGroup.java new file mode 100644 index 00000000000..a96c7b6930c --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/selectgroup/OptionGroup.java @@ -0,0 +1,51 @@ +package io.openems.edge.core.appmanager.formly.builder.selectgroup; + +import static io.openems.common.utils.JsonUtils.toJsonArray; + +import java.util.List; + +import com.google.gson.JsonElement; + +import io.openems.common.utils.JsonUtils; + +public record OptionGroup(// + /** + * Non-null. + */ + String group, // + /** + * Non-null. + */ + String title, // + /** + * Non-null. + */ + List