|
|
|
@@ -231,17 +231,62 @@ void testSetResetAndDisabledLogic() |
|
|
|
require(readBit(repository, 5), "disabled logic must not change outputs"); |
|
|
|
} |
|
|
|
|
|
|
|
void testConflictingCoilsAreRejected() |
|
|
|
void testSetResetPairOnSameAddress() |
|
|
|
{ |
|
|
|
VirtualRegisterRepository repository; |
|
|
|
SoftwareLogicExecutor executor; |
|
|
|
const ControlLogic program = logic({ |
|
|
|
rung("low-level-rung", |
|
|
|
{{comparison("low-level", 0, |
|
|
|
ComparisonOperator::LessThanOrEqual, 30)}}, |
|
|
|
coil("pump-set", 20, CoilMode::Set)), |
|
|
|
rung("high-level-rung", |
|
|
|
{{comparison("high-level", 0, |
|
|
|
ComparisonOperator::GreaterThanOrEqual, 80)}}, |
|
|
|
coil("pump-reset", 20, CoilMode::Reset))}); |
|
|
|
|
|
|
|
writeWord(repository, 0, 20); |
|
|
|
require(executor.executeScan({program}, repository).succeeded, |
|
|
|
"set/reset pair scan must succeed at the low limit"); |
|
|
|
require(readBit(repository, 20), "low level must latch the pump on"); |
|
|
|
|
|
|
|
writeWord(repository, 0, 50); |
|
|
|
require(executor.executeScan({program}, repository).succeeded, |
|
|
|
"set/reset pair scan must succeed inside the deadband"); |
|
|
|
require(readBit(repository, 20), "deadband must retain the latched on state"); |
|
|
|
|
|
|
|
writeWord(repository, 0, 90); |
|
|
|
require(executor.executeScan({program}, repository).succeeded, |
|
|
|
"set/reset pair scan must succeed at the high limit"); |
|
|
|
require(!readBit(repository, 20), "high level must reset the pump"); |
|
|
|
|
|
|
|
writeWord(repository, 0, 50); |
|
|
|
require(executor.executeScan({program}, repository).succeeded, |
|
|
|
"set/reset pair rescan must succeed inside the deadband"); |
|
|
|
require(!readBit(repository, 20), "deadband must retain the reset state"); |
|
|
|
} |
|
|
|
|
|
|
|
void testConflictingCoilsAreRejected() |
|
|
|
{ |
|
|
|
SoftwareLogicExecutor executor; |
|
|
|
const ControlLogic normal_and_set = logic({ |
|
|
|
rung("normal-rung", {{contact("normal-input", 0)}}, coil("normal", 8)), |
|
|
|
rung("set-rung", {{contact("set-input", 1)}}, coil("set", 8, CoilMode::Set))}); |
|
|
|
const LogicScanResult result = executor.validate({program}); |
|
|
|
require(!result.succeeded && result.error == LogicScanError::ConflictingOutput, |
|
|
|
"mixed output modes for one address must be rejected"); |
|
|
|
require(result.rungId == "set-rung" && result.nodeId == "set", |
|
|
|
const LogicScanResult set_result = executor.validate({normal_and_set}); |
|
|
|
require(!set_result.succeeded |
|
|
|
&& set_result.error == LogicScanError::ConflictingOutput, |
|
|
|
"normal and set coils for one address must be rejected"); |
|
|
|
require(set_result.rungId == "set-rung" && set_result.nodeId == "set", |
|
|
|
"conflict error must identify the offending rung and node"); |
|
|
|
|
|
|
|
const ControlLogic reset_and_normal = logic({ |
|
|
|
rung("reset-rung", {{contact("reset-input", 0)}}, |
|
|
|
coil("reset", 9, CoilMode::Reset)), |
|
|
|
rung("normal-rung", {{contact("normal-input", 1)}}, coil("normal", 9))}); |
|
|
|
const LogicScanResult reset_result = executor.validate({reset_and_normal}); |
|
|
|
require(!reset_result.succeeded |
|
|
|
&& reset_result.error == LogicScanError::ConflictingOutput, |
|
|
|
"reset and normal coils for one address must be rejected"); |
|
|
|
} |
|
|
|
|
|
|
|
void testHmiSimulationClosedLoop() |
|
|
|
@@ -348,6 +393,7 @@ int main(int argc, char *argv[]) |
|
|
|
testNestedSeriesParallelExpression(); |
|
|
|
testAllComparisons(); |
|
|
|
testSetResetAndDisabledLogic(); |
|
|
|
testSetResetPairOnSameAddress(); |
|
|
|
testConflictingCoilsAreRejected(); |
|
|
|
testHmiSimulationClosedLoop(); |
|
|
|
testSimulationLifecycleSnapshotAndFault(); |
|
|
|
|