Parcourir la source

feat: add editable register monitoring

main
suyu il y a 3 semaines
Parent
révision
bf1961ea1c
15 fichiers modifiés avec 303 ajouts et 22 suppressions
  1. +6
    -0
      app/src/domain/virtual_register_repository.cpp
  2. +2
    -0
      app/src/domain/virtual_register_repository.h
  3. +6
    -1
      app/src/main.cpp
  4. +19
    -2
      app/src/services/offline_simulation_service.cpp
  5. +5
    -0
      app/src/services/offline_simulation_service.h
  6. +64
    -14
      app/src/services/register_monitor_service.cpp
  7. +20
    -1
      app/src/services/register_monitor_service.h
  8. +6
    -1
      app/src/ui/free_monitor_widget.cpp
  9. +1
    -1
      app/src/ui/free_monitor_widget.h
  10. +78
    -0
      app/src/ui/main_window.cpp
  11. +11
    -0
      app/src/ui/main_window.h
  12. +22
    -0
      app/src/ui/main_window.ui
  13. +3
    -2
      app/src/ui/runtime_panel_controller.cpp
  14. +32
    -0
      app/tests/offline_simulation_service_tests.cpp
  15. +28
    -0
      app/tests/register_monitor_service_tests.cpp

+ 6
- 0
app/src/domain/virtual_register_repository.cpp Voir le fichier

@@ -85,3 +85,9 @@ void VirtualRegisterRepository::clear()
bits_.fill(false);
words_.fill(0);
}

void VirtualRegisterRepository::copyFrom(const VirtualRegisterRepository &source)
{
bits_ = source.bits_;
words_ = source.words_;
}

+ 2
- 0
app/src/domain/virtual_register_repository.h Voir le fichier

@@ -27,6 +27,8 @@ public:

// 将所有虚拟寄存器恢复为默认值,开始新的离线会话
void clear();
// 从另一份虚拟寄存器复制完整的 M/D 快照
void copyFrom(const VirtualRegisterRepository &source);

private:
static constexpr std::size_t kRegisterCount =


+ 6
- 1
app/src/main.cpp Voir le fichier

@@ -60,6 +60,8 @@ int main(int argc, char *argv[])

// 在内存中保存离线仿真使用的 M、D 寄存器值,不会写入真实 PLC
VirtualRegisterRepository virtual_register_repository;
// 保存编辑态确认的离线初始值,不参与仿真扫描和工程持久化
VirtualRegisterRepository offline_initial_repository;
// 保存从真实 PLC 最近一次读取到的 M、D 值,并转交写入请求
PlcRegisterRepository plc_register_repository;
// 给界面提供统一的寄存器入口,启动时先使用上面的离线寄存器
@@ -79,8 +81,11 @@ int main(int argc, char *argv[])
RegisterCommentService register_comment_service(project_service);
// 处理自由监控列表中的寄存器读取和单点写入
RegisterMonitorService register_monitor_service(active_register_repository);
register_monitor_service.setOfflineInitialRepository(offline_initial_repository);
register_monitor_service.setOfflineInitialCaptureEnabled(true);
// 用虚拟寄存器执行本地梯形图,实现不连接 PLC 的离线仿真
OfflineSimulationService offline_simulation_service(virtual_register_repository);
OfflineSimulationService offline_simulation_service(
virtual_register_repository, &offline_initial_repository);
// 从 PLC 缓存推算本地梯形图轨迹,输出只写内部临时仓库
OnlineLogicMonitorService online_logic_monitor_service(plc_register_repository);
// 管理三种运行模式,并控制离线仿真和真机只读轨迹的生命周期


+ 19
- 2
app/src/services/offline_simulation_service.cpp Voir le fichier

@@ -4,9 +4,11 @@

