wiRedPanda
Logic Circuit Simulator
Loading...
Searching...
No Matches
SimulationHandler.cpp
Go to the documentation of this file.
1// Copyright 2015 - 2026, GIBIS-UNIFESP and the wiRedPanda contributors
2// SPDX-License-Identifier: GPL-3.0-or-later
3
5
6#include <QFile>
7#include <QJsonArray>
8#include <QTextStream>
9
12#include "App/Scene/Scene.h"
14#include "App/UI/MainWindow.h"
15
17 : BaseHandler(mainWindow, validator)
18 , m_persistentDolphin(nullptr)
19{
20}
21
23{
24 // SimulationHandler is destroyed synchronously (not necessarily from within a
25 // running event loop), so deleteLater() may never fire — delete directly.
26 delete m_persistentDolphin;
27 m_persistentDolphin = nullptr;
28}
29
30QJsonObject SimulationHandler::handleCommand(const QString &command, const QJsonObject &params, const QJsonValue &requestId)
31{
32 if (command == "simulation_control") {
33 return handleSimulationControl(params, requestId);
34 } else if (command == "create_waveform") {
35 return handleCreateWaveform(params, requestId);
36 } else if (command == "export_waveform") {
37 return handleExportWaveform(params, requestId);
38 } else {
39 return createErrorResponse(QString("Unknown simulation command: %1").arg(command),
41 }
42}
43
44QJsonObject SimulationHandler::handleSimulationControl(const QJsonObject &params, const QJsonValue &requestId)
45{
46 if (!validateParameters(params, {"action"})) {
47 return createErrorResponse("Missing required parameter: action", requestId, JsonRpcError::InvalidParams);
48 }
49
50 QString errorMsg;
51 if (!validateNonEmptyString(params.value("action"), "action", errorMsg)) {
52 return createErrorResponse(errorMsg, requestId, JsonRpcError::InvalidParams);
53 }
54
55 QString action = params.value("action").toString();
56
57 Scene *scene = currentScene();
58 if (!scene) {
59 return createErrorResponse("No active circuit scene available", requestId, JsonRpcError::SceneNotAvailable);
60 }
61
62 Simulation *simulation = scene->simulation();
63 if (!simulation) {
64 return createErrorResponse("No simulation available", requestId, JsonRpcError::SimulationError);
65 }
66
67 return tryCommand([&]() -> QJsonObject {
68 if (action == "start") {
69 simulation->start();
70 } else if (action == "stop") {
71 simulation->stop();
72 } else if (action == "restart") {
73 simulation->restart();
74 } else if (action == "update") {
75 simulation->update();
76 } else {
77 return createErrorResponse(QString("Invalid action: %1").arg(action),
79 }
80 return createSuccessResponse(QJsonObject(), requestId);
81 }, "control simulation", requestId);
82}
83
84QJsonObject SimulationHandler::handleCreateWaveform(const QJsonObject &params, const QJsonValue &requestId)
85{
86 Scene *scene = currentScene();
87 if (!scene) {
88 return createErrorResponse("No active circuit scene available", requestId, JsonRpcError::SceneNotAvailable);
89 }
90
91 int duration = params.value("duration").toInt(32);
92 QJsonObject inputPatterns = params.value("input_patterns").toObject();
93
94 if (duration <= 0 || duration > SignalModel::kMaxColumns) {
95 return createErrorResponse(QString("Duration must be between 1 and %1").arg(SignalModel::kMaxColumns), requestId, JsonRpcError::ValidationError);
96 }
97
98 return tryCommand([&]() -> QJsonObject {
99 if (m_persistentDolphin) {
100 m_persistentDolphin->deleteLater();
101 m_persistentDolphin = nullptr;
102 }
103 m_persistentDolphin = new BewavedDolphin(scene, false, m_mainWindow, m_mainWindow);
104 BewavedDolphin *bewavedDolphin = m_persistentDolphin;
105
106 bewavedDolphin->prepare("");
107
108 bewavedDolphin->setLength(duration, false);
109
110 if (!inputPatterns.isEmpty()) {
111 for (auto it = inputPatterns.begin(); it != inputPatterns.end(); ++it) {
112 QString inputLabel = it.key();
113 QJsonArray pattern = it.value().toArray();
114
115 const int rowIndex = bewavedDolphin->inputRow(inputLabel);
116
117 if (rowIndex == -1) {
118 return createErrorResponse(QString("Input element with label '%1' not found").arg(inputLabel),
120 }
121
122 if (pattern.size() != duration) {
123 return createErrorResponse(QString("Pattern length for '%1' (%2) doesn't match duration (%3)")
124 .arg(inputLabel).arg(pattern.size()).arg(duration),
126 }
127
128 for (int col = 0; col < duration; ++col) {
129 int value = pattern[col].toInt();
130 if (value != 0 && value != 1) {
131 return createErrorResponse(QString("Invalid pattern value %1 for '%2' at step %3 (must be 0 or 1)")
132 .arg(value).arg(inputLabel).arg(col),
134 }
135 bewavedDolphin->setCellValue(rowIndex, col, value);
136 }
137 }
138 }
139
140 bewavedDolphin->run();
141
142 QJsonObject waveformData;
143 QJsonArray inputData;
144 QJsonArray outputData;
145
146 const auto waveform = bewavedDolphin->snapshot(duration);
147
148 for (const auto &signal : waveform.inputs) {
149 QJsonObject inputSignal;
150 inputSignal["label"] = signal.label;
151 inputSignal["type"] = "input";
152
153 QJsonArray values;
154 for (const int value : signal.values) {
155 values.append(value);
156 }
157 inputSignal["values"] = values;
158 inputData.append(inputSignal);
159 }
160
161 for (const auto &signal : waveform.outputs) {
162 QJsonObject outputSignal;
163 outputSignal["label"] = signal.label;
164 outputSignal["type"] = "output";
165
166 QJsonArray values;
167 for (const int value : signal.values) {
168 values.append(value);
169 }
170 outputSignal["values"] = values;
171 outputData.append(outputSignal);
172 }
173
174 waveformData["inputs"] = inputData;
175 waveformData["outputs"] = outputData;
176 waveformData["duration"] = duration;
177
178 QJsonObject result;
179 result["actual_duration"] = duration;
180 result["requested_duration"] = duration;
181 result["waveform_data"] = waveformData;
182 result["message"] = "Waveform created and analyzed successfully";
183 result["status"] = "ready";
184
185 return createSuccessResponse(result, requestId);
186 }, "create waveform", requestId);
187}
188
189QJsonObject SimulationHandler::handleExportWaveform(const QJsonObject &params, const QJsonValue &requestId)
190{
191 if (!validateParameters(params, {"filename", "format"})) {
192 return createErrorResponse("Missing required parameters: filename, format", requestId, JsonRpcError::InvalidParams);
193 }
194
195 QString filename = params.value("filename").toString();
196 QString format = params.value("format").toString().toLower();
197
198 if (format != "txt" && format != "png") {
199 return createErrorResponse("Only 'txt' and 'png' formats are supported for waveform export",
201 }
202
203 if (!m_persistentDolphin) {
204 return createErrorResponse("No waveform data available. Call create_waveform first.",
206 }
207
208 return tryCommand([&]() -> QJsonObject {
209 QJsonObject result;
210 result["filename"] = filename;
211 result["format"] = format;
212
213 if (format == "txt") {
214 QFile file(filename);
215 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
216 return createErrorResponse(QString("Failed to create file: %1").arg(filename),
217 requestId, JsonRpcError::FileError);
218 }
219
220 QTextStream stream(&file);
221 m_persistentDolphin->saveToTxt(stream);
222 stream.flush();
223 if (stream.status() != QTextStream::Ok || file.error() != QFileDevice::NoError) {
224 return createErrorResponse(QString("Failed to write waveform text: %1").arg(file.errorString()),
225 requestId, JsonRpcError::FileError);
226 }
227
228 } else if (format == "png") {
229 if (!m_persistentDolphin->exportToPng(filename)) {
230 return createErrorResponse("Failed to export waveform as PNG", requestId, JsonRpcError::FileError);
231 }
232 }
233
234 result["exported"] = true;
235 return createSuccessResponse(result, requestId);
236 }, "export waveform", requestId);
237}
BewavedDolphin waveform editor: digital signal creation, display, and export.
Main application window providing menus, toolbars, and tab management.
Main circuit editing scene with undo/redo and user interaction.
SignalModel for the beWavedDolphin waveform table.
Synchronous cycle-based simulation engine with event-driven clock support.
QJsonObject createSuccessResponse(const QJsonObject &result={}, const QJsonValue &requestId=QJsonValue()) const
QJsonObject tryCommand(Fn &&fn, const QString &action, const QJsonValue &requestId=QJsonValue())
Wraps fn in a try/catch, returning an error response on exception.
Definition BaseHandler.h:55
BaseHandler(MainWindow *mainWindow, const MCPValidator *validator)
QJsonObject createErrorResponse(const QString &error, const QJsonValue &requestId=QJsonValue(), int code=JsonRpcError::InternalError) const
MainWindow * m_mainWindow
Definition BaseHandler.h:85
bool validateParameters(const QJsonObject &params, const QStringList &required) const
bool validateNonEmptyString(const QJsonValue &value, const QString &paramName, QString &errorMsg) const
Scene * currentScene()
int inputRow(const QString &label) const
Returns the input row index whose element label equals label, or -1 (MCP access).
void run()
Runs the simulation for all input combinations and fills output rows.
void setCellValue(const int row, const int col, const int value)
Sets a single cell value in the waveform table.
void prepare(const QString &fileName={})
Prepares the waveform from fileName (or blank if empty).
WaveformSnapshot snapshot(int duration) const
Returns the input/output signals over the first duration columns (MCP access).
void setLength(const int simLength, const bool runSimulation=false)
Sets the number of time-step columns.
JSON Schema validator for MCP commands and responses using native json-schema-validator.
The top-level application window hosting the tab bar, menus, element palette, and editor.
Definition MainWindow.h:47
Simulation * simulation()
Returns the simulation engine associated with this scene.
Definition Scene.cpp:288
static constexpr int kMaxColumns
Definition SignalModel.h:38
QJsonObject handleCommand(const QString &command, const QJsonObject &params, const QJsonValue &requestId) override
SimulationHandler(MainWindow *mainWindow, const MCPValidator *validator)
void update()
Executes one simulation step (used by tests to advance the simulation manually).
void restart()
void start()
Starts the 1 ms simulation timer.
void stop()
Stops the simulation timer.
constexpr int SceneNotAvailable
No active circuit scene to operate on.
constexpr int SimulationError
Simulation control / waveform failure.
constexpr int MethodNotFound
The requested method does not exist or is unavailable.
constexpr int ElementNotFound
Referenced element id does not exist in the scene.
constexpr int FileError
File save/load/path error.
constexpr int ValidationError
Semantic validation failure (e.g. port index out of range, enum value not allowed).
constexpr int InvalidParams
Invalid method parameters (missing required, wrong type, etc.).