#include "control_logic_model.h" #include namespace { void setError(std::string *error, const std::string &message) { if (error != nullptr) { *error = message; } } bool validateConfig(const ContactNodeConfig &config, std::string *error) { if (!config.address.isValid() || config.address.area() != RegisterArea::M) { setError(error, "contact node requires a valid M address"); return false; } if (config.mode != ContactMode::NormallyOpen && config.mode != ContactMode::NormallyClosed) { setError(error, "contact node has an unsupported mode"); return false; } return true; } bool validateConfig(const CoilNodeConfig &config, std::string *error) { if (!config.address.isValid() || config.address.area() != RegisterArea::M) { setError(error, "coil node requires a valid M address"); return false; } if (config.mode != CoilMode::Normal && config.mode != CoilMode::Set && config.mode != CoilMode::Reset) { setError(error, "coil node has an unsupported mode"); return false; } return true; } bool validateConfig(const CompareNodeConfig &config, std::string *error) { if (!config.address.isValid() || config.address.area() != RegisterArea::D) { setError(error, "comparison node requires a valid D address"); return false; } if (config.comparison != ComparisonOperator::Equal && config.comparison != ComparisonOperator::NotEqual && config.comparison != ComparisonOperator::LessThan && config.comparison != ComparisonOperator::LessThanOrEqual && config.comparison != ComparisonOperator::GreaterThan && config.comparison != ComparisonOperator::GreaterThanOrEqual) { setError(error, "comparison node has an unsupported operator"); return false; } return true; } template bool hasDuplicateId(const std::vector &items) { for (auto current = items.cbegin(); current != items.cend(); ++current) { if (std::find_if( current + 1, items.cend(), [¤t](const TItem &candidate) { return candidate.id == current->id; }) != items.cend()) { return true; } } return false; } } // namespace bool LogicNode::validate(std::string *error) const { if (id.empty()) { setError(error, "logic node id must not be empty"); return false; } return std::visit( [error](const auto &config) { return validateConfig(config, error); }, config); } bool LogicNode::isConfigured() const { return configured; } bool LogicNode::isCondition() const { return !std::holds_alternative(config); } bool LogicNode::isOutput() const { return std::holds_alternative(config); } bool LadderStage::validate(std::string *error) const { if (id.empty() || branches.empty()) { setError(error, "ladder stage id and branches must not be empty"); return false; } if (hasDuplicateId(branches)) { setError(error, "parallel branch node ids must be unique"); return false; } for (const LogicNode &node : branches) { if (!node.validate(error)) { return false; } if (!node.isCondition()) { setError(error, "ladder stage may contain condition nodes only"); return false; } } return true; } bool LadderStage::validateForRunning(std::string *error) const { if (!validate(error)) { return false; } for (const LogicNode &node : branches) { if (!node.isConfigured()) { setError(error, "ladder condition " + node.id + " is not configured"); return false; } } return true; } bool LadderRung::validate(std::string *error) const { return validateStructure(error); } bool LadderRung::validateForRunning(std::string *error) const { if (!validateStructure(error)) { return false; } if (stages.empty() && !output.has_value()) { return true; } if (stages.empty() || !output.has_value()) { setError(error, "incomplete ladder network requires conditions and an output coil"); return false; } for (const LadderStage &stage : stages) { if (!stage.validateForRunning(error)) { return false; } } if (!output->isConfigured()) { setError(error, "output coil " + output->id + " is not configured"); return false; } return true; } bool LadderRung::validateStructure(std::string *error) const { if (id.empty() || name.empty()) { setError(error, "ladder rung id and name must not be empty"); return false; } if (hasDuplicateId(stages)) { setError(error, "ladder stage ids must be unique within a rung"); return false; } std::vector node_ids; for (const LadderStage &stage : stages) { if (!stage.validate(error)) { return false; } for (const LogicNode &node : stage.branches) { node_ids.push_back(node.id); } } if (output.has_value()) { if (!output->validate(error)) { return false; } if (!output->isOutput()) { setError(error, "ladder rung output must be a coil node"); return false; } node_ids.push_back(output->id); } std::sort(node_ids.begin(), node_ids.end()); if (std::adjacent_find(node_ids.cbegin(), node_ids.cend()) != node_ids.cend()) { setError(error, "logic node ids must be unique within a rung"); return false; } return true; } bool ControlLogic::validate(std::string *error) const { if (!validateStructure(error)) { return false; } for (const LadderRung &rung : rungs) { if (!rung.validate(error)) { return false; } } return true; } bool ControlLogic::validateStructure(std::string *error) const { if (id.empty() || name.empty()) { setError(error, "control logic id and name must not be empty"); return false; } if (hasDuplicateId(rungs)) { setError(error, "ladder rung ids must be unique within a logic"); return false; } std::vector node_ids; for (const LadderRung &rung : rungs) { if (!rung.validateStructure(error)) { return false; } for (const LadderStage &stage : rung.stages) { for (const LogicNode &node : stage.branches) { node_ids.push_back(node.id); } } if (rung.output.has_value()) { node_ids.push_back(rung.output->id); } } std::sort(node_ids.begin(), node_ids.end()); if (std::adjacent_find(node_ids.cbegin(), node_ids.cend()) != node_ids.cend()) { setError(error, "logic node ids must be unique within a logic"); return false; } return true; } bool ControlLogic::validateForRunning(std::string *error) const { if (!validateStructure(error)) { return false; } for (const LadderRung &rung : rungs) { if (!rung.validateForRunning(error)) { return false; } } return true; }