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. + * + *

    + *
  • Sets 'ModbusCommunicationFailed' Channel of the ModbusComponent + *
  • Adds/Removes the component to/from the {@link DefectiveComponents} + *
+ * + * @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 + } + }