Skip to content

Commit

Permalink
Feature/modbus optimizer (#1744)
Browse files Browse the repository at this point in the history
Implemented a java version of an optimizer for Modbus, that is able to aggregate multiple items into one request, hereby increasing the read speed signifficantly.
  • Loading branch information
chrisdutz authored Sep 5, 2024
1 parent 3d853b1 commit 079dd60
Show file tree
Hide file tree
Showing 41 changed files with 2,702 additions and 1,833 deletions.
2 changes: 2 additions & 0 deletions RELEASE_NOTES
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ New Features
them back together. It is now possible to read arrays of
almost unlimited size.
- Added auto-discovery to the EIP and KNXNet/IP Drivers.
- Added an Optimizer to the Modbus driver, that improves read
performance of multi-item read requests by more than 10 times.

Incompatible changes
--------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@

int8_t plc4c_test_read_write_crc_int8();
uint8_t plc4c_test_read_write_crc_uint8();
uint8_t plc4c_test_read_write_read_manual_field(plc4c_spi_read_buffer* readBuffer, uint8_t value);
plc4c_return_code plc4c_test_read_write_write_manual_field(plc4c_spi_write_buffer* writeBuffer, uint8_t value);
uint8_t plc4c_test_read_write_read_a_manual_field(plc4c_spi_read_buffer* readBuffer, uint8_t value);
plc4c_return_code plc4c_test_read_write_write_a_manual_field(plc4c_spi_write_buffer* writeBuffer, uint8_t value);

#ifdef __cplusplus
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,11 @@ namespace org.apache.plc4net.drivers.${protocolName?replace("-", "")}.${outputFl
<#if arrayField.loopExpression.contains("curPos")>
curPos = readBuffer.getPos() - startPos;
</#if>
<#if elementTypeReference.isByteBased()>
var ${arrayField.name} = readBuffer.ReadByteArray("", ${helper.toParseExpression(arrayField, helper.intTypeReference, arrayField.loopExpression, parserArguments)});
<#else>
<#-- If this is a count array, we can directly initialize an array with the given size -->
<#if field.isCountArrayField()>
<#if field.isCountArrayField()>
// Count array
List<IPlcValue> ${arrayField.name};
{
Expand All @@ -111,40 +114,41 @@ namespace org.apache.plc4net.drivers.${protocolName?replace("-", "")}.${outputFl
}
}
<#-- In all other cases do we have to work with a list, that is later converted to an array -->
<#else>
<#else>
<#-- For a length array, we read data till the read position of the buffer reaches a given position -->
<#if arrayField.isLengthArrayField()>
<#if arrayField.isLengthArrayField()>
// Length array
var _${arrayField.name}Length = ${helper.toParseExpression(arrayField, helper.intTypeReference, arrayField.loopExpression,parserArguments)};
var ${arrayField.name}EndPos = readBuffer.getPos() + _${arrayField.name}Length;
var value = new List<IPlcValue>();
while(readBuffer.getPos() < ${arrayField.name}EndPos) {
value.Add(
<#if elementTypeReference.isSimpleTypeReference()>
<#if elementTypeReference.isSimpleTypeReference()>
new ${helper.getPlcValueTypeForTypeReference(elementTypeReference)}(${helper.getReadBufferReadMethodCall(elementTypeReference.asSimpleTypeReference().orElseThrow(), "", arrayField)})
<#else>${elementTypeReference.asNonSimpleTypeReference().orElseThrow().name}IO.StaticParse(readBuffer
<#if elementTypeReference.asNonSimpleTypeReference().orElseThrow().params.isPresent()>,
<#list elementTypeReference.asNonSimpleTypeReference().orElseThrow().params.orElseThrow() as parserArgument>
<#else>${elementTypeReference.asNonSimpleTypeReference().orElseThrow().name}IO.StaticParse(readBuffer
<#if elementTypeReference.asNonSimpleTypeReference().orElseThrow().params.isPresent()>,
<#list elementTypeReference.asNonSimpleTypeReference().orElseThrow().params.orElseThrow() as parserArgument>
(${helper.getLanguageTypeNameForTypeReference(helper.getArgumentType(elementTypeReference, parserArgument?index))}) (${helper.toParseExpression(arrayField,elementTypeReference, parserArgument,parserArguments)})
<#sep>, </#sep>
</#list>
</#if>
<#sep>, </#sep>
</#list>
</#if>
)
</#if>
</#if>
);
}
<#-- A terminated array keeps on reading data as long as the termination expression evaluates to false -->
<#elseif arrayField.isTerminatedArrayField()>
<#elseif arrayField.isTerminatedArrayField()>
// Terminated array
var ${arrayField.name} = new List<${helper.getLanguageTypeNameForField(arrayField)}>();
while(!((boolean) (${helper.toParseExpression(arrayField, helper.boolTypeReference, arrayField.loopExpression,parserArguments)}))) {
${arrayField.name}.Add(<#if elementTypeReference.isSimpleTypeReference()>${helper.getReadBufferReadMethodCall(elementTypeReference.asSimpleTypeReference().orElseThrow(), "", arrayField)}<#else>${elementTypeReference.asNonSimpleTypeReference().orElseThrow().name}IO.StaticParse(readBuffer<#if elementTypeReference.asNonSimpleTypeReference().orElseThrow().params.isPresent()>, <#list elementTypeReference.asNonSimpleTypeReference().orElseThrow().params.orElseThrow() as parserArgument>(${helper.getLanguageTypeNameForTypeReference(helper.getArgumentType(elementTypeReference, parserArgument?index))}) (${helper.toParseExpression(arrayField, elementTypeReference, parserArgument, parserArguments)})<#sep>, </#sep></#list></#if>)</#if>);

<#-- After parsing, update the current position, but only if it's needed -->
<#if arrayField.loopExpression.contains("curPos")>
<#if arrayField.loopExpression.contains("curPos")>
curPos = readBuffer.getPos() - startPos;
</#if>
</#if>
}
</#if>
</#if>
</#if>
<#if arrayField.name == "value">
Expand Down Expand Up @@ -218,8 +222,10 @@ namespace org.apache.plc4net.drivers.${protocolName?replace("-", "")}.${outputFl
<#-- In this case we need to wrap each field in a IPlcValue that matches it's natural type -->
var _map = new Dictionary<string, IPlcValue>();
<#list case.fields as field>
<#if field.isArrayField()>
<#if field.isArrayField() && field.asArrayField().orElseThrow().type.elementTypeReference.isByteBased()>
<#assign field=field.asArrayField().orElseThrow()>
_map["${field.name}"] = new PlcRawByteArray(${field.name});
<#elseif field.isArrayField()>
_map["${field.name}"] = new PlcList(${field.name});
<#elseif field.isPropertyField()>
<#assign field=field.asPropertyField().orElseThrow()>
Expand Down Expand Up @@ -321,7 +327,7 @@ namespace org.apache.plc4net.drivers.${protocolName?replace("-", "")}.${outputFl
return new Plc${case.name}(value);
</#switch>
</#if>
}<#sep> else </#sep></#list>
} </#list>
<#if !defaultCaseOutput>
return null;
</#if>
Expand Down Expand Up @@ -422,7 +428,7 @@ namespace org.apache.plc4net.drivers.${protocolName?replace("-", "")}.${outputFl
</#switch>
</#list>
return writeBuffer;
}<#sep> else </#sep></#list>
} </#list>
<#if !defaultCaseOutput>
return null;
</#if>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,15 @@ func ${type.name}ParseWithBuffer(ctx context.Context, readBuffer utils.ReadBuffe
<#assign arrayElementType = arrayField.type.elementTypeReference>

// Array Field (${arrayField.name})
<#if arrayElementType.isByteBased()>
<#if !field.isCountArrayField() && !field.isLengthArrayField()>
return nil, errors.Wrap(_${arrayField.name}Err, "Array fields of type byte only support 'count' and 'length' loop-types.")<@emitImport import="github.com/pkg/errors" />
</#if>
${arrayField.name}, _${arrayField.name}Err := readBuffer.ReadByteArray("${arrayField.name}", int(${helper.toParseExpression(null, null, arrayField.loopExpression, parserArguments)}))<#if arrayField.loopExpression.contains("CEIL")><@emitImport import="math" /></#if>
if _${arrayField.name}Err != nil {
return nil, errors.Wrap(_${arrayField.name}Err, "Error parsing '${arrayField.name}' field")<@emitImport import="github.com/pkg/errors" />
}
<#else>
var ${arrayField.name} []api.PlcValue
for i := 0; i < int(${helper.toParseExpression(null, null, arrayField.loopExpression, parserArguments)}); i++ {
_item, _itemErr := <#if arrayElementType.isSimpleTypeReference()>${helper.getReadBufferReadMethodCall(arrayField.name, arrayElementType.asSimpleTypeReference().orElseThrow(), arrayField)}<#else>Complex type array in data-io parsing currently not implemented</#if>
Expand All @@ -128,6 +137,7 @@ func ${type.name}ParseWithBuffer(ctx context.Context, readBuffer utils.ReadBuffe
}
${arrayField.name} = append(${arrayField.name}, ${helper.getPlcValueTypeForTypeReference(arrayElementType)}(_item))
}
</#if>
<#if arrayField.name == "value">
<#assign valueDefined=true>
</#if>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ class ${type.name}:
<#if discriminatorType.isEnumTypeReference()>
${helper.getLanguageTypeNameForTypeReference(discriminatorType)}.${helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorValueTerm, parserArguments)}
<#else>
${helper.camelCaseToSnakeCase(helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorValueTerm, parserArguments))}
${helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorValueTerm, parserArguments)}
</#if>
<#sep> and </#sep>
</#list>
Expand Down Expand Up @@ -131,7 +131,7 @@ class ${type.name}:
)
</@compress>

