1 /*
2  * Stellarium TelescopeControl Plug-in
3  *
4  * Copyright (C) 2009-2011 Bogdan Marinov (this file)
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335, USA.
19  */
20 
21 #include "TelescopeConfigurationDialog.hpp"
22 #include "Dialog.hpp"
23 #include "StelApp.hpp"
24 #include "StelCore.hpp"
25 #include "StelFileMgr.hpp"
26 #include "StelLocaleMgr.hpp"
27 #include "StelModuleMgr.hpp"
28 #include "StelStyle.hpp"
29 #include "StelTranslator.hpp"
30 #include "TelescopeControl.hpp"
31 #include "ui_telescopeConfigurationDialog.h"
32 
33 #ifdef Q_OS_WIN
34 	#include "../common/ASCOMSupport.hpp"
35 #endif
36 
37 #include <QCompleter>
38 #include <QDebug>
39 #include <QDir>
40 #include <QFile>
41 #include <QFrame>
42 #include <QTimer>
43 #include <QRegularExpression>
44 #include <QtSerialPort/QSerialPortInfo>
45 
TelescopeConfigurationDialog()46 TelescopeConfigurationDialog::TelescopeConfigurationDialog()
47 	: StelDialog("TelescopeControlConfiguration"), configuredSlot(0)
48 {
49 	ui = new Ui_telescopeConfigurationDialog();
50 
51 	telescopeManager = GETSTELMODULE(TelescopeControl);
52 
53 	telescopeNameValidator = new QRegularExpressionValidator(QRegularExpression("[^:\"]+"), this); // Test the update for JSON
54 	hostNameValidator =
55 	  new QRegularExpressionValidator(QRegularExpression("[a-zA-Z0-9\\-\\.]+"), this); // TODO: Write a proper host/IP regexp?
56 	circleListValidator = new QRegularExpressionValidator(QRegularExpression("[0-9,\\.\\s]+"), this);
57 #ifdef Q_OS_WIN
58 	serialPortValidator = new QRegularExpressionValidator(QRegularExpression("COM[0-9]+"), this);
59 #else
60 	serialPortValidator = new QRegularExpressionValidator(QRegularExpression("/.*"), this);
61 #endif
62 }
63 
~TelescopeConfigurationDialog()64 TelescopeConfigurationDialog::~TelescopeConfigurationDialog()
65 {
66 	delete ui;
67 	delete telescopeNameValidator;
68 	delete hostNameValidator;
69 	delete circleListValidator;
70 	delete serialPortValidator;
71 }
72 
listSerialPorts()73 QStringList* TelescopeConfigurationDialog::listSerialPorts()
74 {
75 	// list real serial ports
76 	QStringList* plist = new QStringList();
77 	for (const auto& serialPortInfo : QSerialPortInfo::availablePorts())
78 	{
79 #ifdef Q_OS_WIN
80 		plist->append(serialPortInfo.portName()); // Use COM1 in the GUI instead \\.\COM1 naming
81 #else
82 		plist->append(serialPortInfo.systemLocation());
83 #endif
84 		qDebug() << "[TelescopeControl] port name:" << serialPortInfo.portName()
85 				 << "; vendor identifier:" << serialPortInfo.vendorIdentifier()
86 				 << "; product identifier:" << serialPortInfo.productIdentifier();
87 	}
88 
89 // on linux find some virtual ports
90 #ifdef Q_OS_LINUX
91 	QStringList filters;
92 	filters << "ttyNET*"
93 			<< "ttynet*"
94 			<< "Telescope*";
95 	// look in /dev/*
96 	QDir dev("/dev");
97 	dev.setFilter(QDir::System);
98 	dev.setSorting(QDir::Reversed);
99 	dev.setNameFilters(filters);
100 	QFileInfoList list = dev.entryInfoList();
101 	for (int i = 0; i < list.size(); i++)
102 	{
103 		QFileInfo fileInfo = list.at(i);
104 		plist->append(fileInfo.absoluteFilePath());
105 	}
106 	// look in /tmp/* for non-root virtual ports (append ttyS8 and ttyUSB*)
107 	filters << "ttyS*"
108 			<< "ttyUSB*";
109 	QDir tmp("/tmp");
110 	tmp.setFilter(QDir::System);
111 	tmp.setSorting(QDir::Reversed);
112 	tmp.setNameFilters(filters);
113 	list = tmp.entryInfoList();
114 	for (int i = 0; i < list.size(); i++)
115 	{
116 		QFileInfo fileInfo = list.at(i);
117 		plist->append(fileInfo.absoluteFilePath());
118 	}
119 #endif
120 
121 	return plist;
122 }
123 
retranslate()124 void TelescopeConfigurationDialog::retranslate()
125 {
126 	if (dialog)
127 	{
128 		ui->retranslateUi(dialog);
129 		populateToolTips();
130 	}
131 }
132 
133 // Initialize the dialog widgets and connect the signals/slots
createDialogContent()134 void TelescopeConfigurationDialog::createDialogContent()
135 {
136 	ui->setupUi(dialog);
137 
138 	// ASCOM Telescope client widget needs to be dynamically added in order to make use of preprocessors to exclude for non-windows
139 	#ifdef Q_OS_WIN
140 	ascomWidget = new TelescopeClientASCOMWidget(ui->scrollAreaWidgetContents);
141 	ui->ASCOMLayout->addWidget(ascomWidget);
142 
143 	if (!ASCOMSupport::isASCOMSupported())
144 	{
145 		ui->radioButtonTelescopeASCOM->hide();
146 	}
147 	#endif
148 
149 	// Inherited connect
150 	connect(&StelApp::getInstance(), SIGNAL(languageChanged()), this, SLOT(retranslate()));
151 	connect(ui->closeStelWindow, SIGNAL(clicked()), this, SLOT(buttonDiscardPressed()));
152 	connect(ui->TitleBar, SIGNAL(movedTo(QPoint)), this, SLOT(handleMovedTo(QPoint)));
153 	connect(dialog, SIGNAL(rejected()), this, SLOT(buttonDiscardPressed()));
154 
155 	// Connect: sender, signal, receiver, member
156 	connect(ui->radioButtonTelescopeLocal, SIGNAL(toggled(bool)), this, SLOT(toggleTypeLocal(bool)));
157 	connect(ui->radioButtonTelescopeConnection, SIGNAL(toggled(bool)), this, SLOT(toggleTypeConnection(bool)));
158 	connect(ui->radioButtonTelescopeVirtual, SIGNAL(toggled(bool)), this, SLOT(toggleTypeVirtual(bool)));
159 	connect(ui->radioButtonTelescopeRTS2, SIGNAL(toggled(bool)), this, SLOT(toggleTypeRTS2(bool)));
160 	connect(ui->radioButtonTelescopeINDI, SIGNAL(toggled(bool)), this, SLOT(toggleTypeINDI(bool)));
161 	#ifdef Q_OS_WIN
162 	connect(ui->radioButtonTelescopeASCOM, SIGNAL(toggled(bool)), this, SLOT(toggleTypeASCOM(bool)));
163 	#else
164 	ui->radioButtonTelescopeASCOM->hide();
165 	#endif
166 
167 	connect(ui->pushButtonSave, SIGNAL(clicked()), this, SLOT(buttonSavePressed()));
168 	connect(ui->pushButtonDiscard, SIGNAL(clicked()), this, SLOT(buttonDiscardPressed()));
169 
170 	connect(ui->comboBoxDeviceModel, SIGNAL(currentIndexChanged(const QString&)), this,
171 	  SLOT(deviceModelSelected(const QString&)));
172 
173 	// Setting validators
174 	ui->lineEditTelescopeName->setValidator(telescopeNameValidator);
175 	ui->lineEditHostName->setValidator(hostNameValidator);
176 	ui->lineEditCircleList->setValidator(circleListValidator);
177 	ui->comboSerialPort->setValidator(serialPortValidator);
178 
179 	populateToolTips();
180 }
181 
populateToolTips()182 void TelescopeConfigurationDialog::populateToolTips()
183 {
184 	ui->doubleSpinBoxTelescopeDelay->setToolTip(
185 	  QString("<p>%1</p>")
186 		.arg(q_("The approximate time it takes for the signals from the telescope to reach Stellarium. "
187 				"Increase this value if the reticle is skipping.")));
188 	ui->doubleSpinBoxRTS2Refresh->setToolTip(
189 	  QString("<p>%1</p>")
190 		.arg(q_("Refresh rate of the RTS2 telescope. Delay before sending next telescope status request. The "
191 				"default value of 0.5 second works fine with most setups.")));
192 }
193 
194 // Set the configuration panel in a predictable state
initConfigurationDialog()195 void TelescopeConfigurationDialog::initConfigurationDialog()
196 {
197 	ui->groupBoxConnectionSettings->hide();
198 	ui->groupBoxDeviceSettings->hide();
199 	ui->groupBoxRTS2Settings->hide();
200 	ui->INDIProperties->hide();
201 	#ifdef Q_OS_WIN
202 	ascomWidget->hide();
203 	#endif
204 
205 	// Reusing code used in both methods that call this one
206 	deviceModelNames = telescopeManager->getDeviceModels().keys();
207 
208 	// Name
209 	ui->lineEditTelescopeName->clear();
210 
211 	// Equinox
212 	ui->radioButtonJ2000->setChecked(true);
213 
214 	// Connect at startup
215 	ui->checkBoxConnectAtStartup->setChecked(false);
216 
217 	// Serial port
218 	QStringList* plist = listSerialPorts();
219 	ui->comboSerialPort->clear();
220 	ui->comboSerialPort->addItems(*plist);
221 	ui->comboSerialPort->activated(plist->value(0));
222 	ui->comboSerialPort->setEditText(plist->value(0));
223 	delete (plist);
224 
225 	// Populating the list of available devices
226 	ui->comboBoxDeviceModel->clear();
227 	if (!deviceModelNames.isEmpty())
228 	{
229 		deviceModelNames.sort();
230 		ui->comboBoxDeviceModel->addItems(deviceModelNames);
231 	}
232 	ui->comboBoxDeviceModel->setCurrentIndex(0);
233 
234 	// FOV circles
235 	ui->checkBoxCircles->setChecked(false);
236 	ui->lineEditCircleList->clear();
237 
238 	// It is very unlikely that this situation will happen any more due to the
239 	// embedded telescope servers.
240 	if (deviceModelNames.isEmpty())
241 	{
242 		ui->radioButtonTelescopeLocal->setEnabled(false);
243 		ui->radioButtonTelescopeConnection->setChecked(true);
244 		toggleTypeConnection(true); // Not called if the button is already checked
245 	}
246 	else
247 	{
248 		ui->radioButtonTelescopeLocal->setEnabled(true);
249 		ui->radioButtonTelescopeLocal->setChecked(true);
250 		toggleTypeLocal(true); // Not called if the button is already checked
251 	}
252 }
253 
initNewTelescopeConfiguration(int slot)254 void TelescopeConfigurationDialog::initNewTelescopeConfiguration(int slot)
255 {
256 	configuredSlot = slot;
257 	initConfigurationDialog();
258 	ui->stelWindowTitle->setText(q_("Add New Telescope"));
259 	ui->lineEditTelescopeName->setText(QString("New Telescope %1").arg(QString::number(configuredSlot)));
260 
261 	ui->doubleSpinBoxTelescopeDelay->setValue(SECONDS_FROM_MICROSECONDS(DEFAULT_DELAY));
262 }
263 
initExistingTelescopeConfiguration(int slot)264 void TelescopeConfigurationDialog::initExistingTelescopeConfiguration(int slot)
265 {
266 	configuredSlot = slot;
267 	initConfigurationDialog();
268 	ui->stelWindowTitle->setText(q_("Configure Telescope"));
269 
270 	// Read the telescope properties
271 	QString name;
272 	ConnectionType connectionType;
273 	QString equinox;
274 	QString host;
275 	int portTCP;
276 	int delay;
277 	bool connectAtStartup;
278 	QList<double> circles;
279 	QString deviceModelName;
280 	QString serialPortName;
281 	QString rts2Url;
282 	QString rts2Username;
283 	QString rts2Password;
284 	int rts2Refresh;
285 	QString ascomDeviceId;
286 	bool ascomUseDeviceEqCoordType;
287 
288 	if (!telescopeManager->getTelescopeAtSlot(slot, connectionType, name, equinox, host, portTCP, delay,
289 		  connectAtStartup, circles, deviceModelName, serialPortName, rts2Url, rts2Username, rts2Password,
290 		  rts2Refresh, ascomDeviceId, ascomUseDeviceEqCoordType))
291 	{
292 		// TODO: Add debug
293 		return;
294 	}
295 	ui->lineEditTelescopeName->setText(name);
296 
297 	if (connectionType == ConnectionInternal && !deviceModelName.isEmpty())
298 	{
299 		ui->radioButtonTelescopeLocal->setChecked(true);
300 		ui->lineEditHostName->setText("localhost"); // TODO: Remove magic word!
301 
302 		// Make the current device model selected in the list
303 		int index = ui->comboBoxDeviceModel->findText(deviceModelName);
304 		if (index < 0)
305 		{
306 			qDebug() << "TelescopeConfigurationDialog: Current device model is not in the list?";
307 			emit changesDiscarded();
308 			return;
309 		}
310 		else
311 			ui->comboBoxDeviceModel->setCurrentIndex(index);
312 
313 		// Initialize the serial port value
314 		ui->comboSerialPort->activated(serialPortName);
315 		ui->comboSerialPort->setEditText(serialPortName);
316 	}
317 	else if (connectionType == ConnectionRemote)
318 	{
319 		ui->radioButtonTelescopeConnection->setChecked(true); // Calls toggleTypeConnection(true)
320 		ui->lineEditHostName->setText(host);
321 	}
322 	else if (connectionType == ConnectionLocal)
323 	{
324 		ui->radioButtonTelescopeConnection->setChecked(true);
325 		ui->lineEditHostName->setText("localhost");
326 	}
327 	else if (connectionType == ConnectionVirtual)
328 	{
329 		ui->radioButtonTelescopeVirtual->setChecked(true);
330 	}
331 	else if (connectionType == ConnectionRTS2)
332 	{
333 		ui->radioButtonTelescopeRTS2->setChecked(true);
334 		ui->lineEditRTS2Url->setText(rts2Url);
335 		ui->lineEditRTS2Username->setText(rts2Username);
336 		ui->lineEditRTS2Password->setText(rts2Password);
337 		ui->doubleSpinBoxRTS2Refresh->setValue(SECONDS_FROM_MICROSECONDS(rts2Refresh));
338 	}
339 	else if (connectionType == ConnectionINDI)
340 	{
341 		ui->radioButtonTelescopeINDI->setChecked(true);
342 		ui->INDIProperties->setHost(host);
343 		ui->INDIProperties->setPort(portTCP);
344 		ui->INDIProperties->setSelectedDevice(deviceModelName);
345 	}
346 	#ifdef Q_OS_WIN
347 	else if (connectionType == ConnectionASCOM)
348 	{
349 		ui->radioButtonTelescopeASCOM->setChecked(true);
350 		ascomWidget->setSelectedDevice(ascomDeviceId);
351 		ascomWidget->setUseDeviceEqCoordType(ascomUseDeviceEqCoordType);
352 	}
353 	#endif
354 
355 	// Equinox
356 	if (equinox == "JNow")
357 		ui->radioButtonJNow->setChecked(true);
358 	else
359 		ui->radioButtonJ2000->setChecked(true);
360 
361 	// Circles
362 	if (!circles.isEmpty())
363 	{
364 		ui->checkBoxCircles->setChecked(true);
365 
366 		QStringList circleList;
367 		for (int i = 0; i < circles.size(); i++)
368 			circleList.append(QString::number(circles[i]));
369 		ui->lineEditCircleList->setText(circleList.join(", "));
370 	}
371 
372 	// TCP port
373 	ui->spinBoxTCPPort->setValue(portTCP);
374 
375 	// Delay
376 	ui->doubleSpinBoxTelescopeDelay->setValue(SECONDS_FROM_MICROSECONDS(delay)); // Microseconds to seconds
377 
378 	// Connect at startup
379 	ui->checkBoxConnectAtStartup->setChecked(connectAtStartup);
380 }
381 
toggleTypeLocal(bool isChecked)382 void TelescopeConfigurationDialog::toggleTypeLocal(bool isChecked)
383 {
384 	if (isChecked)
385 	{
386 		// Re-initialize values that may have been changed
387 		ui->comboBoxDeviceModel->setCurrentIndex(0);
388 		QStringList* plist = listSerialPorts();
389 		ui->comboSerialPort->activated(plist->value(0));
390 		ui->comboSerialPort->setEditText(plist->value(0));
391 		delete (plist);
392 		ui->lineEditHostName->setText("localhost");
393 		ui->spinBoxTCPPort->setValue(DEFAULT_TCP_PORT_FOR_SLOT(configuredSlot));
394 
395 		ui->groupBoxDeviceSettings->show();
396 
397 		ui->scrollArea->ensureWidgetVisible(ui->groupBoxTelescopeProperties);
398 	}
399 	else
400 	{
401 		ui->groupBoxDeviceSettings->hide();
402 	}
403 }
404 
toggleTypeConnection(bool isChecked)405 void TelescopeConfigurationDialog::toggleTypeConnection(bool isChecked)
406 {
407 	if (isChecked)
408 	{
409 		// Re-initialize values that may have been changed
410 		ui->lineEditHostName->setText("localhost");
411 		ui->spinBoxTCPPort->setValue(DEFAULT_TCP_PORT_FOR_SLOT(configuredSlot));
412 
413 		ui->groupBoxConnectionSettings->show();
414 
415 		ui->scrollArea->ensureWidgetVisible(ui->groupBoxTelescopeProperties);
416 	}
417 	else
418 	{
419 		ui->groupBoxConnectionSettings->hide();
420 	}
421 }
422 
toggleTypeVirtual(bool isChecked)423 void TelescopeConfigurationDialog::toggleTypeVirtual(bool isChecked)
424 {
425 	Q_UNUSED(isChecked);
426 	ui->scrollArea->ensureWidgetVisible(ui->groupBoxTelescopeProperties);
427 }
428 
toggleTypeRTS2(bool isChecked)429 void TelescopeConfigurationDialog::toggleTypeRTS2(bool isChecked)
430 {
431 	if (isChecked)
432 	{
433 		// Re-initialize values that may have been changed
434 		ui->lineEditRTS2Url->setText("localhost:8889");
435 
436 		ui->groupBoxRTS2Settings->show();
437 
438 		ui->scrollArea->ensureWidgetVisible(ui->groupBoxRTS2Settings);
439 	}
440 	else
441 	{
442 		ui->groupBoxRTS2Settings->hide();
443 	}
444 }
445 
toggleTypeINDI(bool enabled)446 void TelescopeConfigurationDialog::toggleTypeINDI(bool enabled)
447 {
448 	ui->INDIProperties->setVisible(enabled);
449 }
450 
451 #ifdef Q_OS_WIN
toggleTypeASCOM(bool enabled)452 void TelescopeConfigurationDialog::toggleTypeASCOM(bool enabled)
453 {
454 	ascomWidget->setVisible(enabled);
455 }
456 #endif
457 
buttonSavePressed()458 void TelescopeConfigurationDialog::buttonSavePressed()
459 {
460 	// Main telescope properties
461 	QString name = ui->lineEditTelescopeName->text().trimmed();
462 
463 	if (name.isEmpty()) return;
464 
465 	QString host = ui->lineEditHostName->text();
466 
467 	if (host.isEmpty()) // Remove validation of hostname
468 		return;
469 
470 	int delay = qRound(MICROSECONDS_FROM_SECONDS(ui->doubleSpinBoxTelescopeDelay->value()));
471 	int portTCP = ui->spinBoxTCPPort->value();
472 	bool connectAtStartup = ui->checkBoxConnectAtStartup->isChecked();
473 
474 	// Circles
475 	// TODO: This will change if there is a validator for that field
476 	QList<double> circles;
477 	QString rawCircles = ui->lineEditCircleList->text().trimmed();
478 	QStringList circleStrings;
479 	if (ui->checkBoxCircles->isChecked() && !(rawCircles.isEmpty()))
480 	{
481 		#if (QT_VERSION>=QT_VERSION_CHECK(5, 14, 0))
482 		circleStrings = rawCircles.simplified().remove(' ').split(',', Qt::SkipEmptyParts);
483 		#else
484 		circleStrings = rawCircles.simplified().remove(' ').split(',', QString::SkipEmptyParts);
485 		#endif
486 		circleStrings.removeDuplicates();
487 		circleStrings.sort();
488 
489 		for (int i = 0; i < circleStrings.size(); i++)
490 		{
491 			if (i >= MAX_CIRCLE_COUNT) break;
492 			double circle = circleStrings.at(i).toDouble();
493 			if (circle > 0.0) circles.append(circle);
494 		}
495 	}
496 
497 	QString equinox("J2000");
498 	if (ui->radioButtonJNow->isChecked()) equinox = "JNow";
499 
500 	// Type and server properties
501 	// TODO: When adding, check for success!
502 	ConnectionType type = ConnectionNA;
503 	if (ui->radioButtonTelescopeLocal->isChecked())
504 	{
505 		// Read the serial port
506 		QString serialPortName = ui->comboSerialPort->currentText();
507 		type = ConnectionInternal;
508 		telescopeManager->addTelescopeAtSlot(configuredSlot, type, name, equinox, host, portTCP, delay,
509 		  connectAtStartup, circles, ui->comboBoxDeviceModel->currentText(), serialPortName);
510 	}
511 	else if (ui->radioButtonTelescopeConnection->isChecked())
512 	{
513 		if (host == "localhost")
514 			type = ConnectionLocal;
515 		else
516 			type = ConnectionRemote;
517 		telescopeManager->addTelescopeAtSlot(
518 		  configuredSlot, type, name, equinox, host, portTCP, delay, connectAtStartup, circles);
519 	}
520 	else if (ui->radioButtonTelescopeVirtual->isChecked())
521 	{
522 		type = ConnectionVirtual;
523 		telescopeManager->addTelescopeAtSlot(
524 		  configuredSlot, type, name, equinox, QString(), portTCP, delay, connectAtStartup, circles);
525 	}
526 	else if (ui->radioButtonTelescopeRTS2->isChecked())
527 	{
528 		type = ConnectionRTS2;
529 		telescopeManager->addTelescopeAtSlot(configuredSlot, type, name, equinox, host, portTCP, delay,
530 		  connectAtStartup, circles, QString(), QString(), ui->lineEditRTS2Url->text(),
531 		  ui->lineEditRTS2Username->text(), ui->lineEditRTS2Password->text(),
532 		  qRound(MICROSECONDS_FROM_SECONDS(ui->doubleSpinBoxRTS2Refresh->value())));
533 	}
534 	else if (ui->radioButtonTelescopeINDI->isChecked())
535 	{
536 		type = ConnectionINDI;
537 		telescopeManager->addTelescopeAtSlot(configuredSlot, type, name, equinox, ui->INDIProperties->host(),
538 		  ui->INDIProperties->port(), delay, connectAtStartup, circles, ui->INDIProperties->selectedDevice());
539 	}
540 	#ifdef Q_OS_WIN
541 	else if (ui->radioButtonTelescopeASCOM->isChecked())
542 	{
543 		type = ConnectionASCOM;
544 		telescopeManager->addTelescopeAtSlot(configuredSlot, type, name, equinox, host, portTCP, delay,
545 		  connectAtStartup, circles, QString(), QString(), QString(), QString(), QString(), -1,
546 		  ascomWidget->selectedDevice(), ascomWidget->useDeviceEqCoordType());
547 	}
548 	#endif
549 
550 	emit changesSaved(name, type);
551 }
552 
buttonDiscardPressed()553 void TelescopeConfigurationDialog::buttonDiscardPressed()
554 {
555 	emit changesDiscarded();
556 }
557 
deviceModelSelected(const QString & deviceModelName)558 void TelescopeConfigurationDialog::deviceModelSelected(const QString& deviceModelName)
559 {
560 	ui->labelDeviceModelDescription->setText(
561 	  q_(telescopeManager->getDeviceModels().value(deviceModelName).description));
562 	ui->doubleSpinBoxTelescopeDelay->setValue(
563 	  SECONDS_FROM_MICROSECONDS(telescopeManager->getDeviceModels().value(deviceModelName).defaultDelay));
564 }
565