Skip to content

Commit 9698542

Browse files
committed
Added support for CSV operation
1 parent cca7cae commit 9698542

File tree

6 files changed

+124
-28
lines changed

6 files changed

+124
-28
lines changed

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ dependencies {
2727
implementation("com.sun.xml.bind:jaxb-impl:2.3.3")
2828
implementation ("com.github.paulorb:modbus-kt:1.0.11")
2929
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2")
30+
implementation("org.apache.commons:commons-csv:1.10.0")
3031
testImplementation(kotlin("test"))
3132
}
3233

examples/configuration_simulation.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
<register addressType="HOLDING_REGISTER" address="10" datatype="FLOAT32" symbol="TEMPERATURE1">-12.5</register>
88
<register addressType="HOLDING_REGISTER" address="12" datatype="FLOAT32" symbol="TEMPERATURE_MOTOR1">-12.5</register>
99
<register addressType="HOLDING_REGISTER" address="14" datatype="FLOAT32" symbol="TEMPERATURE_MOTOR2">-12.5</register>
10+
<register addressType="HOLDING_REGISTER" address="16" datatype="FLOAT32" symbol="TEMPERATURE_MOTOR3">-12.5</register>
11+
<register addressType="HOLDING_REGISTER" address="18" datatype="FLOAT32" symbol="TEMPERATURE_MOTOR4">-12.5</register>
1012
<register addressType="HOLDING_REGISTER" address="1" datatype="INT16" symbol="RPM_MOTOR1">1</register>
1113
<register addressType="HOLDING_REGISTER" address="2" datatype="INT16" symbol="RPM_MOTOR2">-2</register>
1214
<register addressType="HOLDING_REGISTER" address="3" datatype="INT16" symbol="RPM_MOTOR3">3</register>
@@ -45,6 +47,8 @@
4547
<set symbol="MOTOR_SPEED1">190.5</set>
4648
<linear symbol="TEMPERATURE_MOTOR1" a="3" b="2" startX="0" endX="12" replay="true" step="1.5"/>
4749
<linear symbol="TEMPERATURE_MOTOR2" a="3" b="2" startX="12" endX="0" replay="true" step="1.5"/>
50+
<csv symbol="TEMPERATURE_MOTOR3" file="test_data.csv" column="1" replay="true"/>
51+
<csv symbol="TEMPERATURE_MOTOR4" file="test_data.csv" column="2" step="2" startRow="2" endRow="5" replay="true"/>
4852
<set symbol="RELAYON">1</set>
4953
<set symbol="RELAY_STATUS">1</set>
5054
<set symbol="RPM_MOTOR">400</set>

src/main/kotlin/ConfigurationParser.kt

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@ import javax.xml.bind.annotation.*
77

88
class ConfigurationParser {
99

10-
private var fileName: String = ""
10+
companion object {
11+
var fileName: String = ""
12+
}
13+
14+
1115
fun setFileName(file: String) {
1216
fileName = file
1317
}
1418
private fun load(): Device? {
1519
try {
16-
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)
20+
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)
1721
val unmarshaller = context.createUnmarshaller()
1822
if(fileName.isEmpty()) {
1923
val reader = StringReader(this::class.java.classLoader.getResource("configuration.xml")!!.readText())
@@ -63,6 +67,27 @@ class Simulation(
6367
}
6468

6569

70+
//<csv symbol="TEMPERATURE_MOTOR4" file="test.csv" column="0" step="2" startRow="2" endRow="100" replay="true"/>
71+
@XmlRootElement(name="csv")
72+
data class Csv(
73+
@field:XmlAttribute(required = true)
74+
val symbol: String,
75+
@field:XmlAttribute(required = true)
76+
val file: String,
77+
@field:XmlAttribute(required = true)
78+
val column: Int,
79+
@field:XmlAttribute(required = false)
80+
val step: Int,
81+
@field:XmlAttribute(required = false)
82+
val startRow: Int,
83+
@field:XmlAttribute(required = false)
84+
val endRow: Int,
85+
@field:XmlAttribute(required = false)
86+
val replay: Boolean
87+
){
88+
constructor(): this("", "", 0,1,0,-1, false)
89+
}
90+
6691
// <linear symbol="RPM_MOTOR1" a="5" b="3" startX="500" endX="1000" replay="false" step="3"/>
6792
@XmlRootElement(name="linear")
6893
data class Linear(

src/main/kotlin/PlcMemory.kt

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -28,30 +28,6 @@ class PlcMemory(configurationParser: ConfigurationParser) : IModbusServerEventL
2828
AddressType.INPUT_REGISTER -> inputRegister[register.address.toInt()] = register.value.toShort()
2929
}
3030
}
31-
var simulationElements = device.simulation.randomElements
32-
simulationElements?.forEach {element ->
33-
when(element){
34-
is Set -> {
35-
println("Set symbol ${element.symbol} value ${element.value}")
36-
}
37-
is Random -> {
38-
println("Random symbol ${element.symbol} valueMax ${element.valueMax} valueMin ${element.valueMin}")
39-
}
40-
is Delay -> {
41-
println("Delay value ${element.value}")
42-
}
43-
is Linear -> {
44-
//TODO
45-
}
46-
is Add -> {
47-
//TODO
48-
}
49-
is Sub -> {
50-
//TODO
51-
}
52-
else -> throw UnsupportedOperationException("Unknown simulation step type")
53-
}
54-
}
5531
}
5632

