653 lines
18 KiB
C++
653 lines
18 KiB
C++
#include "atb_system.h"
|
|
#include "ATBHMIconfig.h"
|
|
#include <QDebug>
|
|
#include <QProcess>
|
|
#include <QFile>
|
|
#include <QDir>
|
|
#include <QFileInfo>
|
|
#include <QTextStream>
|
|
#include <QString>
|
|
#include <QTimer>
|
|
|
|
|
|
ATB_system::ATB_system(ATBHMIconfig *config, QObject *parent) :
|
|
QObject(parent)
|
|
{
|
|
|
|
this->config = config;
|
|
|
|
this->tf_gpio = nullptr;
|
|
|
|
#if defined (ARCH_PTU2)
|
|
if (config->hasFeatureTF()) init_touch_feedback();
|
|
#elif defined (ARCH_PTU4)
|
|
if (config->hasFeatureTF()) init_touch_feedback();
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
ATB_system::~ATB_system()
|
|
{
|
|
if (config->hasFeatureTF()) {
|
|
delete(this->tf_gpio_outstream);
|
|
delete(this->tf_gpio);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void ATB_system::executeSystemCommand(quint16 cmd, QByteArray data)
|
|
{
|
|
Q_UNUSED(data);
|
|
|
|
switch (cmd) {
|
|
case SYS_COMMAND_SLEEP:
|
|
qDebug() << "ATB_system::executeSystemCommand(): " << cmd;
|
|
dbus_permitSuspend();
|
|
break;
|
|
case SYS_COMMAND_DIMLOW:
|
|
if (config->hasFeatureDBusDisplayControl()) {
|
|
dbus_DimControlStart();
|
|
} else {
|
|
this->DimLow();
|
|
}
|
|
break;
|
|
case SYS_COMMAND_DIMHIGH:
|
|
if (config->hasFeatureDBusDisplayControl()) {
|
|
dbus_DimHighPermanent();
|
|
} else {
|
|
this->DimHigh();
|
|
}
|
|
break;
|
|
case SYS_COMMAND_BEEP:
|
|
if (config->hasFeatureTF()) this->Beep();
|
|
break;
|
|
case SYS_COMMAND_MACHINE_NUMBER: // deprecated and not used
|
|
// setMachineNumber(data);
|
|
break;
|
|
case SYS_COMMAND_LED_CONTROL:
|
|
this->privateConfigLED(data);
|
|
break;
|
|
case SYS_COMMAND_HALT:
|
|
this->halt();
|
|
break;
|
|
case SYS_COMMAND_REBOOT:
|
|
this->reboot();
|
|
break;
|
|
default:
|
|
qDebug() << "ATB_system::executeSystemCommand(): unknown command: " << cmd;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* stubs for display control
|
|
*
|
|
* this methodes should later be scriptable an call corresponding methodes
|
|
* in DisplayControl via DBus.
|
|
*/
|
|
|
|
void ATB_system::DimLow() { return; }
|
|
|
|
void ATB_system::DimHigh()
|
|
{
|
|
//if (config->hasFeatureDC())
|
|
// this->dc->DimHigh();
|
|
}
|
|
|
|
//void ATB_system::Dim(quint8 value) { if (config->hasFeatureDC()) this->dc->Dim((uchar)value); }
|
|
|
|
void ATB_system::DimStart() { return; }
|
|
void ATB_system::DimStop() { return; }
|
|
|
|
|
|
/*****************************************************************************
|
|
*/
|
|
|
|
void ATB_system::Beep() {
|
|
qDebug() << "ATB_system::Beep(): now i should make a beep!";
|
|
#if defined (ARCH_PTU2) || defined (ARCH_PTU4)
|
|
QTimer::singleShot (BEEPTIMEOUT, this, SLOT(BeepStop()) );
|
|
*(this->tf_gpio_outstream) << config->getTouchFeedbackOnValue();
|
|
this->tf_gpio_outstream->flush();
|
|
#endif
|
|
}
|
|
|
|
void ATB_system::BeepStop() {
|
|
#if defined (ARCH_PTU2) || defined (ARCH_PTU4)
|
|
*(this->tf_gpio_outstream) << config->getTouchFeedbackOffValue();
|
|
this->tf_gpio_outstream->flush();
|
|
#endif
|
|
}
|
|
|
|
|
|
/********************************************************************************
|
|
* set date/time
|
|
*
|
|
* Datestring format: YYYY-MM-DD hh:mm[:ss]
|
|
* This is a format that is accepted both by busybox-date (e.g. PTU2) and by
|
|
* coreutils-date.
|
|
*
|
|
* example:
|
|
* /bin/date -s "2015-08-05 17:55:00"
|
|
*
|
|
*/
|
|
void ATB_system::setDateTime(const QString & dateTimeString)
|
|
{
|
|
QString datetimeCMD = "/bin/date -s " + dateTimeString;
|
|
|
|
#if defined (ARCH_PTU2) || defined (ARCH_PTU4)
|
|
QProcess::startDetached(datetimeCMD);
|
|
|
|
// the following does not work on PTU2:
|
|
//QProcess::startDetached ("/sbin/hwclock -w");
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/********************************************************************************
|
|
* static functions to write single values to files.
|
|
*
|
|
* Used e.g. to store some machine specific settings like customer number or machine
|
|
* number in filesystem.
|
|
*
|
|
* This data should be updated rarely and the calling function must keep this in mind!
|
|
*
|
|
* Note: QFile::open() creates a file, if it is not existing.
|
|
*/
|
|
|
|
quint8 ATB_system::setPSAConfigInt(const QString &filename, quint16 iValue)
|
|
{
|
|
return setPSAConfigString(filename, QString::number(iValue));
|
|
}
|
|
|
|
|
|
quint8 ATB_system::setPSAConfigString(const QString & filename, const QString & sValue)
|
|
{
|
|
#if defined (ARCH_PTU2) || defined (ARCH_PTU4)
|
|
QFile file(filename);
|
|
#else
|
|
QFile file(QDir::homePath() + filename);
|
|
#endif
|
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
|
qDebug() << "ATB_system::setPSAConfigString() cannot open file: " << filename;
|
|
qDebug() << " file.errorString() = " << file.errorString();
|
|
return 0;
|
|
}
|
|
|
|
qDebug() << "ATB_system::setPSAConfigString() write file: " << filename;
|
|
|
|
QTextStream out(&file);
|
|
out << sValue;
|
|
out.flush();
|
|
|
|
file.close();
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
quint16 ATB_system::readPSAConfigInt(const QString & filename)
|
|
{
|
|
bool ok;
|
|
quint16 result = (quint16)readPSAConfigString(filename).toInt(&ok);
|
|
|
|
if (!ok) {
|
|
result = 0;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
QString ATB_system::readPSAConfigString(const QString & filename)
|
|
{
|
|
#if defined (ARCH_PTU2) || defined (ARCH_PTU4)
|
|
QFileInfo fileinfo(filename);
|
|
#else
|
|
QFileInfo fileinfo(QDir::homePath() + filename);
|
|
#endif
|
|
if (! fileinfo.isReadable() ) {
|
|
qDebug() << "ATB_system::readPSAConfigString(): \"" << filename << "\" is not readable";
|
|
return 0;
|
|
}
|
|
|
|
#if defined (ARCH_PTU2) || defined (ARCH_PTU4)
|
|
QFile file(filename);
|
|
#else
|
|
QFile file(QDir::homePath() + filename);
|
|
#endif
|
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
qDebug() << "ATB_system::readPSAConfigString() cannot open file: " << filename;
|
|
return 0;
|
|
}
|
|
|
|
QTextStream in(&file);
|
|
|
|
QString stringValue = in.readLine(100);
|
|
qDebug() << "ATB_system::readPSAConfigString() stringValue = " << stringValue;
|
|
|
|
file.close();
|
|
|
|
return stringValue;
|
|
}
|
|
|
|
|
|
/********************************************************************************
|
|
* LED control interface
|
|
*
|
|
*/
|
|
void ATB_system::onConfigLED(LED_NAME LED, LED_TRIGGER trigger, quint16 delayOn, quint16 delayOff)
|
|
{
|
|
QString LEDPath = "/sys/class/leds/";
|
|
QString LEDName = "";
|
|
QString LEDtrigger = "";
|
|
|
|
// DEBUG
|
|
|
|
|
|
|
|
// ////////
|
|
|
|
|
|
switch (LED) {
|
|
#if defined (ARCH_PTU2)
|
|
case ATB_LED1: LEDName.append("atb_led1"); break;
|
|
case ATB_LED2: LEDName.append("atb_led2"); break;
|
|
case ATB_LED3: LEDName.append("atb_led3"); break;
|
|
case ATB_LED4: LEDName.append("atb_led4"); break;
|
|
#elif defined (ARCH_PTU4)
|
|
case ATB_LED1: LEDName.append("D504"); break;
|
|
case ATB_LED2: LEDName.append("D503"); break;
|
|
case ATB_LED3: LEDName.append("D502"); break;
|
|
case ATB_LED4: LEDName.append("D501"); break;
|
|
#endif
|
|
default: return;
|
|
}
|
|
|
|
switch (trigger) {
|
|
case LED_TRIGGER_NONE: LEDtrigger.append("none"); break;
|
|
case LED_TRIGGER_TIMER: LEDtrigger.append("timer"); break;
|
|
#if defined (ARCH_PTU2)
|
|
case LED_TRIGGER_DEFAULT_ON: LEDtrigger.append("default-on"); break;
|
|
#elif defined (ARCH_PTU4)
|
|
/* note: ptu4 leds currently do not support 'default-on' trigger
|
|
* workaround is to set delay_off to '0'
|
|
*/
|
|
case LED_TRIGGER_DEFAULT_ON: LEDtrigger.append("timer");
|
|
trigger = LED_TRIGGER_TIMER;
|
|
delayOn = 1;
|
|
delayOff = 0;
|
|
break;
|
|
#endif
|
|
default: return;
|
|
}
|
|
|
|
QString filename = LEDPath + LEDName + "/trigger";
|
|
|
|
// set trigger
|
|
this->privateSetLEDTrigger(filename, LEDtrigger);
|
|
|
|
|
|
// set delays for trigger 'timer'
|
|
if (trigger == LED_TRIGGER_TIMER) {
|
|
|
|
// set delay on
|
|
if (delayOn != 0) {
|
|
filename = LEDPath + LEDName + "/delay_on";
|
|
this->privateSetLEDDelayOn(filename, QString::number(delayOn));
|
|
}
|
|
|
|
// set delay off
|
|
if (delayOff != 0) {
|
|
filename = LEDPath + LEDName + "/delay_off";
|
|
this->privateSetLEDDelayOn(filename, QString::number(delayOff));
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* wrapper function for LED control.
|
|
*
|
|
* TODO: Test, check: plausibillity, cmd.lenght(), type casts to LED_NAME, LED_TRIGGER
|
|
*/
|
|
void ATB_system::privateConfigLED(const QByteArray & cmd)
|
|
{
|
|
quint16 delayOn = (quint8)cmd.at(3) + ((quint16)(cmd.at(2) << 8));
|
|
quint16 delayOff = (quint8)cmd.at(5) + ((quint16)(cmd.at(4) << 8));
|
|
|
|
this->onConfigLED((LED_NAME)cmd.at(0), (LED_TRIGGER)cmd.at(1), delayOn, delayOff);
|
|
}
|
|
|
|
void ATB_system::privateSetLEDTrigger(const QString & led, const QString & trigger)
|
|
{
|
|
#if defined (ARCH_PTU2) || defined (ARCH_PTU4)
|
|
QFile file(led);
|
|
#else
|
|
QFile file(QDir::homePath() + led);
|
|
#endif
|
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
|
qDebug() << "ATB_system::onConfigLED() cannot open file: " << led;
|
|
qDebug() << " file.errorString() = " << file.errorString();
|
|
return;
|
|
}
|
|
|
|
qDebug() << "ATB_system::onConfigLED() write file: " << led;
|
|
|
|
QTextStream out(&file);
|
|
out << trigger;
|
|
out.flush();
|
|
|
|
file.close();
|
|
}
|
|
|
|
|
|
void ATB_system::privateSetLEDDelayOn(const QString & led, const QString & delayOn)
|
|
{
|
|
#if defined (ARCH_PTU2) || defined (ARCH_PTU4)
|
|
QFile file(led);
|
|
#else
|
|
QFile file(QDir::homePath() + led);
|
|
#endif
|
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
|
qDebug() << "ATB_system::onConfigLED() cannot open file: " << led;
|
|
qDebug() << " file.errorString() = " << file.errorString();
|
|
return;
|
|
}
|
|
|
|
qDebug() << "ATB_system::onConfigLED() write file: " << led;
|
|
|
|
QTextStream out(&file);
|
|
out << delayOn;
|
|
out.flush();
|
|
|
|
file.close();
|
|
}
|
|
|
|
|
|
void ATB_system::privateSetLEDDelayOff(const QString & led, const QString & delayOff)
|
|
{
|
|
#if defined (ARCH_PTU2) || defined (ARCH_PTU4)
|
|
QFile file(led);
|
|
#else
|
|
QFile file(QDir::homePath() + led);
|
|
#endif
|
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
|
qDebug() << "ATB_system::onConfigLED() cannot open file: " << led;
|
|
qDebug() << " file.errorString() = " << file.errorString();
|
|
return;
|
|
}
|
|
|
|
qDebug() << "ATB_system::onConfigLED() write file: " << led;
|
|
|
|
QTextStream out(&file);
|
|
out << delayOff;
|
|
out.flush();
|
|
|
|
file.close();
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
* functions for switching blink button on/off
|
|
* This is only available on PTU4
|
|
*/
|
|
void ATB_system::switchBlinkButtonOn()
|
|
{
|
|
this->privateSwitchBlinkButton(true);
|
|
}
|
|
|
|
void ATB_system::switchBlinkButtonOff()
|
|
{
|
|
this->privateSwitchBlinkButton(false);
|
|
}
|
|
|
|
void ATB_system::privateSwitchBlinkButton(bool on)
|
|
{
|
|
#if defined (ARCH_PTU2)
|
|
|
|
#elif defined (ARCH_PTU4)
|
|
QString BlinkLED("/sys/class/leds/TIMER_RESET/brightness");
|
|
|
|
QFile file(BlinkLED);
|
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
|
qDebug() << "ATB_system::privateSwitchBlinkButton() cannot open file: " << BlinkLED;
|
|
qDebug() << " file.errorString() = " << file.errorString();
|
|
return;
|
|
}
|
|
|
|
QTextStream out(&file);
|
|
|
|
if (on) { out << "0"; }
|
|
else { out << "1"; }
|
|
|
|
out.flush();
|
|
|
|
file.close();
|
|
|
|
|
|
#else
|
|
Q_UNUSED(on)
|
|
#endif
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
* halt system.
|
|
*
|
|
*/
|
|
void ATB_system::halt()
|
|
{
|
|
QProcess::startDetached("/sbin/halt");
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* reboot system.
|
|
*
|
|
*/
|
|
void ATB_system::reboot()
|
|
{
|
|
QProcess::startDetached("/sbin/reboot");
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
* functions for wake vmc
|
|
* This is only available on PTU4
|
|
*/
|
|
void ATB_system::onWakeVMC()
|
|
{
|
|
#if defined (ARCH_PTU2)
|
|
|
|
#elif defined (ARCH_PTU4)
|
|
if (config->hasFeatureDBusSuspendControl()) {
|
|
this->dbus_wakeSystem();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* get event device name by a type:
|
|
*
|
|
* e.g. type is TOUCHSCREEN:
|
|
* getEventDeviceName(TOUCHSCREEN) returns "/dev/input/event0"
|
|
*
|
|
*/
|
|
QString ATB_system::getEventDeviceName(DEVICE_TYPE type)
|
|
{
|
|
QString procDevices = "/proc/bus/input/devices";
|
|
|
|
QFile inFile(procDevices);
|
|
if (!inFile.open(QFile::ReadOnly)) {
|
|
qCritical() << "ATB_system::getEventDeviceName() ERROR open()" << procDevices;
|
|
return "";
|
|
}
|
|
|
|
QTextStream textInStream(&inFile);
|
|
QString line;
|
|
QString N_RegExp;
|
|
QString DeviceTypeName;
|
|
int lineCounter = 0;
|
|
bool typeFound = false;
|
|
QString H_RegExp = "^H: Handlers"; // H: Handlers=kbd event2
|
|
QString eventDeviceName = "";
|
|
QString eventPart;
|
|
QStringList devNames;
|
|
QString DeviceNamePattern;
|
|
|
|
//PTU4 Touchscreen N: Name="atmel_mxt_ts T100 touchscreen"
|
|
//PTU4 GPIO kys N: Name="gpio_keys.6"
|
|
//RFID Reader, Stronglink SL040 N: Name="StrongLink USB CardReader"
|
|
//2D Scanner, Datalogig DSE0420 N: Name="Datalogic ADC Inc. Handheld Barcode Scanner" VID: 05f9, PID:4005
|
|
//2D Scanner, Datalogig DSM0400 N: Name="Datalogic Handheld Barcode Scanner" VID: 05f9, PID:4005
|
|
//2D Scanner, Honeywell N56000 N: Name="Honeywell Imaging & Mobility N5600"
|
|
//2D Scanner, Newland FM3080 N: Name="Newland Auto-ID NLS-FM3080V2-20 USB POS KBW" VID: 1eab, PID:0022
|
|
//2D Scanner, Opticon MDI-4000 N: Name="Opticon Opticon USB Barcode Reader"
|
|
//USB Mouse N: Name="Logitech USB-PS/2 Optical Mouse"
|
|
|
|
/* Datalogic DSE0420
|
|
I: Bus=0003 Vendor=05f9 Product=4005 Version=0110
|
|
N: Name="Datalogic ADC Inc. Handheld Barcode Scanner"
|
|
P: Phys=usb-700000.ehci-2.3/input2
|
|
S: Sysfs=/devices/ahb.0/700000.ehci/usb1/1-2/1-2.3/1-2.3:1.2/input/input14
|
|
U: Uniq=S/N G16C15795
|
|
H: Handlers=kbd event3
|
|
B: PROP=0
|
|
B: EV=120013
|
|
B: KEY=10000 7 ff9f207a c14057ff febeffdf ffefffff ffffffff fffffffe
|
|
B: MSC=10
|
|
B: LED=1f
|
|
*/
|
|
/* Datalogic DSM04XX
|
|
I: Bus=0003 Vendor=05f9 Product=4005 Version=0110
|
|
N: Name="Datalogic Handheld Barcode Scanner"
|
|
P: Phys=usb-700000.ehci-2.3/input0
|
|
S: Sysfs=/devices/ahb.0/700000.ehci/usb1/1-2/1-2.3/1-2.3:1.0/input/input15
|
|
U: Uniq=S/N G21FA0180
|
|
H: Handlers=kbd event3
|
|
B: PROP=0
|
|
B: EV=120013
|
|
B: KEY=e080ffdf 1cfffff ffffffff fffffffe
|
|
B: MSC=10
|
|
B: LED=1f
|
|
*/
|
|
/*
|
|
I: Bus=0003 Vendor=1eab Product=0022 Version=0110
|
|
N: Name="Newland Auto-ID NLS-FM3080V2-20 USB POS KBW"
|
|
P: Phys=usb-700000.ehci-2.2/input0
|
|
S: Sysfs=/devices/ahb.0/700000.ehci/usb1/1-2/1-2.2/1-2.2:1.0/input/input6
|
|
U: Uniq=FM3080V2-20-BH00017
|
|
H: Handlers=kbd event2
|
|
B: PROP=0
|
|
B: EV=120013
|
|
B: KEY=10000 7 ff9f207a c14057ff febeffdf ffefffff ffffffff fffffffe
|
|
B: MSC=10
|
|
B: LED=1f
|
|
*/
|
|
|
|
|
|
switch (type) {
|
|
case EVENT_DEVICE_BARCODEREADER:
|
|
N_RegExp = "^N: Name=.+(Barcode|Honeywell Imaging|Newland Auto).+";
|
|
DeviceTypeName = "1D/2D-Scanner";
|
|
DeviceNamePattern = "event";
|
|
break;
|
|
case EVENT_DEVICE_TOUCHSCREEN:
|
|
N_RegExp = "^N: Name=.+touch.+";
|
|
DeviceTypeName = "Touchscreen";
|
|
DeviceNamePattern = "event";
|
|
break;
|
|
case EVENT_DEVICE_GPIO_KEYBOARD:
|
|
N_RegExp = "^N: Name=.+gpio.+";
|
|
DeviceTypeName = "gpio-keys";
|
|
DeviceNamePattern = "event";
|
|
break;
|
|
case EVENT_DEVICE_KEYBOARD:
|
|
N_RegExp = "^N: Name=.+touch.+";
|
|
DeviceTypeName = "keyboard";
|
|
DeviceNamePattern = "event";
|
|
break;
|
|
case EVENT_DEVICE_RFIDREADER:
|
|
N_RegExp = "^N: Name=.+Card.+";
|
|
DeviceTypeName = "card reader";
|
|
DeviceNamePattern = "event";
|
|
break;
|
|
case EVENT_DEVICE_USBMOUSE:
|
|
N_RegExp = "^N: Name=.+USB.+Mouse.+";
|
|
DeviceTypeName = "USB Mouse";
|
|
DeviceNamePattern = "mouse";
|
|
break;
|
|
}
|
|
|
|
qDebug() << "ATB_system::getEventDeviceName() N_RegExp = " << N_RegExp;
|
|
|
|
do {
|
|
line = textInStream.readLine();
|
|
|
|
// DEBUG
|
|
//qDebug() << " " << line;
|
|
|
|
// find "N:"-line:
|
|
if (line.contains(QRegExp(N_RegExp))) {
|
|
typeFound = true;
|
|
lineCounter = 0;
|
|
|
|
qDebug() << "ATB_system::getEventDeviceName() found N_RegExp = " << N_RegExp;
|
|
|
|
}
|
|
|
|
if (typeFound) {
|
|
// found "N:"-line, now search for "H:"-line;
|
|
// the corresponding "H:"-line should be within the next 7 following text lines.
|
|
|
|
// example lines:
|
|
// - "H: Handlers=event0 mouse0" -> for touchscreen on PTU4
|
|
// - "H: Handlers=event3 mouse1" -> for USB mouse on PTU4
|
|
|
|
lineCounter++;
|
|
if ( (lineCounter<=7) && (line.contains(QRegExp(H_RegExp))) ) {
|
|
eventPart = line.split("=", QString::SkipEmptyParts).takeLast();
|
|
devNames = eventPart.split(" ", QString::SkipEmptyParts);
|
|
|
|
for (int i=0; i<devNames.size(); i++ ) {
|
|
|
|
if (devNames.at(i).contains(DeviceNamePattern)) {
|
|
eventDeviceName = "/dev/input/" + devNames.at(i);
|
|
|
|
qDebug() << "ATB_system::getEventDeviceName() found device = " << eventDeviceName;
|
|
}
|
|
}
|
|
|
|
}
|
|
if ( lineCounter>7 ) {
|
|
lineCounter=0;
|
|
typeFound = false;
|
|
}
|
|
}
|
|
} while (!line.isNull());
|
|
|
|
if (eventDeviceName == "") {
|
|
qCritical() << "ATB_system::getEventDeviceName(): could not detect input event device type " << DeviceTypeName;
|
|
}
|
|
|
|
return eventDeviceName;
|
|
}
|
|
|
|
|
|
|
|
|
|
|