forked from Serial-Studio/Serial-Studio
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGenerator.cpp
More file actions
447 lines (382 loc) · 13.2 KB
/
Generator.cpp
File metadata and controls
447 lines (382 loc) · 13.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
/*
* Copyright (c) 2020-2021 Alex Spataru <https://github.com/alex-spataru>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "Generator.h"
#include <Logger.h>
#include <CSV/Player.h>
#include <IO/Manager.h>
#include <Misc/Utilities.h>
#include <ConsoleAppender.h>
#include <QFileInfo>
#include <QFileDialog>
using namespace JSON;
/*
* Only instance of the class
*/
static Generator *INSTANCE = nullptr;
/*
* Regular expresion used to check if there are still unmatched values
* on the JSON map file.
*/
static const QRegExp UNMATCHED_VALUES_REGEX("(%\b([0-9]|[1-9][0-9])\b)");
/**
* Initializes the JSON Parser class and connects appropiate SIGNALS/SLOTS
*/
Generator::Generator()
: m_frameCount(0)
, m_opMode(kAutomatic)
{
auto io = IO::Manager::getInstance();
auto cp = CSV::Player::getInstance();
connect(cp, SIGNAL(openChanged()), this, SLOT(reset()));
connect(io, SIGNAL(deviceChanged()), this, SLOT(reset()));
connect(io, SIGNAL(frameReceived(QByteArray)), this, SLOT(readData(QByteArray)));
m_workerThread.start();
LOG_TRACE() << "Class initialized";
}
/**
* Returns the only instance of the class
*/
Generator *Generator::getInstance()
{
if (!INSTANCE)
INSTANCE = new Generator();
return INSTANCE;
}
/**
* Returns the JSON map data from the loaded file as a string
*/
QString Generator::jsonMapData() const
{
return m_jsonMapData;
}
/**
* Returns the file name (e.g. "JsonMap.json") of the loaded JSON map file
*/
QString Generator::jsonMapFilename() const
{
if (m_jsonMap.isOpen())
{
auto fileInfo = QFileInfo(m_jsonMap.fileName());
return fileInfo.fileName();
}
return "";
}
/**
* Returns the file path of the loaded JSON map file
*/
QString Generator::jsonMapFilepath() const
{
if (m_jsonMap.isOpen())
{
auto fileInfo = QFileInfo(m_jsonMap.fileName());
return fileInfo.filePath();
}
return "";
}
/**
* Returns the operation mode
*/
Generator::OperationMode Generator::operationMode() const
{
return m_opMode;
}
/**
* Creates a file dialog & lets the user select the JSON file map
*/
void Generator::loadJsonMap()
{
// clang-format off
auto file = QFileDialog::getOpenFileName(Q_NULLPTR,
tr("Select JSON map file"),
QDir::homePath(),
tr("JSON files") + " (*.json)");
// clang-format on
if (!file.isEmpty())
loadJsonMap(file);
}
/**
* Opens, validates & loads into memory the JSON file in the given @a path.
*/
void Generator::loadJsonMap(const QString &path, const bool silent)
{
// Log information
LOG_TRACE() << "Loading JSON file, silent flag set to" << silent;
// Validate path
if (path.isEmpty())
return;
// Close previous file (if open)
if (m_jsonMap.isOpen())
{
m_jsonMap.close();
emit jsonFileMapChanged();
}
// Try to open the file (read only mode)
m_jsonMap.setFileName(path);
if (m_jsonMap.open(QFile::ReadOnly))
{
// Read data & validate JSON from file
QJsonParseError error;
auto data = m_jsonMap.readAll();
auto document = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError)
{
LOG_TRACE() << "JSON parse error" << error.errorString();
m_jsonMap.close();
writeSettings("");
Misc::Utilities::showMessageBox(tr("JSON parse error"), error.errorString());
}
// JSON contains no errors, load data & save settings
else
{
LOG_TRACE() << "JSON map loaded successfully";
writeSettings(path);
m_jsonMapData = QString::fromUtf8(data);
if (!silent)
Misc::Utilities::showMessageBox(
tr("JSON map file loaded successfully!"),
tr("File \"%1\" loaded into memory").arg(jsonMapFilename()));
}
// Get rid of warnings
Q_UNUSED(document);
}
// Open error
else
{
LOG_TRACE() << "JSON file error" << m_jsonMap.errorString();
writeSettings("");
Misc::Utilities::showMessageBox(tr("Cannot read JSON file"),
tr("Please check file permissions & location"));
m_jsonMap.close();
}
// Update UI
emit jsonFileMapChanged();
}
/**
* Changes the operation mode of the JSON parser. There are two possible op.
* modes:
*
* @c kManual serial data only contains the comma-separated values, and we need
* to use a JSON map file (given by the user) to know what each value
* means. This method is recommended when we need to transfer &
* display a large amount of information from the microcontroller
* unit to the computer.
*
* @c kAutomatic serial data contains the JSON data frame, good for simple
* applications or for prototyping.
*/
void Generator::setOperationMode(const OperationMode mode)
{
m_opMode = mode;
emit operationModeChanged();
LOG_TRACE() << "Operation mode set to" << mode;
}
/**
* Loads the last saved JSON map file (if any)
*/
void Generator::readSettings()
{
auto path = m_settings.value("json_map_location", "").toString();
if (!path.isEmpty())
loadJsonMap(path, true);
}
/**
* Saves the location of the last valid JSON map file that was opened (if any)
*/
void Generator::writeSettings(const QString &path)
{
m_settings.setValue("json_map_location", path);
}
/**
* Notifies the rest of the application that a new JSON frame has been received. The JFI
* also contains RX date/time and frame number.
*
* Read the "FrameInfo.h" file for more information.
*/
void Generator::loadJFI(const JFI_Object &info)
{
bool csvOpen = CSV::Player::getInstance()->isOpen();
bool devOpen = IO::Manager::getInstance()->connected();
if (csvOpen || devOpen)
{
if (JFI_Valid(info))
emit jsonChanged(info);
}
else
reset();
}
/**
* Create a new JFI event with the given @a JSON document and increment the frame count
*/
void Generator::loadJSON(const QJsonDocument &json)
{
auto jfi = JFI_CreateNew(m_frameCount, QDateTime::currentDateTime(), json);
m_frameCount++;
loadJFI(jfi);
}
/**
* Resets all the statistics related to the current device and the JSON map file
*/
void Generator::reset()
{
m_frameCount = 0;
emit jsonChanged(JFI_Empty());
}
/**
* Tries to parse the given data as a JSON document according to the selected
* operation mode.
*
* Possible operation modes:
* - Auto: serial data contains the JSON data frame
* - Manual: serial data only contains the comma-separated values, and we need
* to use a JSON map file (given by the user) to know what each value
* means
*
* If JSON parsing is successfull, then the class shall notify the rest of the
* application in order to process packet data.
*/
void Generator::readData(const QByteArray &data)
{
// CSV-replay active, abort
if (CSV::Player::getInstance()->isOpen())
return;
// Data empty, abort
if (data.isEmpty())
return;
// Increment received frames
m_frameCount++;
// Create new worker thread to read JSON data
QThread *thread = new QThread;
JSONWorker *worker = new JSONWorker(data, m_frameCount, QDateTime::currentDateTime());
worker->moveToThread(thread);
connect(thread, SIGNAL(started()), worker, SLOT(process()));
connect(worker, SIGNAL(finished()), thread, SLOT(quit()));
connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
connect(worker, &JSONWorker::jsonReady, this, &Generator::loadJFI);
thread->start();
}
//----------------------------------------------------------------------------------------
// JSON worker object (executed for each frame on a new thread)
//----------------------------------------------------------------------------------------
/**
* Constructor function, stores received frame data & the date/time that the frame data
* was received.
*/
JSONWorker::JSONWorker(const QByteArray &data, const quint64 frame, const QDateTime &time)
: m_time(time)
, m_data(data)
, m_frame(frame)
, m_engine(nullptr)
{
}
/**
* Reads the frame & inserts its values on the JSON map, and/or extracts the JSON frame
* directly from the serial data.
*/
void JSONWorker::process()
{
// Init variables
QJsonParseError error;
QJsonDocument document;
// Serial device sends JSON (auto mode)
if (Generator::getInstance()->operationMode() == Generator::kAutomatic)
document = QJsonDocument::fromJson(m_data, &error);
// We need to use a map file, check if its loaded & replace values into map
else
{
// Initialize javscript engine
m_engine = new QJSEngine(this);
// Empty JSON map data
if (Generator::getInstance()->jsonMapData().isEmpty())
return;
// Init conversion status boolean
bool ok = true;
// Separate incoming data & add it to the JSON map
auto json = Generator::getInstance()->jsonMapData();
auto list = QString::fromUtf8(m_data).split(',');
for (int i = 0; i < list.count(); ++i)
{
// Get value at i & insert it into json
auto str = list.at(i);
auto mod = json.arg(str);
// If JSON after insertion is different we're good to go
if (json != mod)
json = mod;
// JSON is the same after insertion -> format error
else
{
ok = false;
break;
}
}
// Test that JSON does not contain unmatched values
if (ok)
ok = !(json.contains(UNMATCHED_VALUES_REGEX));
// There was an error & the JSON map is incomplete (or misses received
// info from the microcontroller).
if (!ok)
return;
// Create json document
auto jsonDocument = QJsonDocument::fromJson(json.toUtf8(), &error);
// Calculate dynamically generated values
auto root = jsonDocument.object();
auto groups = root.value("g").toArray();
for (int i = 0; i < groups.count(); ++i)
{
// Get group
auto group = groups.at(i).toObject();
// Evaluate each dataset of the current group
auto datasets = group.value("d").toArray();
for (int j = 0; j < datasets.count(); ++j)
{
// Get dataset object & value
auto dataset = datasets.at(j).toObject();
auto value = dataset.value("v").toString();
// Evaluate code in dataset value (if any)
auto jsValue = m_engine->evaluate(value);
// Code execution correct, replace value in JSON
if (!jsValue.isError())
{
dataset.remove("v");
dataset.insert("v", jsValue.toString());
datasets.replace(j, dataset);
}
}
// Replace group datasets
group.remove("d");
group.insert("d", datasets);
groups.replace(i, group);
}
// Replace root document group objects
root.remove("g");
root.insert("g", groups);
// Create JSON document
document = QJsonDocument(root);
// Delete javacript engine
m_engine->deleteLater();
}
// No parse error, update UI & reset error counter
if (error.error == QJsonParseError::NoError)
emit jsonReady(JFI_CreateNew(m_frame, m_time, document));
// Delete object in 500 ms
QTimer::singleShot(500, this, SIGNAL(finished()));
}