OfflineSimulationService::OfflineSimulationService(
VirtualRegisterRepository &repository,
VirtualRegisterRepository *initial_repository,
QObject *parent)
: QObject(parent),
repository_(repository)
repository_(repository),
initial_repository_(initial_repository)
{
timer_.setInterval(kDefaultScanIntervalMs);
timer_.setTimerType(Qt::PreciseTimer);
@@ -31,10 +33,14 @@ SimulationStartResult OfflineSimulationService::start(
return {false, SimulationStartError::InvalidLogic, validation};
}

// 重启会话时清空沿触发状态和虚拟 M/D,避免继承上次运行现场
// 重启会话时清空沿触发状态和运行值,再装载编辑态确认的初始值
timer_.stop();
executor_.resetRuntime();
repository_.clear();
if (initial_repository_ != nullptr)
{
repository_.copyFrom(*initial_repository_);
}
logic_snapshot_ = std::move(snapshot);
successful_scan_count_ = 0;
last_error_ = {true, LogicScanError::None, {}, {}, {}, {}};
@@ -45,6 +51,15 @@ SimulationStartResult OfflineSimulationService::start(
return {true, SimulationStartError::None, {true, LogicScanError::None, {}, {}, {}, {}}};
}

void OfflineSimulationService::restoreInitialValues()
{
repository_.clear();
if (initial_repository_ != nullptr)
{
repository_.copyFrom(*initial_repository_);
}
}

void OfflineSimulationService::stop()
{
timer_.stop();
@@ -54,6 +69,8 @@ void OfflineSimulationService::stop()
{
return;
}
// 运行结束后丢弃本轮输出,编辑态继续看到下一轮要使用的初始值
restoreInitialValues();
state_ = SimulationState::Stopped;
last_error_ = {true, LogicScanError::None, {}, {}, {}, {}};
emit stateChanged();


+ 5
- 0
app/src/services/offline_simulation_service.h Voir le fichier

@@ -45,10 +45,12 @@ public:
/**
* @brief 创建离线仿真服务
* @param repository 离线模式使用的虚拟 M/D 寄存器仓库
* @param initial_repository 编辑态确认的离线初始值仓库,可为空以保持旧的全清零行为
* @param parent Qt 对象父级
*/
explicit OfflineSimulationService(
VirtualRegisterRepository &repository,
VirtualRegisterRepository *initial_repository = nullptr,
QObject *parent = nullptr);

// 复制工程逻辑作为本次会话快照并启动定时扫描;启动失败时不进入运行态
@@ -57,6 +59,8 @@ public:
void stop();
// 立即执行一轮扫描;非 Running 状态调用会返回失败结果
LogicScanResult executeOnce();
// 将当前会话的离线初始值恢复到运行仓库
void restoreInitialValues();

// 返回当前离线仿真生命周期状态
SimulationState state() const;
@@ -82,6 +86,7 @@ private:
void enterFault(const LogicScanResult &error);

VirtualRegisterRepository &repository_; // 不拥有的虚拟寄存器仓库
VirtualRegisterRepository *initial_repository_ = nullptr; // 不拥有的离线初始值仓库
SoftwareLogicExecutor executor_; // 执行逻辑校验和扫描的领域执行器
QTimer timer_; // 按固定周期触发离线扫描
std::vector<ControlLogic> logic_snapshot_; // 本次仿真使用的逻辑快照


+ 64
- 14
app/src/services/register_monitor_service.cpp Voir le fichier

@@ -1,5 +1,7 @@
#include "register_monitor_service.h"

#include "domain/virtual_register_repository.h"

#include <algorithm>
#include <cmath>
#include <optional>
@@ -31,6 +33,17 @@ RegisterMonitorService::RegisterMonitorService(RegisterRepository &repository)
{
}

void RegisterMonitorService::setOfflineInitialRepository(
VirtualRegisterRepository &repository)
{
offline_initial_repository_ = &repository;
}

void RegisterMonitorService::setOfflineInitialCaptureEnabled(bool enabled)
{
capture_offline_initial_values_ = enabled;
}

RegisterMonitorResult RegisterMonitorService::addRange(
const std::string &start_address, int count)
{
@@ -175,34 +188,32 @@ RegisterMonitorWriteResult RegisterMonitorService::writeBit(
const RegisterAddress &address, bool value)
{
const RegisterWriteResult result = repository_.writeBit(address, value);
if (result.succeeded)
{
return {true, RegisterError::None, {}};
}
return {false, result.error, result.error == RegisterError::AreaMismatch
const RegisterMonitorWriteResult active_result = result.succeeded
? RegisterMonitorWriteResult{true, RegisterError::None, {}}
: RegisterMonitorWriteResult{false, result.error, result.error == RegisterError::AreaMismatch
? "位写入只允许访问 M 区"
: result.error == RegisterError::InvalidAddress
? "写入地址无效"
: result.error == RegisterError::Unavailable
? "当前寄存器源不可用"
: "寄存器写入请求被拒绝"};
return captureInitialBit(address, value, active_result);
}

RegisterMonitorWriteResult RegisterMonitorService::writeWord(
const RegisterAddress &address, std::int16_t value)
{
const RegisterWriteResult result = repository_.writeWord(address, value);
if (result.succeeded)
{
return {true, RegisterError::None, {}};
}
return {false, result.error, result.error == RegisterError::AreaMismatch
const RegisterMonitorWriteResult active_result = result.succeeded
? RegisterMonitorWriteResult{true, RegisterError::None, {}}
: RegisterMonitorWriteResult{false, result.error, result.error == RegisterError::AreaMismatch
? "字写入只允许访问 D 区"
: result.error == RegisterError::InvalidAddress
? "写入地址无效"
: result.error == RegisterError::Unavailable
? "当前寄存器源不可用"
: "寄存器写入请求被拒绝"};
return captureInitialWord(address, value, active_result);
}

RegisterMonitorWriteResult RegisterMonitorService::writeFloat(
@@ -216,12 +227,51 @@ RegisterMonitorWriteResult RegisterMonitorService::writeFloat(
}
const RegisterWriteResult result = repository_.writeWordPair(
address, Float32Codec::encode(value));
if (result.succeeded)
const RegisterMonitorWriteResult active_result = result.succeeded
? RegisterMonitorWriteResult{true, RegisterError::None, {}}
: RegisterMonitorWriteResult{false, result.error, result.error == RegisterError::Unavailable
? "当前寄存器源不可用" : "寄存器写入请求被拒绝"};
return captureInitialFloat(address, value, active_result);
}

RegisterMonitorWriteResult RegisterMonitorService::captureInitialBit(
const RegisterAddress &address, bool value,
const RegisterMonitorWriteResult &active_result)
{
if (!active_result.succeeded || !capture_offline_initial_values_
|| offline_initial_repository_ == nullptr)
{
return active_result;
}
offline_initial_repository_->writeBit(address, value);
return active_result;
}

RegisterMonitorWriteResult RegisterMonitorService::captureInitialWord(
const RegisterAddress &address, std::int16_t value,
const RegisterMonitorWriteResult &active_result)
{
if (!active_result.succeeded || !capture_offline_initial_values_
|| offline_initial_repository_ == nullptr)
{
return {true, RegisterError::None, {}};
return active_result;
}
return {false, result.error, result.error == RegisterError::Unavailable
? "当前寄存器源不可用" : "寄存器写入请求被拒绝"};
offline_initial_repository_->writeWord(address, value);
return active_result;
}

RegisterMonitorWriteResult RegisterMonitorService::captureInitialFloat(
const RegisterAddress &address, float value,
const RegisterMonitorWriteResult &active_result)
{
if (!active_result.succeeded || !capture_offline_initial_values_
|| offline_initial_repository_ == nullptr)
{
return active_result;
}
offline_initial_repository_->writeWordPair(
address, Float32Codec::encode(value));
return active_result;
}

const std::vector<RegisterAddress> &RegisterMonitorService::addresses() const


+ 20
- 1
app/src/services/register_monitor_service.h Voir le fichier

@@ -8,6 +8,8 @@
#include <string>
#include <vector>

class VirtualRegisterRepository;

// 自由监控列表编辑的失败分类
enum class RegisterMonitorError
{
@@ -38,7 +40,7 @@ struct RegisterMonitorWriteResult
/**
* @brief 编排自由监控地址列表和寄存器仓库读写
*
* 地址列表只属于当前运行会话,不写入工程文件;仓库由当前运行模式注入
* 地址列表和离线初始值只属于当前运行会话,不写入工程文件;仓库由当前运行模式注入
*/
class RegisterMonitorService
{
@@ -49,6 +51,11 @@ public:
*/
explicit RegisterMonitorService(RegisterRepository &repository);

// 配置编辑态离线初始值仓库;该仓库只保存在当前进程内
void setOfflineInitialRepository(VirtualRegisterRepository &repository);
// 编辑态开启时,监控写入同时更新离线初始值
void setOfflineInitialCaptureEnabled(bool enabled);

/**
* @brief 从起始地址连续添加同一区域的多个地址
* @param start_address 文本地址,例如 M0 或 D100
@@ -108,7 +115,19 @@ public:
void setAddressesChangedCallback(std::function<void()> callback);

private:
RegisterMonitorWriteResult captureInitialBit(
const RegisterAddress &address, bool value,
const RegisterMonitorWriteResult &active_result);
RegisterMonitorWriteResult captureInitialWord(
const RegisterAddress &address, std::int16_t value,
const RegisterMonitorWriteResult &active_result);
RegisterMonitorWriteResult captureInitialFloat(
const RegisterAddress &address, float value,
const RegisterMonitorWriteResult &active_result);

RegisterRepository &repository_; // 当前运行模式的寄存器仓库,不由服务拥有
VirtualRegisterRepository *offline_initial_repository_ = nullptr;
bool capture_offline_initial_values_ = false;
RegisterMonitorModel model_; // 当前会话的去重监控地址列表
std::function<void()> addresses_changed_callback_; // 地址变化后的轮询刷新通知
};

+ 6
- 1
app/src/ui/free_monitor_widget.cpp Voir le fichier

@@ -73,7 +73,12 @@ void FreeMonitorWidget::refreshValues(
QString source = tr("数据源:未启用");
bool values_available = false;
bool communication_fault = false;
if (mode == ApplicationMode::OfflineRunning)
if (mode == ApplicationMode::Editing)
{
source = tr("数据源:离线初始值 · 虚拟 M/D");
values_available = true;
}
else if (mode == ApplicationMode::OfflineRunning)
{
source = tr("数据源:虚拟 M/D");
values_available = true;


+ 1
- 1
app/src/ui/free_monitor_widget.h Voir le fichier

@@ -34,7 +34,7 @@ public:
/** 释放 Designer 界面对象 */
~FreeMonitorWidget() override;

/** 真机未连接、通信故障或编辑态时应关闭写入入口 */
/** 根据当前模式和通信状态开启或关闭写入入口 */
void setWriteEnabled(bool enabled);
/** 根据运行模式和 PLC 状态刷新所有监控值 */
void refreshValues(ApplicationMode mode, PlcConnectionState plc_state);


+ 78
- 0
app/src/ui/main_window.cpp Voir le fichier

@@ -6,6 +6,7 @@
#include "logic_editor_widget.h"
#include "logic_instruction_dialog.h"
#include "plc_connection_dialog.h"
#include "free_monitor_widget.h"
#include "runtime_monitor_widget.h"
#include "toolbar_icon_factory.h"
#include "project_workspace_controller.h"
@@ -30,6 +31,7 @@
#include <QApplication>
#include <QCloseEvent>
#include <QDialogButtonBox>
#include <QDockWidget>
#include <QEvent>
#include <QFileDialog>
#include <QGraphicsScene>
@@ -352,6 +354,7 @@ void MainWindow::initializeUi()
configureHmiEditor();
configureLogicEditor();
configureRuntimeMonitor();
configureDataMonitor();
configureProjectTree();
runtime_mode_service_.setPlcStatusChangedCallback(
[this] { schedulePlcStatusUpdate(); });
@@ -384,6 +387,7 @@ void MainWindow::initializeUi()
MainWindow::~MainWindow()
{
qApp->removeEventFilter(this);
register_monitor_service_.setAddressesChangedCallback({});
runtime_mode_service_.setPlcStatusChangedCallback({});
}

@@ -871,6 +875,77 @@ void MainWindow::configureRuntimeMonitor()
runtime_monitor_widget_ = runtime_panel_controller_->runtimeMonitorWidget();
}

void MainWindow::configureDataMonitor()
{
data_monitor_dock_ = ui_->dataMonitorDock;
data_monitor_widget_ = new FreeMonitorWidget(
register_monitor_service_, data_monitor_dock_);
data_monitor_widget_->setObjectName(QStringLiteral("dataMonitorWidget"));
ui_->dataMonitorDockLayout->addWidget(data_monitor_widget_);
ui_->viewMenu->addAction(data_monitor_dock_->toggleViewAction());

connect(data_monitor_widget_, &FreeMonitorWidget::monitorAddressesChanged,
this,
[this]
{
runtime_mode_service_.setMonitorAddresses(
register_monitor_service_.pollAddresses(),
register_monitor_service_.float32Starts());
refreshDataMonitorUi();
});
connect(data_monitor_widget_, &FreeMonitorWidget::operationMessage,
this,
[this](const QString &message)
{
statusBar()->showMessage(message, 4000);
appendOutputMessage(message);
});
register_monitor_service_.setAddressesChangedCallback(
[this]
{
if (data_monitor_widget_ != nullptr)
{
data_monitor_widget_->reloadAddresses();
}
if (runtime_monitor_widget_ != nullptr
&& runtime_monitor_widget_->freeMonitorWidget() != nullptr)
{
runtime_monitor_widget_->freeMonitorWidget()->reloadAddresses();
}
runtime_mode_service_.setMonitorAddresses(
register_monitor_service_.pollAddresses(),
register_monitor_service_.float32Starts());
});
data_monitor_refresh_timer_ = new QTimer(this);
data_monitor_refresh_timer_->setInterval(150);
connect(data_monitor_refresh_timer_, &QTimer::timeout,
this, &MainWindow::refreshDataMonitorUi);
data_monitor_refresh_timer_->start();
refreshDataMonitorUi();
}

void MainWindow::refreshDataMonitorUi()
{
if (data_monitor_widget_ == nullptr)
{
return;
}
const ApplicationMode mode = runtime_mode_service_.mode();
const bool online_connected = mode == ApplicationMode::OnlineRunning
&& runtime_mode_service_.plcConnectionState()
== PlcConnectionState::Connected;
data_monitor_widget_->setWriteEnabled(
mode == ApplicationMode::Editing
|| mode == ApplicationMode::OfflineRunning
|| online_connected);
data_monitor_widget_->refreshValues(
mode, runtime_mode_service_.plcConnectionState());
if (data_monitor_dock_ != nullptr)
{
data_monitor_dock_->setVisible(mode == ApplicationMode::Editing);
}
}

void MainWindow::configureProjectTree()
{
project_workspace_controller_ = std::make_unique<ProjectWorkspaceController>(
@@ -1751,6 +1826,8 @@ void MainWindow::updateModeUi(const QString &message)
// 按服务层策略统一启用/禁用编辑入口,避免单个按钮遗漏状态同步
const ApplicationMode mode = runtime_mode_service_.mode();
const ModePolicy policy = runtime_mode_service_.policy();
register_monitor_service_.setOfflineInitialCaptureEnabled(
mode == ApplicationMode::Editing);
restoreCurrentModeAction();
const bool running = mode != ApplicationMode::Editing;
if (running)
@@ -1869,6 +1946,7 @@ void MainWindow::updateModeUi(const QString &message)
register_status_label_->setText(tr("PLC:通信故障"));
}
updateSimulationUi(false);
refreshDataMonitorUi();
statusBar()->showMessage(message, 4000);
const bool duplicate = ui_->outputList->count() > 0
&& ui_->outputList->item(ui_->outputList->count() - 1)->text() == message;


+ 11
- 0
app/src/ui/main_window.h Voir le fichier

@@ -26,6 +26,8 @@ class QActionGroup;
class QCloseEvent;
class QEvent;
class QLabel;
class QDockWidget;
class QTimer;

namespace Ui {
class MainWindow;
@@ -46,6 +48,7 @@ class LogicEditorWidget;
class RegisterMonitorService;
class PlcDiscoveryGateway;
class RuntimeMonitorWidget;
class FreeMonitorWidget;
class ProjectWorkspaceController;
class PropertyPanelController;
class RuntimePanelController;
@@ -127,6 +130,8 @@ private:
void configureLogicEditor();
/** 创建并连接运行监控面板 */
void configureRuntimeMonitor();
/** 创建并连接编辑态数据监控面板 */
void configureDataMonitor();
/** 创建并连接工程树 */
void configureProjectTree();
/** 创建并连接控件属性编辑表单 */
@@ -232,6 +237,8 @@ private:
void restoreCurrentModeAction();
/** 完成主窗口初始化和首次界面刷新 */
void initializeUi();
/** 刷新编辑态数据监控的模式、权限和当前值 */
void refreshDataMonitorUi();

/** Qt Designer 生成的主窗口界面对象 */
std::unique_ptr<Ui::MainWindow> ui_;
@@ -279,6 +286,10 @@ private:
LogicEditorWidget *logic_editor_widget_ = nullptr;
/** 唯一的运行监控控件 */
RuntimeMonitorWidget *runtime_monitor_widget_ = nullptr;
/** 主窗口中的数据监控面板 */
QDockWidget *data_monitor_dock_ = nullptr;
FreeMonitorWidget *data_monitor_widget_ = nullptr;
QTimer *data_monitor_refresh_timer_ = nullptr;
/** 显示当前运行模式的状态标签 */
QLabel *mode_status_label_ = nullptr;
/** 显示 PLC 连接状态的标签 */


+ 22
- 0
app/src/ui/main_window.ui Voir le fichier

@@ -905,6 +905,28 @@
</layout>
</widget>
</widget>
<widget class="QDockWidget" name="dataMonitorDock">
<property name="minimumSize">
<size>
<width>640</width>
<height>220</height>
</size>
</property>
<property name="windowTitle">
<string>数据监控</string>
</property>
<attribute name="dockWidgetArea">
<number>2</number>
</attribute>
<widget class="QWidget" name="dataMonitorDockContents">
<layout class="QVBoxLayout" name="dataMonitorDockLayout">
<property name="leftMargin"><number>6</number></property>
<property name="topMargin"><number>6</number></property>
<property name="rightMargin"><number>6</number></property>
<property name="bottomMargin"><number>6</number></property>
</layout>
</widget>
</widget>
<widget class="QDockWidget" name="outputDock">
<property name="minimumSize">
<size>


+ 3
- 2
app/src/ui/runtime_panel_controller.cpp Voir le fichier

@@ -157,9 +157,10 @@ void RuntimePanelController::enterRuntime(
if (!runtime_session_active_)
{
runtime_monitor_widget_->setProjectObjects(page_id, logic_id);
// 运行窗口只创建一次,后续进入运行态复用同一个监控投影
runtime_session_active_ = true;
// 运行窗口只创建一次,后续进入运行态复用同一个监控投影
runtime_session_active_ = true;
}
runtime_monitor_widget_->freeMonitorWidget()->reloadAddresses();
runtime_monitor_widget_->setMode(mode, plc_state);
runtime_monitor_window_->showForRuntime();
}


+ 32
- 0
app/tests/offline_simulation_service_tests.cpp Voir le fichier

@@ -660,6 +660,37 @@ void testSimulationLifecycleSnapshotAndFault()
simulation.stop();
}

void testSimulationUsesInitialValuesAndDiscardsRuntimeOutputs()
{
VirtualRegisterRepository repository;
VirtualRegisterRepository initial_repository;
OfflineSimulationService simulation(repository, &initial_repository);
const ControlLogic program = logic({
rung("initial-rung", {{contact("initial-input", 0)}}, coil("runtime-output", 1))});

writeBit(initial_repository, 0, true);
writeWord(initial_repository, 10, 321);
require(simulation.start({program}).succeeded,
"simulation must start from the explicit initial repository");
require(readBit(repository, 0)
&& readWord(repository, 10) == 321,
"simulation startup must copy initial M/D values into its runtime repository");
require(simulation.executeOnce().succeeded && readBit(repository, 1),
"simulation must execute against the copied initial values");
simulation.stop();
require(!readBit(repository, 1)
&& readBit(repository, 0)
&& readWord(repository, 10) == 321,
"stopping simulation must restore initial values instead of runtime outputs");

writeBit(initial_repository, 0, false);
require(simulation.start({program}).succeeded,
"simulation must support restart with changed initial values");
require(!readBit(repository, 0) && !readBit(repository, 1),
"restart must use the latest initial values and discard the previous output");
simulation.stop();
}

class FailingVirtualRegisterRepository final : public VirtualRegisterRepository
{
public:
@@ -760,6 +791,7 @@ int main(int argc, char *argv[])
testConflictingCoilsAreRejected();
testHmiSimulationClosedLoop();
testSimulationLifecycleSnapshotAndFault();
testSimulationUsesInitialValuesAndDiscardsRuntimeOutputs();
testRepositoryFailureEntersFaultState();
testOnlineMonitorUsesTemporaryRegistersWithoutWritingPlcSource();
testOnlineMonitorPreservesFaultAfterStopping();


+ 28
- 0
app/tests/register_monitor_service_tests.cpp Voir le fichier

@@ -103,6 +103,33 @@ void testRegisterWrites()
"monitor word writes must preserve signed 16-bit boundary values");
}

void testOfflineInitialValueCapture()
{
VirtualRegisterRepository virtual_repository;
VirtualRegisterRepository initial_repository;
ActiveRegisterRepository active_repository(virtual_repository);
RegisterMonitorService service(active_repository);
service.setOfflineInitialRepository(initial_repository);
service.setOfflineInitialCaptureEnabled(true);

require(service.writeBit({RegisterArea::M, 3}, true).succeeded
&& service.writeWord({RegisterArea::D, 10}, -456).succeeded
&& service.writeFloat({RegisterArea::D, 20}, 1.5f).succeeded,
"editing monitor writes must succeed against the virtual repository");
require(initial_repository.readBit({RegisterArea::M, 3}).value
&& initial_repository.readWord({RegisterArea::D, 10}).value == -456
&& std::fabs(Float32Codec::decode(
initial_repository.readWordPair({RegisterArea::D, 20}).values[0],
initial_repository.readWordPair({RegisterArea::D, 20}).values[1])
.value_or(0.0f) - 1.5f) < 0.000001f,
"editing monitor writes must be captured as offline initial values");

service.setOfflineInitialCaptureEnabled(false);
require(service.writeBit({RegisterArea::M, 3}, false).succeeded
&& initial_repository.readBit({RegisterArea::M, 3}).value,
"runtime writes must not overwrite the captured initial values");
}

void testFloat32Monitoring()
{
VirtualRegisterRepository repository;
@@ -151,6 +178,7 @@ int main()
testRangeManagement();
testSharedActiveRepositoryValues();
testRegisterWrites();
testOfflineInitialValueCapture();
testFloat32Monitoring();
}
catch (const std::exception &error)


Chargement…
Annuler
Enregistrer