<#-- A terminated array keeps on reading data as long as the termination expression evaluates to false -->
<#-- A terminated array keeps on reading data as long as the termination expression evaluates to False -->
<#elseif arrayField.isTerminatedArrayField()>
# Terminated array
${arrayField.name}: ${helper.getNonPrimitiveLanguageTypeNameForField(arrayField)} = new LinkedList<>()
Expand Down Expand Up @@ -377,8 +377,8 @@ class ${type.name}:
for val in values.get_list():
<#if elementTypeReference.isByteBased()>
<@emitImport import="from typing import List" />
value: list[byte] = val.get_raw()
write_buffer.write_byte_array("", value)
value: ${helper.getLanguageTypeNameForField(arrayField)} = val.get_raw()
write_buffer.write_byte_array("", value)
<#else>
value: ${helper.getLanguageTypeNameForTypeReference(elementTypeReference)} = val.get_${helper.camelCaseToSnakeCase(helper.getLanguageTypeNameForTypeReference(elementTypeReference)?cap_first)}()
${helper.getWriteBufferWriteMethodCall(elementTypeReference.asSimpleTypeReference().orElseThrow(), "(" + arrayField.name + ")", arrayField)}
Expand All @@ -387,7 +387,7 @@ class ${type.name}:

<#if case.name == "BOOL">
while write_buffer.getPos() < len(write_buffer.get_data()):
write_buffer.write_bit(false)
write_buffer.write_bit(False)
</#if>
<#break>
<#case "const">
Expand Down
Loading

0 comments on commit 079dd60

Please sign in to comment.