Skip to content

Commit db43502

Browse files
committed
open QtScxmlTester source code
1 parent 15f5a86 commit db43502

File tree

6 files changed

+614
-2
lines changed

6 files changed

+614
-2
lines changed

Doc/DebugScxmlStateCharts.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ There are two ready-to-use testing applications:
1616
1. Based on [USCXML framework](https://github.com/tklab-tud/uscxml).
1717
Supports **null, lua, ecmascript(since 2.1.5.1507)** datamodels
1818

19-
2. Based on [Qt SCXML framework](https://doc.qt.io/qt-5/qtscxml-index.html)
19+
2. [QtScxmlTester](../Testers/QtScxmlTester/README.md) - based on [Qt SCXML framework](https://doc.qt.io/qt-5/qtscxml-index.html)
2020
Supports **null, ecmascript** datamodels
2121

2222
Also you may write your own testing application using the corresponding API

Examples/Misc/foreach_script.scxml

Lines changed: 335 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ There are two ready-to-use testing applications:
199199
1. Based on [USCXML framework](https://github.com/tklab-tud/uscxml).
200200
Supports **null, lua, ecmascript** datamodels
201201

202-
2. Based on [Qt SCXML framework](https://doc.qt.io/qt-5/qtscxml-overview.html)
202+
2. [QtScxmlTester](Testers/QtScxmlTester/README.md) - based on [Qt SCXML framework](https://doc.qt.io/qt-5/qtscxml-overview.html)
203203
Supports **null, ecmascript** datamodels
204204

205205
Also you may write your own testing application using the corresponding API
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
QT += core scxml network xml
2+
QT -= gui
3+
4+
CONFIG += c++11
5+
6+
TARGET = QtScxmlTester
7+
CONFIG += console
8+
CONFIG -= app_bundle
9+
10+
TEMPLATE = app
11+
12+
SOURCES += main.cpp
13+
14+
# The following define makes your compiler emit warnings if you use
15+
# any feature of Qt which as been marked deprecated (the exact warnings
16+
# depend on your compiler). Please consult the documentation of the
17+
# deprecated API in order to know how to port your code away from it.
18+
DEFINES += QT_DEPRECATED_WARNINGS
19+
20+
# You can also make your code fail to compile if you use deprecated APIs.
21+
# In order to do so, uncomment the following line.
22+
# You can also select to disable deprecated APIs only up to a certain version of Qt.
23+
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
24+
25+
INCLUDEPATH += Monitor/
26+
27+
HEADERS += \
28+
../../Include/scxmlexternmonitor2.h
29+
30+
VERSION = 2.0.0.2
31+

Testers/QtScxmlTester/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<a name="top-anchor"/>
2+
3+
| [SCXML Wiki](https://alexzhornyak.github.io/SCXML-tutorial/) | [Forum](https://github.com/alexzhornyak/ScxmlEditor-Tutorial/discussions) |
4+
|---|---|
5+
6+
# QtScxmlTester
7+
Qt Console Application that interacts with [ScxmlEditor](../../README.md). It receives [triggered events](../../Doc/DebugScxmlStateCharts.md#triggers) from application and send back [UDP commands](../../Doc/DebugScxmlStateCharts.md#receive-api) such as enter-exit states, etc.

Testers/QtScxmlTester/main.cpp

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
#include <QCoreApplication>
2+
3+
#include <iostream>
4+
#include <memory>
5+
6+
#include <QDebug>
7+
#include <QLoggingCategory>
8+
#include <QScxmlStateMachine>
9+
#include <QScxmlInvokableService>
10+
#include <QUdpSocket>
11+
#include <QNetworkDatagram>
12+
#include <QDomDocument>
13+
#include <QFile>
14+
#include <QCommandLineParser>
15+
#include <QDateTime>
16+
#include <QRegExp>
17+
#include <QJsonDocument>
18+
#include <QJsonObject>
19+
20+
#include "../../Include/scxmlexternmonitor2.h"
21+
22+
#define MLOG(VAL) std::wcout << L#VAL << "> " << QDateTime::currentDateTime().toString("HH:mm:ss.zzz").toStdWString() << " QtScxmlTester.exe "
23+
24+
typedef enum { cttDefault, cttBool, cttInteger, cttDouble, cttString } TContentTriggerType;
25+
26+
struct Settings {
27+
// flags
28+
bool bCheckIssue = false;
29+
bool bExitStop = false;
30+
bool bScxmlMonitorLog = false;
31+
bool bHideDebugInfo = false;
32+
// network
33+
long nRemotePort = 11005;
34+
long nLocalPort = 11001;
35+
// scxml
36+
QString sScxmlFilePath = "";
37+
};
38+
39+
static Settings settings;
40+
static QUdpSocket socket;
41+
static std::unique_ptr<QScxmlStateMachine> machine_ptr;
42+
43+
void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
44+
{
45+
switch (type) {
46+
case QtDebugMsg:
47+
case QtInfoMsg:
48+
if (QString::compare(context.category, "qt.scxml.statemachine")==0) {
49+
50+
if (msg.contains("failed to parse") || msg.contains("had error")) {
51+
MLOG(ERROR) << msg.toStdWString() << std::endl;
52+
} else if (settings.bScxmlMonitorLog) {
53+
MLOG(INFO) << msg.toStdWString() << std::endl;
54+
}
55+
56+
} else {
57+
MLOG(INFO) << msg.toStdWString() << std::endl;
58+
}
59+
break;
60+
case QtWarningMsg:
61+
MLOG(WARNING) << msg.toStdWString() << std::endl;
62+
break;
63+
case QtCriticalMsg:
64+
case QtFatalMsg:
65+
MLOG(ERROR) << msg.toStdWString() << std::endl;
66+
break;
67+
default:
68+
MLOG(UNKNOWN) << msg.toStdWString() << std::endl;
69+
break;
70+
}
71+
}
72+
73+
int main(int argc, char *argv[])
74+
{
75+
int iRes = 1;
76+
77+
auto showCMD = [](const QStringList &arguments) {
78+
MLOG(INFO) << L"CommandLine [";
79+
for (const auto &it : arguments) {
80+
std::wcout << it.toStdWString() << L" ";
81+
}
82+
std::wcout << L"]" << std::endl;
83+
};
84+
85+
try {
86+
87+
std::locale::global(std::locale("",std::locale::messages));
88+
std::locale::global(std::locale("", std::locale::ctype));
89+
90+
QCoreApplication a(argc, argv);
91+
QCoreApplication::setApplicationName("QtScxmlTester");
92+
93+
QCommandLineParser parser;
94+
parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
95+
parser.addHelpOption();
96+
97+
parser.addOptions({
98+
{ "issue", "check scxml for issues and errors, by default: false", "issue", "false" },
99+
{ "scxmlcoutlog", "log all StateMachine events, by default: false", "scxmlcoutlog", "false" },
100+
{ "exitstop", "pause application on exit, by default: false", "exitstop", "false" },
101+
{ "r", "port for GUI application, by default: 11005", "remote_port", QString::number(settings.nRemotePort) },
102+
{ "l", "local port for QtScxmlTester application, by default: 11001", "local_port", QString::number(settings.nLocalPort) },
103+
{ "f", "full path to scxml file, for example: C:\\Projects\\helloWorld.scxml", "scxml_path" },
104+
{ "hidedebuginfo", "hides debug information, by default: false", "hidedebuginfo", "false" }
105+
});
106+
107+
108+
if (!parser.parse(a.arguments())) {
109+
showCMD(a.arguments());
110+
111+
MLOG(WARNING) << parser.errorText().toStdWString() << std::endl;
112+
}
113+
114+
settings.bCheckIssue = parser.value("issue") == "true";
115+
settings.bExitStop = parser.value("exitstop") == "true";
116+
settings.bScxmlMonitorLog = parser.value("scxmlcoutlog") == "true";
117+
settings.sScxmlFilePath = parser.value("f");
118+
settings.nLocalPort = parser.value("l").toLong();
119+
settings.nRemotePort = parser.value("r").toLong();
120+
settings.bHideDebugInfo = parser.value("hidedebuginfo") == "true";
121+
122+
if (!settings.bHideDebugInfo) {
123+
showCMD(a.arguments());
124+
}
125+
126+
qInstallMessageHandler(myMessageOutput);
127+
QLoggingCategory::setFilterRules("qt.scxml.statemachine=true");
128+
129+
machine_ptr.reset(QScxmlStateMachine::fromFile(settings.sScxmlFilePath));
130+
if (!machine_ptr) throw std::runtime_error("Can not load <" + settings.sScxmlFilePath.toStdString() + ">");
131+
132+
const auto vecErrors = machine_ptr->parseErrors();
133+
for (const auto &itErr : vecErrors) {
134+
MLOG(ERROR) << itErr.toString().toStdWString() << std::endl;
135+
}
136+
137+
if (!vecErrors.empty()) throw std::exception("Application is terminated due to parsing critical errors!");
138+
139+
if (!socket.bind(QHostAddress::Any, static_cast<quint16>(settings.nLocalPort))) {
140+
MLOG(ERROR) << socket.errorString().toStdWString() << std::endl;
141+
return -1;
142+
}
143+
144+
Scxmlmonitor::UDPScxmlExternMonitor *monitor = new Scxmlmonitor::UDPScxmlExternMonitor(machine_ptr.get());
145+
monitor->setRemotePort(settings.nRemotePort);
146+
monitor->setScxmlStateMachine(machine_ptr.get());
147+
148+
auto getTriggerValue = [](const QString &sText, const TContentTriggerType triggerType) {
149+
QVariant varData;
150+
switch (triggerType) {
151+
case cttInteger:
152+
varData = sText.toInt();
153+
break;
154+
case cttDouble:
155+
varData = sText.toDouble();
156+
break;
157+
case cttBool:
158+
{
159+
const bool bVal = sText.compare("true",Qt::CaseInsensitive) == 0;
160+
varData = bVal;
161+
} break;
162+
case cttString:
163+
default:
164+
varData = sText;
165+
break;
166+
}
167+
return varData;
168+
};
169+
170+
QObject::connect(&socket, &QUdpSocket::readyRead, [&](){
171+
while (socket.hasPendingDatagrams()) {
172+
QNetworkDatagram datagram = socket.receiveDatagram();
173+
174+
/* Custom Commands */
175+
QString sIncomeData = QString::fromUtf8(datagram.data());
176+
if (sIncomeData.startsWith("SYNC_INVOKED@")) {
177+
178+
monitor->synchronizeMonitor("", sIncomeData.replace("SYNC_INVOKED@", ""));
179+
180+
continue;
181+
}
182+
183+
/* Events */
184+
QDomDocument doc;
185+
doc.setContent(datagram.data());
186+
187+
QDomElement docElem = doc.documentElement();
188+
if (!docElem.isNull() && docElem.nodeName()=="EVENT") {
189+
190+
QVariant varData;
191+
QVariantMap varMap;
192+
193+
bool isContext = false;
194+
195+
QDomNode n = docElem.firstChild();
196+
while(!n.isNull()) {
197+
QDomElement e = n.toElement(); // try to convert the node to an element.
198+
if(!e.isNull()) {
199+
if (e.nodeName()=="content") {
200+
const TContentTriggerType triggerType = TContentTriggerType(e.attribute("type","0").toInt());
201+
varData = getTriggerValue(e.text(), triggerType);
202+
isContext = true;
203+
} else if (e.nodeName()=="param") {
204+
205+
QString key = e.attribute("name");
206+
QString value = e.attribute("expr");
207+
const TContentTriggerType triggerType = TContentTriggerType(e.attribute("type","0").toInt());
208+
varMap[key] = getTriggerValue(value, triggerType);
209+
}
210+
}
211+
n = n.nextSibling();
212+
}
213+
214+
machine_ptr->submitEvent(docElem.attribute("name"), isContext ? varData : varMap);
215+
}
216+
217+
218+
}
219+
} );
220+
221+
QObject::connect(machine_ptr.get(), &QScxmlStateMachine::finished, &a, &QCoreApplication::quit);
222+
223+
machine_ptr->start();
224+
225+
iRes = a.exec();
226+
} catch (std::exception &e) {
227+
MLOG(ERROR) << QString(e.what()).toStdWString() << std::endl;
228+
iRes = -1;
229+
} catch (...) {
230+
MLOG(ERROR) << L"Unknown exception while executing a program!" << std::endl;
231+
iRes = -1;
232+
}
233+
234+
if (!settings.bHideDebugInfo) {
235+
MLOG(INFO) << L"Program is finished!" << std::endl;
236+
}
237+
238+
return iRes;
239+
}

0 commit comments

Comments
 (0)