Pārlūkot izejas kodu

feat: 增加统一数据点管理

main
suyu pirms 1 mēnesi
vecāks
revīzija
c9ae533440
13 mainītis faili ar 446 papildinājumiem un 0 dzēšanām
  1. +4
    -0
      app/integrated_platform.pro
  2. +37
    -0
      app/src/domain/data_point_model.cpp
  3. +14
    -0
      app/src/domain/data_point_model.h
  4. +23
    -0
      app/src/domain/project_model.cpp
  5. +3
    -0
      app/src/domain/project_model.h
  6. +170
    -0
      app/src/services/data_point_service.cpp
  7. +51
    -0
      app/src/services/data_point_service.h
  8. +107
    -0
      app/tests/data_point_service_tests.cpp
  9. +29
    -0
      app/tests/data_point_service_tests.pro
  10. +2
    -0
      app/tests/domain_tests.pro
  11. +2
    -0
      app/tests/hmi_editor_service_tests.pro
  12. +2
    -0
      app/tests/logic_editor_service_tests.pro
  13. +2
    -0
      app/tests/project_management_tests.pro

+ 4
- 0
app/integrated_platform.pro Parādīt failu

@@ -19,11 +19,13 @@ SOURCES += \
src/ui/main_window.cpp \
src/domain/register_address.cpp \
src/domain/register_repository.cpp \
src/domain/data_point_model.cpp \
src/domain/hmi_model.cpp \
src/domain/control_logic_model.cpp \
src/domain/project_model.cpp \
src/domain/runtime_state.cpp \
src/services/project_service.cpp \
src/services/data_point_service.cpp \
src/services/hmi_editor_service.cpp \
src/services/logic_editor_service.cpp \
src/services/hmi_runtime_service.cpp \
@@ -38,12 +40,14 @@ HEADERS += \
src/ui/main_window.h \
src/domain/register_address.h \
src/domain/register_repository.h \
src/domain/data_point_model.h \
src/domain/hmi_model.h \
src/domain/control_logic_model.h \
src/domain/project_model.h \
src/domain/runtime_state.h \
src/domain/project_storage.h \
src/services/project_service.h \
src/services/data_point_service.h \
src/services/hmi_editor_service.h \
src/services/logic_editor_service.h \
src/services/hmi_runtime_service.h \


+ 37
- 0
app/src/domain/data_point_model.cpp Parādīt failu

@@ -0,0 +1,37 @@
#include "data_point_model.h"

#include <algorithm>
#include <cctype>

namespace {

bool isBlank(const std::string &value)
{
return value.empty()
|| std::all_of(
value.cbegin(), value.cend(),
[](unsigned char character) { return std::isspace(character) != 0; });
}

} // namespace

bool DataPoint::validate(std::string *error) const
{
if (!address.isValid())
{
if (error != nullptr)
{
*error = "data point requires a valid M or D address";
}
return false;
}
if (isBlank(name))
{
if (error != nullptr)
{
*error = "data point name must not be empty";
}
return false;
}
return true;
}

+ 14
- 0
app/src/domain/data_point_model.h Parādīt failu

@@ -0,0 +1,14 @@
#pragma once

#include "register_address.h"

#include <string>

struct DataPoint
{
RegisterAddress address;
std::string name;
std::string comment;

bool validate(std::string *error = nullptr) const;
};

+ 23
- 0
app/src/domain/project_model.cpp Parādīt failu

