hyperion.ng/libsrc/leddevice/dev_serial/ProviderRs232.cpp
LordGrey e9936e131b
mDNS Support (#1452)
* Allow build, if no grabbers are enabled

* Align available functions to right Qt version

* Update to next development version

* Align available functions to right Qt version

* fix workflows (apt/nightly)

* Disable QNetworkConfigurationManager deprecation warnings

* Initial go on Smart Pointers

* Add Deallocation

* Correct QT_WARNING_DISABLE_DEPRECATED (available since 5.9)

* Cluster Build Variables

* Hyperion Light

* Address build warnings

* Hyperion Light - UI

* Update Protobuf to latest master

* Removed compiler warnings

* Added restart ability to systray

* Correct Protobuf

* Ignore 'no-return' warning on protobuf build

* hyperion-remote: Fix auto discovery of hyperion server

* Fix Qt version override

* Update changelog

* Remove Grabber Components, if no Grabber exists

* Standalone Grabber - Fix fps default

* Remote Control - Have Source Selction accrosswhole screen

* Enable Blackborder detection only, if relevant input sources available

* Enable Blackborder detection only, if relevant input sources available

* Remote UI - rearrange containers

* Checkout

* Fix compilation on windows

* Re-added qmdnsengine template cmake

* chrono added for linux

* Removed existing AVAHI/Bonjour, allow to enable/disable mDNS

* hyperiond macos typo fix

* Fix macOS Bundle build

* Fix macOS bundle info details

* Correct CMake files

* Removed existing AVAHI/Bonjour (2)

* Share hyperion's services via mDNS

* Add mDNS Browser and mDNS for LED-Devices

* Support mDNS discovery for standalone grabbers

* Remove ZLib Dependency & Cleanup

* mDNS - hanle 2.local2 an ".local." domains equally

* Hue - Link discovery to bridge class, workaround port 443 for mDNS discovery

* Fix save button state when switching between devices

* Removed sessions (of other hyperions)

* mDNS Publisher - Simplify service naming

* mDNS refactoring & Forwarder discovery

* mDNS Updates to use device service name

* Consistency of standalone grabbers with mDNS Service Registry

* Merge branch 'hyperion-project:master' into mDNS

* Start JSON and WebServers only after Instance 0 is available

* Remove bespoke qDebug Output again

* MDNS updates and refactor Forwarder

* Minor updates

* Upgrade to CMake 3.1

* typo

* macOS fix

* Correct merge

* - Remove dynamic linker flag from standalone dispmanX Grabber
- Added ability to use system qmdns libs

* Cec handler library will load at runtime

* typo fix

* protobuf changes

* mDNS changes for Windows/macOS

* test window build qmdnsengine

* absolute path to protobuf cmake dir

* Rework Hue Wizard supporting mDNS

* LED-Devices - Retry support + Refactoring (excl. Hue)

* LED-Devices - Refactoring/Retry support Hue + additional alignments

* Address LGTM findings

* Fix CI-Build, revert test changes

* Build Windows in Release mode to avoid python problem

* Correct that WebServerObject is available earlier

* Ensure that instance name in logs for one instance are presented

* Update content LEDs

* Rework mDNS Address lookup

* Fix LED UI

* Fix for non mDNS Services (ignore default port)

* Disbale device when now input is available

* Revert back some updates, ensure last color is updated when switched on

* Handle reopening case and changed IP, port for API-calls

* Add UPD-DDP Device

* WLED support for DDP

* Fix printout

* LEDDevice - Allow more retries, udapte defaults

* LED-Net Devices - Select Custom device, if configured

Co-authored-by: Paulchen Panther <16664240+Paulchen-Panther@users.noreply.github.com>
Co-authored-by: Paulchen Panther <Paulchen-Panter@protonmail.com>
2022-05-01 19:42:47 +02:00

377 lines
10 KiB
C++

// LedDevice includes
#include <leddevice/LedDevice.h>
#include "ProviderRs232.h"
#include <utils/WaitTime.h>
// qt includes
#include <QSerialPortInfo>
#include <QEventLoop>
#include <QDir>
#include <chrono>
// Constants
namespace {
const bool verbose = false;
constexpr std::chrono::milliseconds WRITE_TIMEOUT{ 1000 }; // device write timeout in ms
constexpr std::chrono::milliseconds OPEN_TIMEOUT{ 5000 }; // device open timeout in ms
const int MAX_WRITE_TIMEOUTS = 5; // Maximum number of allowed timeouts
const int NUM_POWEROFF_WRITE_BLACK = 2; // Number of write "BLACK" during powering off
constexpr std::chrono::milliseconds DEFAULT_IDENTIFY_TIME{ 500 };
// tty discovery service
const char DISCOVERY_DIRECTORY[] = "/dev/";
const char DISCOVERY_FILEPATTERN[] = "tty*";
} //End of constants
ProviderRs232::ProviderRs232(const QJsonObject &deviceConfig)
: LedDevice(deviceConfig)
, _rs232Port(this)
,_baudRate_Hz(1000000)
,_isAutoDeviceName(false)
,_delayAfterConnect_ms(0)
,_frameDropCounter(0)
{
}
bool ProviderRs232::init(const QJsonObject &deviceConfig)
{
bool isInitOK = false;
// Initialise sub-class
if ( LedDevice::init(deviceConfig) )
{
_deviceName = deviceConfig["output"].toString("auto");
_isAutoDeviceName = _deviceName.toLower() == "auto";
// If device name was given as unix /dev/ system-location, get port name
if ( _deviceName.startsWith(QLatin1String("/dev/")) )
{
_location = _deviceName;
//Handle udev devices
QFileInfo file_info(_deviceName);
if (file_info.isSymLink())
{
_deviceName = file_info.symLinkTarget();
}
_deviceName = _deviceName.mid(5);
}
_baudRate_Hz = deviceConfig["rate"].toInt();
_delayAfterConnect_ms = deviceConfig["delayAfterConnect"].toInt(1500);
Debug(_log, "DeviceName : %s", QSTRING_CSTR(_deviceName));
DebugIf(!_location.isEmpty(), _log, "Location : %s", QSTRING_CSTR(_location));
Debug(_log, "AutoDevice : %d", _isAutoDeviceName);
Debug(_log, "baudRate_Hz : %d", _baudRate_Hz);
Debug(_log, "delayAfCon ms: %d", _delayAfterConnect_ms);
isInitOK = true;
}
return isInitOK;
}
ProviderRs232::~ProviderRs232()
{
}
int ProviderRs232::open()
{
int retval = -1;
_isDeviceReady = false;
// open device physically
if ( tryOpen(_delayAfterConnect_ms) )
{
// Everything is OK, device is ready
_isDeviceReady = true;
retval = 0;
}
return retval;
}
int ProviderRs232::close()
{
int retval = 0;
_isDeviceReady = false;
// Test, if device requires closing
if (_rs232Port.isOpen())
{
if ( _rs232Port.flush() )
{
Debug(_log,"Flush was successful");
}
Debug(_log,"Close UART: %s", QSTRING_CSTR(_deviceName) );
_rs232Port.close();
// Everything is OK -> device is closed
}
return retval;
}
bool ProviderRs232::powerOff()
{
// Simulate power-off by writing a final "Black" to have a defined outcome
bool rc = false;
if ( writeBlack( NUM_POWEROFF_WRITE_BLACK ) >= 0 )
{
rc = true;
}
return rc;
}
bool ProviderRs232::tryOpen(int delayAfterConnect_ms)
{
if (_deviceName.isEmpty() || _rs232Port.portName().isEmpty())
{
if (!_rs232Port.isOpen())
{
if ( _isAutoDeviceName )
{
_deviceName = discoverFirst();
if (_deviceName.isEmpty())
{
this->setInError( QString("No serial device found automatically!") );
return false;
}
}
}
_rs232Port.setPortName(_deviceName);
}
if (!_rs232Port.isOpen())
{
if (!_location.isEmpty())
{
Info(_log, "Opening UART: %s (%s)", QSTRING_CSTR(_deviceName), QSTRING_CSTR(_location));
}
else
{
Info(_log, "Opening UART: %s", QSTRING_CSTR(_deviceName));
}
_frameDropCounter = 0;
_rs232Port.setBaudRate( _baudRate_Hz );
Debug(_log, "_rs232Port.open(QIODevice::ReadWrite): %s, Baud rate [%d]bps", QSTRING_CSTR(_deviceName), _baudRate_Hz);
QSerialPortInfo serialPortInfo(_deviceName);
if (!serialPortInfo.isNull() )
{
Debug(_log, "portName: %s", QSTRING_CSTR(serialPortInfo.portName()));
Debug(_log, "systemLocation: %s", QSTRING_CSTR(serialPortInfo.systemLocation()));
Debug(_log, "description: %s", QSTRING_CSTR(serialPortInfo.description()));
Debug(_log, "manufacturer: %s", QSTRING_CSTR(serialPortInfo.manufacturer()));
Debug(_log, "vendorIdentifier: %s", QSTRING_CSTR(QString("0x%1").arg(serialPortInfo.vendorIdentifier(), 0, 16)));
Debug(_log, "productIdentifier: %s", QSTRING_CSTR(QString("0x%1").arg(serialPortInfo.productIdentifier(), 0, 16)));
Debug(_log, "serialNumber: %s", QSTRING_CSTR(serialPortInfo.serialNumber()));
if ( !_rs232Port.open(QIODevice::ReadWrite) )
{
this->setInError(_rs232Port.errorString());
return false;
}
}
else
{
QString errortext = QString("Invalid serial device name: %1 %2!").arg(_deviceName, _location);
this->setInError( errortext );
return false;
}
}
if (delayAfterConnect_ms > 0)
{
Debug(_log, "delayAfterConnect for %d ms - start", delayAfterConnect_ms);
// Wait delayAfterConnect_ms before allowing write
QEventLoop loop;
QTimer::singleShot(delayAfterConnect_ms, &loop, &QEventLoop::quit);
loop.exec();
Debug(_log, "delayAfterConnect for %d ms - finished", delayAfterConnect_ms);
}
return _rs232Port.isOpen();
}
void ProviderRs232::setInError(const QString& errorMsg)
{
_rs232Port.clearError();
this->close();
LedDevice::setInError( errorMsg );
}
int ProviderRs232::writeBytes(const qint64 size, const uint8_t *data)
{
int rc = 0;
if (!_rs232Port.isOpen())
{
Debug(_log, "!_rs232Port.isOpen()");
if ( !tryOpen(OPEN_TIMEOUT.count()) )
{
return -1;
}
}
qint64 bytesWritten = _rs232Port.write(reinterpret_cast<const char*>(data), size);
if (bytesWritten == -1 || bytesWritten != size)
{
this->setInError( QString ("Rs232 SerialPortError: %1").arg(_rs232Port.errorString()) );
rc = -1;
}
else
{
if (!_rs232Port.waitForBytesWritten(WRITE_TIMEOUT.count()))
{
if ( _rs232Port.error() == QSerialPort::TimeoutError )
{
Debug(_log, "Timeout after %dms: %d frames already dropped", WRITE_TIMEOUT.count(), _frameDropCounter);
++_frameDropCounter;
// Check,if number of timeouts in a given time frame is greater than defined
// TODO: ProviderRs232::writeBytes - Add time frame to check for timeouts that devices does not close after absolute number of timeouts
if ( _frameDropCounter > MAX_WRITE_TIMEOUTS )
{
this->setInError( QString ("Timeout writing data to %1").arg(_deviceName) );
rc = -1;
}
else
{
//give it another try
_rs232Port.clearError();
}
}
else
{
this->setInError( QString ("Rs232 SerialPortError: %1").arg(_rs232Port.errorString()) );
rc = -1;
}
}
}
return rc;
}
QString ProviderRs232::discoverFirst()
{
// take first available USB serial port - currently no probing!
for (auto & port : QSerialPortInfo::availablePorts())
{
if (!port.isNull())
{
Info(_log, "found serial device: %s", QSTRING_CSTR(port.portName()));
return port.portName();
}
}
return "";
}
QJsonObject ProviderRs232::discover(const QJsonObject& params)
{
DebugIf(verbose,_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
QJsonObject devicesDiscovered;
devicesDiscovered.insert("ledDeviceType", _activeDeviceType );
QJsonArray deviceList;
bool showAll = params["discoverAll"].toBool(false);
// Discover serial Devices
for (auto &port : QSerialPortInfo::availablePorts() )
{
if ( !port.isNull() && (showAll || port.vendorIdentifier() != 0) )
{
QJsonObject portInfo;
portInfo.insert("description", port.description());
portInfo.insert("manufacturer", port.manufacturer());
portInfo.insert("portName", port.portName());
portInfo.insert("productIdentifier", QString("0x%1").arg(port.productIdentifier(), 0, 16));
portInfo.insert("serialNumber", port.serialNumber());
portInfo.insert("systemLocation", port.systemLocation());
portInfo.insert("vendorIdentifier", QString("0x%1").arg(port.vendorIdentifier(), 0, 16));
deviceList.append(portInfo);
}
}
#ifndef _WIN32
//Check all /dev/tty* files, if they are udev-serial devices
QDir deviceDirectory (DISCOVERY_DIRECTORY);
QStringList deviceFilter(DISCOVERY_FILEPATTERN);
deviceDirectory.setNameFilters(deviceFilter);
deviceDirectory.setSorting(QDir::Name);
QFileInfoList deviceFiles = deviceDirectory.entryInfoList(QDir::AllEntries);
QFileInfoList::const_iterator deviceFileIterator;
for (deviceFileIterator = deviceFiles.constBegin(); deviceFileIterator != deviceFiles.constEnd(); ++deviceFileIterator)
{
if ((*deviceFileIterator).isSymLink())
{
QSerialPortInfo port = QSerialPortInfo(QSerialPort((*deviceFileIterator).symLinkTarget()));
QJsonObject portInfo;
portInfo.insert("portName", (*deviceFileIterator).fileName());
portInfo.insert("systemLocation", (*deviceFileIterator).absoluteFilePath());
portInfo.insert("udev", true);
portInfo.insert("description", port.description());
portInfo.insert("manufacturer", port.manufacturer());
portInfo.insert("productIdentifier", QString("0x%1").arg(port.productIdentifier(), 0, 16));
portInfo.insert("serialNumber", port.serialNumber());
portInfo.insert("vendorIdentifier", QString("0x%1").arg(port.vendorIdentifier(), 0, 16));
deviceList.append(portInfo);
}
}
#endif
devicesDiscovered.insert("devices", deviceList);
DebugIf(verbose,_log, "devicesDiscovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData());
return devicesDiscovered;
}
void ProviderRs232::identify(const QJsonObject& params)
{
DebugIf(verbose,_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
QString deviceName = params["output"].toString("");
if (!deviceName.isEmpty())
{
Info(_log, "Identify %s, device: %s", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(deviceName) );
_devConfig = params;
init(_devConfig);
{
if ( open() == 0 )
{
for (int i = 0; i < 2; ++i)
{
if (writeColor(ColorRgb::RED) == 0)
{
wait(DEFAULT_IDENTIFY_TIME);
writeColor(ColorRgb::BLACK);
wait(DEFAULT_IDENTIFY_TIME);
}
else
{
break;
}
}
close();
}
}
}
}