5733
//0x

src/main/kotlin/PlcSimulation.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ class PlcSimulation(
88
coroutineScope: CoroutineScope
99
) {
1010
val linearOperations = LinearOperation()
11+
val csvOperations = CsvOperation()
1112
init {
1213
var simulationConfiguration = configurationParser.getConfiguredDevice().simulation
1314
var configuration = configurationParser.getConfiguredDevice().configuration
@@ -42,6 +43,10 @@ class PlcSimulation(
4243
subOperation(element, configuration, memory)
4344
}
4445

46+
is Csv -> {
47+
csvOperations.process(element, configuration, memory)
48+
}
49+
4550
else -> throw UnsupportedOperationException("Unknown simulation step type")
4651
}
4752
}
@@ -52,8 +57,8 @@ class PlcSimulation(
5257
delay(simulationConfiguration.plcScanTime - elapsedTime)
5358
}
5459
}
55-
} catch (e: CancellationException) {
56-
println("Job: Caught CancellationException - ${e.message}")
60+
} catch (e: Exception) {
61+
println("Exception - ${e.message}")
5762
} finally {
5863
println("Job: Finally block, cleaning up resources")
5964
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package operations
2+
3+
import Configuration
4+
import Csv
5+
import PlcMemory
6+
import org.apache.commons.csv.CSVFormat
7+
import org.apache.commons.csv.CSVParser
8+
import java.io.FileReader
9+
import java.nio.file.Paths
10+
import java.util.concurrent.CancellationException
11+
12+
data class CsvOperationInfo(var position: Int, var csvColumn: List<String>)
13+
class CsvOperation {
14+
private var csvVariables: MutableMap<String, CsvOperationInfo> = mutableMapOf<String, CsvOperationInfo>()
15+
16+
@Throws(InternalError::class)
17+
private fun parseCsv(csvFileName: String, column: Int) : List<String> {
18+
val columnValues = mutableListOf<String>()
19+
try {
20+
var newPath = Paths.get(Paths.get(ConfigurationParser.fileName).parent.toString(), csvFileName)
21+
FileReader( newPath.toString()).use { fileReader ->
22+
val csvParser = CSVParser(fileReader, CSVFormat.DEFAULT)
23+
for (csvRecord in csvParser) {
24+
columnValues.add( csvRecord.get(column))
25+
}
26+
}
27+
} catch (e: Exception) {
28+
throw InternalError("Error reading csv file")
29+
}
30+
return columnValues
31+
}
32+
33+
34+
private fun getNextValue(csv: Csv): String {
35+
if(!csvVariables.containsKey(csv.symbol)){
36+
csvVariables[csv.symbol] = CsvOperationInfo(csv.startRow,parseCsv(csv.file,csv.column))
37+
}
38+
val currentValue = csvVariables[csv.symbol]!!.csvColumn[csvVariables[csv.symbol]!!.position]
39+
csvVariables[csv.symbol]!!.position++
40+
if(csvVariables[csv.symbol]!!.position > csvVariables[csv.symbol]!!.csvColumn.size -1 ||
41+
(csv.endRow != -1 && csvVariables[csv.symbol]!!.position > csv.endRow)
42+
){
43+
if(csv.replay){
44+
csvVariables[csv.symbol]!!.position = csv.startRow
45+
}else{
46+
csvVariables[csv.symbol]!!.position--
47+
}
48+
}
49+
return currentValue
50+
}
51+
fun process(element: Csv, configuration: Configuration, memory: PlcMemory) {
52+
var nextValue = getNextValue(element)
53+
var variable = configuration.registers.getVarConfiguration(element.symbol)
54+
if (variable == null) {
55+
println("ERROR: Symbol ${element.symbol} not found during CSV execution")
56+
throw CancellationException("Error - CSV")
57+
} else {
58+
when (variable.addressType) {
59+
60+
AddressType.HOLDING_REGISTER -> {
61+
//get the current value
62+
//add
63+
//set back the new value
64+
65+
if (variable.datatype == "FLOAT32") {
66+
67+
setHoldingRegisterFloat32(nextValue.toFloat(), memory, variable)
68+
} else {
69+
70+
setHoldingRegisterInt16(memory, variable, nextValue.toFloat().toInt().toShort())
71+
}
72+
}
73+
74+
AddressType.INPUT_REGISTER -> {
75+
memory.setInputRegister(variable.address.toInt(), nextValue.toFloat().toInt().toShort())
76+
}
77+
78+
else -> {
79+
throw CancellationException("Error - Linear")
80+
}
81+
}
82+
}
83+
}
84+
85+
}

0 commit comments

Comments
 (0)