#include "software_logic_executor.h" #include #include namespace { LogicScanResult success() { return {true, LogicScanError::None, {}, {}, {}, {}}; } LogicScanResult failure( LogicScanError error, const std::string &message, const std::string &logic_id = {}, const std::string &rung_id = {}, const std::string &node_id = {}) { return {false, error, message, logic_id, rung_id, node_id}; } bool compareWord( std::int16_t actual, ComparisonOperator comparison, std::int16_t expected) { switch (comparison) { case ComparisonOperator::Equal: { return actual == expected; } case ComparisonOperator::NotEqual: { return actual != expected; } case ComparisonOperator::LessThan: { return actual < expected; } case ComparisonOperator::LessThanOrEqual: { return actual <= expected; } case ComparisonOperator::GreaterThan: { return actual > expected; } case ComparisonOperator::GreaterThanOrEqual: { return actual >= expected; } default: { return false; } } } } // namespace LogicScanResult SoftwareLogicExecutor::validate( const std::vector &logics) const { std::map output_modes; for (const ControlLogic &logic : logics) { std::string validation_error; if (!logic.validateForRunning(&validation_error)) { return failure( LogicScanError::InvalidLogic, validation_error, logic.id); } if (!logic.enabled) { continue; } for (const LadderRung &rung : logic.rungs) { if (!rung.output.has_value()) { continue; } const auto *output = std::get_if(&rung.output->config); if (output == nullptr) { return failure( LogicScanError::InvalidLogic, "ladder output is not a coil", logic.id, rung.id, rung.output->id); } const auto existing = output_modes.find(output->address.index()); if (existing != output_modes.end() && existing->second != output->mode) { return failure( LogicScanError::ConflictingOutput, "mixed coil modes target " + output->address.toString(), logic.id, rung.id, rung.output->id); } output_modes[output->address.index()] = output->mode; } } return success(); } LogicScanResult SoftwareLogicExecutor::executeScan( const std::vector &logics, RegisterRepository &repository) const { const LogicScanResult validation = validate(logics); if (!validation.succeeded) { return validation; } for (const ControlLogic &logic : logics) { if (!logic.enabled) { continue; } for (const LadderRung &rung : logic.rungs) { if (rung.stages.empty() && !rung.output.has_value()) { continue; } bool rung_value = true; for (const LadderStage &stage : rung.stages) { bool stage_value = false; for (const LogicNode &node : stage.branches) { bool condition_value = false; LogicScanResult result = evaluateCondition( node, repository, &condition_value); if (!result.succeeded) { result.logicId = logic.id; result.rungId = rung.id; return result; } stage_value = stage_value || condition_value; } rung_value = rung_value && stage_value; } LogicScanResult result = writeOutput( *rung.output, rung_value, repository); if (!result.succeeded) { result.logicId = logic.id; result.rungId = rung.id; return result; } } } return success(); } LogicScanResult SoftwareLogicExecutor::evaluateCondition( const LogicNode &node, RegisterRepository &repository, bool *value) const { if (value == nullptr) { return failure( LogicScanError::InvalidLogic, "condition result target is missing", {}, {}, node.id); } return std::visit( [&repository, value, &node](const auto &config) -> LogicScanResult { using Config = std::decay_t; if constexpr (std::is_same_v) { const BitReadResult read = repository.readBit(config.address); if (!read.succeeded) { return failure( LogicScanError::RegisterReadFailed, "failed to read " + config.address.toString(), {}, {}, node.id); } *value = config.mode == ContactMode::NormallyOpen ? read.value : !read.value; return success(); } else if constexpr (std::is_same_v) { const WordReadResult read = repository.readWord(config.address); if (!read.succeeded) { return failure( LogicScanError::RegisterReadFailed, "failed to read " + config.address.toString(), {}, {}, node.id); } *value = compareWord(read.value, config.comparison, config.value); return success(); } else { return failure( LogicScanError::InvalidLogic, "condition node contains an output coil", {}, {}, node.id); } }, node.config); } LogicScanResult SoftwareLogicExecutor::writeOutput( const LogicNode &node, bool rung_value, RegisterRepository &repository) const { const auto *config = std::get_if(&node.config); if (config == nullptr) { return failure( LogicScanError::InvalidLogic, "ladder output is not a coil", {}, {}, node.id); } bool should_write = true; bool output_value = rung_value; switch (config->mode) { case CoilMode::Normal: { break; } case CoilMode::Set: { should_write = rung_value; output_value = true; break; } case CoilMode::Reset: { should_write = rung_value; output_value = false; break; } default: { return failure( LogicScanError::InvalidLogic, "unsupported coil mode", {}, {}, node.id); } } if (!should_write) { return success(); } const RegisterWriteResult write = repository.writeBit( config->address, output_value); if (!write.succeeded) { return failure( LogicScanError::RegisterWriteFailed, "failed to write " + config->address.toString(), {}, {}, node.id); } return success(); }