From ba1de2c9233b3c948f25cf00b8f7651d8cf61ba3 Mon Sep 17 00:00:00 2001 From: paulorb Date: Tue, 10 Sep 2024 20:02:30 +0400 Subject: [PATCH 1/2] feat(ISSUE-13): Add support for Mult, Div, IfLess and ifGreater --- docs/getting-started/operations.md | 30 +++ examples/configuration_simulation.xml | 13 + src/main/kotlin/ConfigurationParser.kt | 59 +++- src/main/kotlin/PlcSimulation.kt | 283 ++++++++++++++++++++ src/main/kotlin/operations/DivOperation.kt | 73 +++++ src/main/kotlin/operations/MultOperation.kt | 73 +++++ 6 files changed, 530 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/operations/DivOperation.kt create mode 100644 src/main/kotlin/operations/MultOperation.kt diff --git a/docs/getting-started/operations.md b/docs/getting-started/operations.md index aa7c527..647e2f1 100644 --- a/docs/getting-started/operations.md +++ b/docs/getting-started/operations.md @@ -45,6 +45,36 @@ Supported registers: **HOLDING_REGISTER**, **INPUT_REGISTER** *For **INPUT_REGISTER** type *FLOAT32* is not supported!* +## Mult +Mult (as the name implies) multiplies a certain value of a variable, like the example below: + +```xml + 1.12 +``` + +Both **symbol** and **value** are mandatory fields. **value** can be of type *FLOAT32* , *INT16* which must +follow the same type of the specified **symbol** definition. + +Supported registers: **HOLDING_REGISTER**, **INPUT_REGISTER** + +*For **INPUT_REGISTER** type *FLOAT32* is not supported!* + + +## Div +Div (as the name implies) divides a certain value of a variable, like the example below: + +```xml +
1.12
+``` + +Both **symbol** and **value** are mandatory fields. **value** can be of type *FLOAT32* , *INT16* which must +follow the same type of the specified **symbol** definition. + +Supported registers: **HOLDING_REGISTER**, **INPUT_REGISTER** + +*For **INPUT_REGISTER** type *FLOAT32* is not supported!* + + ## Random Random generates new random values each time it executes diff --git a/examples/configuration_simulation.xml b/examples/configuration_simulation.xml index 5bfa98d..ac6f3c9 100644 --- a/examples/configuration_simulation.xml +++ b/examples/configuration_simulation.xml @@ -48,9 +48,20 @@ 100 100.5 + 500 60 190.5 + 2.5 + + + + +
3.5
+ + + + 777 @@ -74,9 +85,11 @@ + 1 + 1 400 diff --git a/src/main/kotlin/ConfigurationParser.kt b/src/main/kotlin/ConfigurationParser.kt index f0062d5..819e346 100644 --- a/src/main/kotlin/ConfigurationParser.kt +++ b/src/main/kotlin/ConfigurationParser.kt @@ -23,7 +23,7 @@ class ConfigurationParser { } private fun load(): Device? { try { - val context = JAXBContext.newInstance(Device::class.java, Set::class.java, Random::class.java, Delay::class.java, Linear::class.java, Add::class.java, Sub::class.java, Csv::class.java, IfEqual::class.java, Parameters::class.java, Parameter::class.java, Trace::class.java) + val context = JAXBContext.newInstance(Device::class.java, Set::class.java, Random::class.java, Delay::class.java, Linear::class.java, Add::class.java, Sub::class.java, Csv::class.java, IfEqual::class.java, Parameters::class.java, Parameter::class.java, Trace::class.java, Mult::class.java, Div::class.java, IfGreater::class.java, IfLess::class.java) val unmarshaller = context.createUnmarshaller() return if(fileName.isEmpty() ) { val reader = StringReader(this::class.java.classLoader.getResource("configuration.xml")!!.readText()) @@ -97,6 +97,40 @@ data class IfEqual( constructor() : this("","",mutableListOf()) } +// +// 12 +// any other operation ... +// +@XmlRootElement(name="ifGreater") +data class IfGreater( + @field:XmlAttribute(required = true) + val symbol: String, + @field:XmlAttribute(required = true) + val value: String, + @XmlAnyElement(lax = true) + var randomElements: List +){ + constructor() : this("","",mutableListOf()) +} + +// +// 12 +// any other operation ... +// +@XmlRootElement(name="ifLess") +data class IfLess( + @field:XmlAttribute(required = true) + val symbol: String, + @field:XmlAttribute(required = true) + val value: String, + @XmlAnyElement(lax = true) + var randomElements: List +){ + constructor() : this("","",mutableListOf()) +} + + + // @XmlRootElement(name="csv") data class Csv( @@ -173,6 +207,29 @@ data class Add( constructor(): this("", "0") } +//RPM_MOTOR1 +@XmlRootElement(name="mult") +data class Mult( + @field:XmlAttribute(required = true) + val symbol: String, + @field:XmlValue + val value: String, +){ + constructor(): this("", "0") +} + +//
RPM_MOTOR1
+@XmlRootElement(name="div") +data class Div( + @field:XmlAttribute(required = true) + val symbol: String, + @field:XmlValue + val value: String, +){ + constructor(): this("", "0") +} + + //100 @XmlRootElement(name="delay") data class Delay( diff --git a/src/main/kotlin/PlcSimulation.kt b/src/main/kotlin/PlcSimulation.kt index 6c2f13c..cceb773 100644 --- a/src/main/kotlin/PlcSimulation.kt +++ b/src/main/kotlin/PlcSimulation.kt @@ -18,6 +18,8 @@ class PlcSimulation( val addOperation = AddOperation(configurationParser.getConfiguredDevice().configuration, memory, parameters) val setOperation = SetOperation(configurationParser.getConfiguredDevice().configuration, memory, parameters) val subOperation = SubOperation(configurationParser.getConfiguredDevice().configuration, memory, parameters) + val multOperation = MultOperation(configurationParser.getConfiguredDevice().configuration, memory, parameters) + val divOperation = DivOperation(configurationParser.getConfiguredDevice().configuration, memory, parameters) companion object { val logger = LoggerFactory.getLogger("PlcSimulation") @@ -84,17 +86,298 @@ class PlcSimulation( ifEqual(element, configuration, memory) } + is IfGreater -> { + ifGreater(element, configuration, memory) + } + + is IfLess -> { + ifLess(element, configuration, memory) + } + is Trace -> { traceOperation.traceOperation(element) } + is Mult -> { + multOperation.multOperation(element) + } + + is Div -> { + divOperation.divOperation(element) + } + else -> throw UnsupportedOperationException("Unknown simulation step type") } } + suspend fun ifGreater(element: IfGreater, configuration: Configuration, memory: PlcMemory) { + logger.info("ifGreater symbol ${element.symbol} value ${element.value}") + var value = processValue(element.value) + var variable = configuration.registers.getVarConfiguration(element.symbol) + if (variable == null) { + val envVar = parameters.resolveEnvVar(element.symbol) + if(envVar != null){ + if(envVar.type == "FLOAT32"){ + if(envVar.value.toFloat() <= value.toFloat()){ + //abort and continue + //since the value does not match the condition + return + } + //execute operations + element.randomElements.forEach { subElement -> + processOperationElement(subElement, configuration, memory) + } + }else + if(envVar.type == "INT16" || envVar.type == "BOOL" ){ + if(envVar.value.toInt() <= value.toInt()){ + //abort and continue + //since the value does not match the condition + return + } + //execute operations + element.randomElements.forEach { subElement -> + processOperationElement(subElement, configuration, memory) + } + }else { + logger.error("Symbol ${element.symbol} has invalid datatype during IfEqual execution") + } + + }else { + logger.error("Symbol ${element.symbol} not found during IfEqual execution") + throw CancellationException("Error - IfEqual") + } + } else { + + when (variable.addressType) { + + AddressType.HOLDING_REGISTER -> { + //get the current value + //compare + //execute operations + + if (variable.datatype == "FLOAT32") { + var currentValue = memory.readHoldingRegister(variable.address.toInt(), 2) + if(currentValue.isEmpty()){ + logger.error("Add Operation - Unable to get value of ${element.symbol} address ${variable.address} ") + throw CancellationException("Error - Add") + } + val intValue = (( currentValue[1].toInt() shl 16) or (currentValue[0].toInt() and 0xFFFF)) + val currentFloatValue = java.lang.Float.intBitsToFloat(intValue) + + //compare + if(currentFloatValue <= value.toFloat()){ + //abort and continue + //since the value does not match the condition + return + } + //execute operations + element.randomElements.forEach { subElement -> + processOperationElement(subElement, configuration, memory) + } + + + } else { + var currentValue = memory.readHoldingRegister(variable.address.toInt(), 1) + if(currentValue.isEmpty()){ + logger.error("IfEqual Operation - Unable to get value of ${element.symbol} address ${variable.address} ") + throw CancellationException("Error - IfEqual") + } + //compare + if(currentValue.first().toInt() <= value.toInt()){ + //abort and continue + //since the value does not match the condition + return + } + //execute operations + element.randomElements.forEach { subElement -> + processOperationElement(subElement, configuration, memory) + } + + } + } + AddressType.INPUT_REGISTER -> { + val currentValue = memory.readInputRegister(variable.address.toInt(), 1) + if(currentValue.isEmpty()){ + logger.error("IfEqual Operation - Unable to get value of ${element.symbol} address ${variable.address} ") + throw CancellationException("Error - IfEqual") + } + //compare + if( currentValue.first().toShort() <= value.toShort()){ + //abort and continue + //since the value does not match the condition + return + } + //execute operations + element.randomElements.forEach { subElement -> + processOperationElement(subElement, configuration, memory) + } + } + AddressType.COIL -> { + if ( (element.value != "0" && element.value != "1")) { + logger.error("Invalid value format on IfEqual. Symbol ${element.symbol} is of BOOL type and supports only values 0 or 1 not ${element.value} for comparison") + } + var currentValue = memory.readCoilStatus(variable.address.toInt(), 2) + if(currentValue.isEmpty()){ + logger.error("IfEqual Operation - Unable to get value of ${element.symbol} address ${variable.address} ") + throw CancellationException("Error - IfEqual") + } + //compare + if( currentValue.first() <= value.toBoolean()){ + //abort and continue + //since the value does not match the condition + return + } + //execute operations + element.randomElements.forEach { subElement -> + processOperationElement(subElement, configuration, memory) + } + + } + + else -> { + throw CancellationException("Error - IfEqual") + } + } + } + + } + + + suspend fun ifLess(element: IfLess, configuration: Configuration, memory: PlcMemory) { + logger.info("ifLess symbol ${element.symbol} value ${element.value}") + var value = processValue(element.value) + var variable = configuration.registers.getVarConfiguration(element.symbol) + if (variable == null) { + val envVar = parameters.resolveEnvVar(element.symbol) + if(envVar != null){ + if(envVar.type == "FLOAT32"){ + if(envVar.value.toFloat() >= value.toFloat()){ + //abort and continue + //since the value does not match the condition + return + } + //execute operations + element.randomElements.forEach { subElement -> + processOperationElement(subElement, configuration, memory) + } + }else + if(envVar.type == "INT16" || envVar.type == "BOOL" ){ + if(envVar.value.toInt() >= value.toInt()){ + //abort and continue + //since the value does not match the condition + return + } + //execute operations + element.randomElements.forEach { subElement -> + processOperationElement(subElement, configuration, memory) + } + }else { + logger.error("Symbol ${element.symbol} has invalid datatype during IfEqual execution") + } + + }else { + logger.error("Symbol ${element.symbol} not found during IfEqual execution") + throw CancellationException("Error - IfEqual") + } + } else { + + when (variable.addressType) { + + AddressType.HOLDING_REGISTER -> { + //get the current value + //compare + //execute operations + + if (variable.datatype == "FLOAT32") { + var currentValue = memory.readHoldingRegister(variable.address.toInt(), 2) + if(currentValue.isEmpty()){ + logger.error("Add Operation - Unable to get value of ${element.symbol} address ${variable.address} ") + throw CancellationException("Error - Add") + } + val intValue = (( currentValue[1].toInt() shl 16) or (currentValue[0].toInt() and 0xFFFF)) + val currentFloatValue = java.lang.Float.intBitsToFloat(intValue) + + //compare + if(currentFloatValue >= value.toFloat()){ + //abort and continue + //since the value does not match the condition + return + } + //execute operations + element.randomElements.forEach { subElement -> + processOperationElement(subElement, configuration, memory) + } + + + } else { + var currentValue = memory.readHoldingRegister(variable.address.toInt(), 1) + if(currentValue.isEmpty()){ + logger.error("IfEqual Operation - Unable to get value of ${element.symbol} address ${variable.address} ") + throw CancellationException("Error - IfEqual") + } + //compare + if(currentValue.first().toInt() >= value.toInt()){ + //abort and continue + //since the value does not match the condition + return + } + //execute operations + element.randomElements.forEach { subElement -> + processOperationElement(subElement, configuration, memory) + } + + } + } + AddressType.INPUT_REGISTER -> { + val currentValue = memory.readInputRegister(variable.address.toInt(), 1) + if(currentValue.isEmpty()){ + logger.error("IfEqual Operation - Unable to get value of ${element.symbol} address ${variable.address} ") + throw CancellationException("Error - IfEqual") + } + //compare + if( currentValue.first().toShort() >= value.toShort()){ + //abort and continue + //since the value does not match the condition + return + } + //execute operations + element.randomElements.forEach { subElement -> + processOperationElement(subElement, configuration, memory) + } + } + AddressType.COIL -> { + if ( (element.value != "0" && element.value != "1")) { + logger.error("Invalid value format on IfEqual. Symbol ${element.symbol} is of BOOL type and supports only values 0 or 1 not ${element.value} for comparison") + } + var currentValue = memory.readCoilStatus(variable.address.toInt(), 2) + if(currentValue.isEmpty()){ + logger.error("IfEqual Operation - Unable to get value of ${element.symbol} address ${variable.address} ") + throw CancellationException("Error - IfEqual") + } + //compare + if( currentValue.first() >= value.toBoolean()){ + //abort and continue + //since the value does not match the condition + return + } + //execute operations + element.randomElements.forEach { subElement -> + processOperationElement(subElement, configuration, memory) + } + + } + + else -> { + throw CancellationException("Error - IfEqual") + } + } + } + + + } suspend fun ifEqual(element: IfEqual, configuration: Configuration, memory: PlcMemory) { logger.info("IfEqual symbol ${element.symbol} value ${element.value}") var value = processValue(element.value) diff --git a/src/main/kotlin/operations/DivOperation.kt b/src/main/kotlin/operations/DivOperation.kt new file mode 100644 index 0000000..53be763 --- /dev/null +++ b/src/main/kotlin/operations/DivOperation.kt @@ -0,0 +1,73 @@ +package operations + +import Div +import AddressType +import Configuration +import EnvironmentVariables +import PlcMemory +import org.slf4j.LoggerFactory +import java.util.concurrent.CancellationException + +class DivOperation(private val configuration: Configuration,private val memory: PlcMemory, environmentVariables: EnvironmentVariables +) : BaseOperation(environmentVariables, configuration) { + + companion object { + val logger = LoggerFactory.getLogger("DivOperation") + } + + fun divOperation(element: Div){ + logger.info("Div symbol ${element.symbol} value ${element.value}") + var value = processValue(element.value) + var variable = configuration.registers.getVarConfiguration(element.symbol) + if (variable == null) { + logger.error("Symbol ${element.symbol} not found during Set execution") + throw CancellationException("Error - Div") + } else { + if(variable.addressType == AddressType.COIL || variable.addressType == AddressType.DISCRETE_INPUT){ + logger.error("Symbol ${element.symbol} is of type COIL or DISCRETE_INPUT which is not support by Div operation") + throw CancellationException("Error - Div") + } + + when (variable.addressType) { + + AddressType.HOLDING_REGISTER -> { + //get the current value + //Div + //set back the new value + + if (variable.datatype == "FLOAT32") { + var currentValue = memory.readHoldingRegister(variable.address.toInt(), 2) + if(currentValue.isEmpty()){ + logger.error("Div Operation - Unable to get value of ${element.symbol} address ${variable.address} ") + throw CancellationException("Error - Div") + } + val intValue = (( currentValue[1].toInt() shl 16) or (currentValue[0].toInt() and 0xFFFF)) + val currentFloatValue = java.lang.Float.intBitsToFloat(intValue) + var floatValue = value.toFloat() + floatValue = currentFloatValue / floatValue + setHoldingRegisterFloat32(floatValue, memory, variable) + } else { + var currentValue = memory.readHoldingRegister(variable.address.toInt(), 1) + if(currentValue.isEmpty()){ + logger.error("Div Operation - Unable to get value of ${element.symbol} address ${variable.address} ") + throw CancellationException("Error - Div") + } + var intValue = value.toInt() + intValue = currentValue.first().toInt() / intValue + setHoldingRegisterInt16(memory, variable, intValue.toShort()) + } + } + AddressType.INPUT_REGISTER -> { + val currentValue = memory.readInputRegister(variable.address.toInt(), 1) + val newValue = currentValue.first() / value.toShort() + memory.setInputRegister(variable.address.toInt(),newValue.toShort()) + } + + else -> { + throw CancellationException("Error - Div") + } + } + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/operations/MultOperation.kt b/src/main/kotlin/operations/MultOperation.kt new file mode 100644 index 0000000..70810a8 --- /dev/null +++ b/src/main/kotlin/operations/MultOperation.kt @@ -0,0 +1,73 @@ +package operations + +import Mult +import AddressType +import Configuration +import EnvironmentVariables +import PlcMemory +import org.slf4j.LoggerFactory +import java.util.concurrent.CancellationException + +class MultOperation(private val configuration: Configuration,private val memory: PlcMemory, environmentVariables: EnvironmentVariables +) : BaseOperation(environmentVariables, configuration) { + + companion object { + val logger = LoggerFactory.getLogger("MultOperation") + } + + fun multOperation(element: Mult){ + logger.info("Mult symbol ${element.symbol} value ${element.value}") + var value = processValue(element.value) + var variable = configuration.registers.getVarConfiguration(element.symbol) + if (variable == null) { + logger.error("Symbol ${element.symbol} not found during Set execution") + throw CancellationException("Error - Mult") + } else { + if(variable.addressType == AddressType.COIL || variable.addressType == AddressType.DISCRETE_INPUT){ + logger.error("Symbol ${element.symbol} is of type COIL or DISCRETE_INPUT which is not support by Mult operation") + throw CancellationException("Error - Mult") + } + + when (variable.addressType) { + + AddressType.HOLDING_REGISTER -> { + //get the current value + //Mult + //set back the new value + + if (variable.datatype == "FLOAT32") { + var currentValue = memory.readHoldingRegister(variable.address.toInt(), 2) + if(currentValue.isEmpty()){ + logger.error("Mult Operation - Unable to get value of ${element.symbol} address ${variable.address} ") + throw CancellationException("Error - Mult") + } + val intValue = (( currentValue[1].toInt() shl 16) or (currentValue[0].toInt() and 0xFFFF)) + val currentFloatValue = java.lang.Float.intBitsToFloat(intValue) + var floatValue = value.toFloat() + floatValue *= currentFloatValue + setHoldingRegisterFloat32(floatValue, memory, variable) + } else { + var currentValue = memory.readHoldingRegister(variable.address.toInt(), 1) + if(currentValue.isEmpty()){ + logger.error("Mult Operation - Unable to get value of ${element.symbol} address ${variable.address} ") + throw CancellationException("Error - Mult") + } + var intValue = value.toInt() + intValue *= currentValue.first().toInt() + setHoldingRegisterInt16(memory, variable, intValue.toShort()) + } + } + AddressType.INPUT_REGISTER -> { + val currentValue = memory.readInputRegister(variable.address.toInt(), 1) + val newValue = currentValue.first() * value.toShort() + memory.setInputRegister(variable.address.toInt(),newValue.toShort()) + } + + else -> { + throw CancellationException("Error - Mult") + } + } + } + } + +} \ No newline at end of file From 30705e41d8b29f7b4fcbcc21c52e129a3d1a15af Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 10 Sep 2024 16:04:35 +0000 Subject: [PATCH 2/2] commit badge --- .github/badges/branches.svg | 2 +- .github/badges/jacoco.svg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/badges/branches.svg b/.github/badges/branches.svg index 13099bb..5943641 100644 --- a/.github/badges/branches.svg +++ b/.github/badges/branches.svg @@ -1 +1 @@ -branches11.6% \ No newline at end of file +branches8.2% \ No newline at end of file diff --git a/.github/badges/jacoco.svg b/.github/badges/jacoco.svg index 5c6b8b1..62e0208 100644 --- a/.github/badges/jacoco.svg +++ b/.github/badges/jacoco.svg @@ -1 +1 @@ -coverage19.8% \ No newline at end of file +coverage15% \ No newline at end of file