@@ -52,6 +52,29 @@ bool Project::validate(std::string *error) const
setError(error, "control logic ids must be unique within a project");
return false;
}
for (auto current = dataPoints.cbegin(); current != dataPoints.cend(); ++current)
{
if (!current->validate(error))
{
return false;
}
const auto duplicate = std::find_if(
current + 1,
dataPoints.cend(),
[&current](const DataPoint &candidate)
{
return candidate.address == current->address
|| candidate.name == current->name;
});
if (duplicate != dataPoints.cend())
{
if (error != nullptr)
{
*error = "data point addresses and names must be unique";
}
return false;
}
}
for (const HmiPage &page : hmiPages)
{
// 工程聚合校验会向下委托页面和控件的完整规则


+ 3
- 0
app/src/domain/project_model.h Parādīt failu

@@ -1,6 +1,7 @@
#pragma once

#include "control_logic_model.h"
#include "data_point_model.h"
#include "hmi_model.h"

#include <string>
@@ -26,6 +27,8 @@ struct Project
std::vector<HmiPage> hmiPages;
// 工程包含的控制逻辑集合
std::vector<ControlLogic> controlLogics;
// M/D 地址的工程级名称和注释定义
std::vector<DataPoint> dataPoints;

// 校验工程配置并通过 error 返回失败原因
bool validate(std::string *error = nullptr) const;


+ 170
- 0
app/src/services/data_point_service.cpp Parādīt failu

@@ -0,0 +1,170 @@
#include "data_point_service.h"

#include "project_service.h"

#include <algorithm>

namespace {

bool nodeUsesAddress(const LogicNode &node, const RegisterAddress &address)
{
return std::visit(
[&address](const auto &config) { return config.address == address; },
node.config);
}

} // namespace

DataPointService::DataPointService(ProjectService &project_service)
: project_service_(project_service)
{
}

const std::vector<DataPoint> &DataPointService::dataPoints() const
{
return project_service_.project().dataPoints;
}

const DataPoint *DataPointService::find(const RegisterAddress &address) const
{
const auto &points = dataPoints();
const auto found = std::find_if(
points.cbegin(), points.cend(),
[&address](const DataPoint &point) { return point.address == address; });
return found == points.cend() ? nullptr : &*found;
}

DataPointResult DataPointService::add(const DataPoint &data_point)
{
const DataPointResult validation = validateUnique(data_point, nullptr);
if (!validation.succeeded)
{
return validation;
}
project_service_.editProject().dataPoints.push_back(data_point);
return {true, DataPointError::None, {}};
}

DataPointResult DataPointService::update(
const RegisterAddress &old_address, const DataPoint &data_point)
{
const auto existing = std::find_if(
dataPoints().cbegin(), dataPoints().cend(),
[&old_address](const DataPoint &candidate)
{
return candidate.address == old_address;
});
if (existing == dataPoints().cend())
{
return {false, DataPointError::NotFound, "data point was not found"};
}
const DataPointResult validation = validateUnique(data_point, &old_address);
if (!validation.succeeded)
{
return validation;
}
if (data_point.address != old_address && !references(old_address).empty())
{
return {false, DataPointError::AddressInUse,
"referenced data point address cannot be changed"};
}
Project &project = project_service_.editProject();
const auto point = std::find_if(
project.dataPoints.begin(), project.dataPoints.end(),
[&old_address](const DataPoint &candidate)
{
return candidate.address == old_address;
});
*point = data_point;
return {true, DataPointError::None, {}};
}

DataPointResult DataPointService::remove(const RegisterAddress &address)
{
if (find(address) == nullptr)
{
return {false, DataPointError::NotFound, "data point was not found"};
}
if (!references(address).empty())
{
return {false, DataPointError::AddressInUse,
"data point is still referenced by HMI or ladder logic"};
}
Project &project = project_service_.editProject();
project.dataPoints.erase(
std::remove_if(
project.dataPoints.begin(), project.dataPoints.end(),
[&address](const DataPoint &point) { return point.address == address; }),
project.dataPoints.end());
return {true, DataPointError::None, {}};
}

std::vector<DataPointReference> DataPointService::references(
const RegisterAddress &address) const
{
std::vector<DataPointReference> result;
const Project &project = project_service_.project();
for (const HmiPage &page : project.hmiPages)
{
for (const HmiControl &control : page.controls)
{
if (control.binding.has_value() && *control.binding == address)
{
result.push_back({"HMI/" + page.name + '/' + control.id});
}
}
}
for (const ControlLogic &logic : project.controlLogics)
{
for (const LadderRung &rung : logic.rungs)
{
if (rung.condition.has_value())
{
std::vector<const LogicNode *> nodes;
collectConditionNodes(*rung.condition, &nodes);
for (const LogicNode *node : nodes)
{
if (nodeUsesAddress(*node, address))
{
result.push_back({"LAD/" + logic.name + '/' + rung.name + '/' + node->id});
}
}
}
if (rung.output.has_value() && nodeUsesAddress(*rung.output, address))
{
result.push_back({"LAD/" + logic.name + '/' + rung.name + '/'
+ rung.output->id});
}
}
}
return result;
}

DataPointResult DataPointService::validateUnique(
const DataPoint &data_point,
const RegisterAddress *ignored_address) const
{
std::string error;
if (!data_point.validate(&error))
{
return {false, DataPointError::InvalidDataPoint, error};
}
for (const DataPoint &existing : dataPoints())
{
if (ignored_address != nullptr && existing.address == *ignored_address)
{
continue;
}
if (existing.address == data_point.address)
{
return {false, DataPointError::DuplicateAddress,
"data point address already exists"};
}
if (existing.name == data_point.name)
{
return {false, DataPointError::DuplicateName,
"data point name already exists"};
}
}
return {true, DataPointError::None, {}};
}

+ 51
- 0
app/src/services/data_point_service.h Parādīt failu

@@ -0,0 +1,51 @@
#pragma once

#include "domain/data_point_model.h"

#include <string>
#include <vector>

class ProjectService;

enum class DataPointError
{
None,
InvalidDataPoint,
DuplicateAddress,
DuplicateName,
NotFound,
AddressInUse
};

struct DataPointResult
{
bool succeeded = false;
DataPointError error = DataPointError::None;
std::string message;
};

struct DataPointReference
{
std::string location;
};

class DataPointService
{
public:
explicit DataPointService(ProjectService &project_service);

const std::vector<DataPoint> &dataPoints() const;
const DataPoint *find(const RegisterAddress &address) const;
DataPointResult add(const DataPoint &data_point);
DataPointResult update(
const RegisterAddress &old_address, const DataPoint &data_point);
DataPointResult remove(const RegisterAddress &address);
std::vector<DataPointReference> references(const RegisterAddress &address) const;

private:
DataPointResult validateUnique(
const DataPoint &data_point,
const RegisterAddress *ignored_address) const;

ProjectService &project_service_;
};

+ 107
- 0
app/tests/data_point_service_tests.cpp Parādīt failu

@@ -0,0 +1,107 @@
#include "domain/project_storage.h"
#include "services/data_point_service.h"
#include "services/project_service.h"

#include <iostream>
#include <stdexcept>

namespace {

class TestProjectStorage final : public ProjectStorage
{
public:
ProjectSaveResult save(const Project &, const std::string &) override
{
return {true, ProjectStorageError::None, {}};
}

ProjectLoadResult load(const std::string &) override
{
return {false, {}, ProjectStorageError::FileReadFailed, {}};
}
};

void require(bool condition, const std::string &message)
{
if (!condition)
{
throw std::runtime_error(message);
}
}

void testUniqueDataPointsAndReferences()
{
TestProjectStorage storage;
ProjectService project_service(storage);
DataPointService service(project_service);
const RegisterAddress m0{RegisterArea::M, 0};
const RegisterAddress m1{RegisterArea::M, 1};
const RegisterAddress d0{RegisterArea::D, 0};

require(service.add({m0, "StartCommand", "启动命令"}).succeeded,
"a valid data point must be added");
require(service.add({d0, "Temperature", "温度"}).succeeded,
"M and D data points must coexist");
require(service.add({m0, "DuplicateAddress", {}}).error
== DataPointError::DuplicateAddress,
"duplicate data point addresses must be rejected");
require(service.add({m1, "StartCommand", {}}).error
== DataPointError::DuplicateName,
"duplicate data point names must be rejected");

HmiControl button;
button.id = "start-button";
button.type = HmiControlType::Button;
button.bounds = {0, 0, 100, 40};
button.text = "启动";
button.binding = m0;
HmiPage page;
page.id = "main-page";
page.name = "主画面";
page.controls.push_back(button);
project_service.editProject().hmiPages.push_back(page);

LogicNode contact;
contact.id = "start-contact";
contact.config = ContactNodeConfig{m0, ContactMode::NormallyOpen};
LadderRung rung;
rung.id = "rung-1";
rung.name = "网络 1";
rung.condition = ConditionExpression::fromNode(contact);
ControlLogic logic;
logic.id = "logic-1";
logic.name = "控制逻辑 1";
logic.rungs.push_back(rung);
project_service.editProject().controlLogics.push_back(logic);

const auto references = service.references(m0);
require(references.size() == 2U,
"data point references must include HMI and ladder locations");
require(service.remove(m0).error == DataPointError::AddressInUse,
"referenced data points must not be deleted");
require(service.update(m0, {m1, "StartCommand", "启动命令"}).error
== DataPointError::AddressInUse,
"referenced data point addresses must not change");
require(service.update(m0, {m0, "Start", "启动命令"}).succeeded,
"referenced data point names and comments may change");
require(service.remove(RegisterAddress{RegisterArea::D, 99}).error
== DataPointError::NotFound,
"removing an unknown data point must report not found");
}

} // namespace

int main()
{
try
{
testUniqueDataPointsAndReferences();
}
catch (const std::exception &error)
{
std::cerr << "data point service tests failed: " << error.what() << '\n';
return 1;
}
std::cout << "data point service tests passed\n";
return 0;
}

+ 29
- 0
app/tests/data_point_service_tests.pro Parādīt failu

@@ -0,0 +1,29 @@
TEMPLATE = app
TARGET = data_point_service_tests

CONFIG += console c++17 testcase warn_on
CONFIG -= app_bundle qt

INCLUDEPATH += ../src

SOURCES += \
data_point_service_tests.cpp \
../src/domain/register_address.cpp \
../src/domain/register_repository.cpp \
../src/domain/data_point_model.cpp \
../src/domain/hmi_model.cpp \
../src/domain/control_logic_model.cpp \
../src/domain/project_model.cpp \
../src/services/project_service.cpp \
../src/services/data_point_service.cpp

HEADERS += \
../src/domain/register_address.h \
../src/domain/register_repository.h \
../src/domain/data_point_model.h \
../src/domain/hmi_model.h \
../src/domain/control_logic_model.h \
../src/domain/project_model.h \
../src/domain/project_storage.h \
../src/services/project_service.h \
../src/services/data_point_service.h

+ 2
- 0
app/tests/domain_tests.pro Parādīt failu

@@ -10,6 +10,7 @@ SOURCES += \
domain_tests.cpp \
../src/domain/register_address.cpp \
../src/domain/register_repository.cpp \
../src/domain/data_point_model.cpp \
../src/domain/hmi_model.cpp \
../src/domain/control_logic_model.cpp \
../src/domain/project_model.cpp \
@@ -18,6 +19,7 @@ SOURCES += \
HEADERS += \
../src/domain/register_address.h \
../src/domain/register_repository.h \
../src/domain/data_point_model.h \
../src/domain/hmi_model.h \
../src/domain/control_logic_model.h \
../src/domain/project_model.h \


+ 2
- 0
app/tests/hmi_editor_service_tests.pro Parādīt failu

@@ -10,6 +10,7 @@ SOURCES += \
hmi_editor_service_tests.cpp \
../src/domain/register_address.cpp \
../src/domain/register_repository.cpp \
../src/domain/data_point_model.cpp \
../src/domain/hmi_model.cpp \
../src/domain/control_logic_model.cpp \
../src/domain/project_model.cpp \
@@ -20,6 +21,7 @@ SOURCES += \
HEADERS += \
../src/domain/register_address.h \
../src/domain/register_repository.h \
../src/domain/data_point_model.h \
../src/domain/hmi_model.h \
../src/domain/control_logic_model.h \
../src/domain/project_model.h \


+ 2
- 0
app/tests/logic_editor_service_tests.pro Parādīt failu

@@ -9,6 +9,7 @@ INCLUDEPATH += ../src
SOURCES += \
logic_editor_service_tests.cpp \
../src/domain/register_address.cpp \
../src/domain/data_point_model.cpp \
../src/domain/control_logic_model.cpp \
../src/domain/project_model.cpp \
../src/domain/hmi_model.cpp \
@@ -18,6 +19,7 @@ SOURCES += \

HEADERS += \
../src/domain/register_address.h \
../src/domain/data_point_model.h \
../src/domain/control_logic_model.h \
../src/domain/project_model.h \
../src/domain/hmi_model.h \


+ 2
- 0
app/tests/project_management_tests.pro Parādīt failu

@@ -12,6 +12,7 @@ SOURCES += \
project_management_tests.cpp \
../src/domain/register_address.cpp \
../src/domain/register_repository.cpp \
../src/domain/data_point_model.cpp \
../src/domain/hmi_model.cpp \
../src/domain/control_logic_model.cpp \
../src/domain/project_model.cpp \
@@ -22,6 +23,7 @@ SOURCES += \
HEADERS += \
../src/domain/register_address.h \
../src/domain/register_repository.h \
../src/domain/data_point_model.h \
../src/domain/hmi_model.h \
../src/domain/control_logic_model.h \
../src/domain/project_model.h \


Notiek ielāde…
Atcelt
Saglabāt