hyperion.ng/libsrc/leddevice/LedDevice.cpp
LordGrey a57bcbc2b8
WLED segment streaming support (#1556)
* WLED segment streaming support

* Address CodeQL findings

* WLED - Remove that interim color is shown when WLED is powered off

* Allow LEDDevice to stay on after streaming

* Apply stayOn on segment streamed to

* Fix LED-Matrix Layout: Add Cabling direction selection element again
2023-02-07 08:15:22 +01:00

705 lines
16 KiB
C++

#include <leddevice/LedDevice.h>
//QT include
#include <QResource>
#include <QStringList>
#include <QDir>
#include <QDateTime>
#include <QEventLoop>
#include <QTimer>
#include <QDateTime>
#include "hyperion/Hyperion.h"
#include <utils/JsonUtils.h>
//std includes
#include <sstream>
#include <iomanip>
#include <chrono>
// Constants
namespace {
// Configuration settings
const char CONFIG_CURRENT_LED_COUNT[] = "currentLedCount";
const char CONFIG_COLOR_ORDER[] = "colorOrder";
const char CONFIG_AUTOSTART[] = "autoStart";
const char CONFIG_LATCH_TIME[] = "latchTime";
const char CONFIG_REWRITE_TIME[] = "rewriteTime";
int DEFAULT_LED_COUNT{ 1 };
const char DEFAULT_COLOR_ORDER[]{ "RGB" };
const bool DEFAULT_IS_AUTOSTART{ true };
const char CONFIG_ENABLE_ATTEMPTS[] = "enableAttempts";
const char CONFIG_ENABLE_ATTEMPTS_INTERVALL[] = "enableAttemptsInterval";
const int DEFAULT_MAX_ENABLE_ATTEMPTS{ 5 };
constexpr std::chrono::seconds DEFAULT_ENABLE_ATTEMPTS_INTERVAL{ 5 };
} //End of constants
LedDevice::LedDevice(const QJsonObject& deviceConfig, QObject* parent)
: QObject(parent)
, _devConfig(deviceConfig)
, _log(Logger::getInstance("LEDDEVICE"))
, _ledBuffer(0)
, _refreshTimer(nullptr)
, _refreshTimerInterval_ms(0)
, _latchTime_ms(0)
, _ledCount(0)
, _isRestoreOrigState(false)
, _isStayOnAfterStreaming(false)
, _isEnabled(false)
, _isDeviceInitialised(false)
, _isDeviceReady(false)
, _isOn(false)
, _isDeviceInError(false)
, _isDeviceRecoverable(false)
, _lastWriteTime(QDateTime::currentDateTime())
, _enableAttemptsTimer(nullptr)
, _enableAttemptTimerInterval(DEFAULT_ENABLE_ATTEMPTS_INTERVAL)
, _enableAttempts(0)
, _maxEnableAttempts(DEFAULT_MAX_ENABLE_ATTEMPTS)
, _isRefreshEnabled(false)
, _isAutoStart(true)
{
_activeDeviceType = deviceConfig["type"].toString("UNSPECIFIED").toLower();
}
LedDevice::~LedDevice()
{
}
void LedDevice::start()
{
Info(_log, "Start LedDevice '%s'.", QSTRING_CSTR(_activeDeviceType));
close();
_isDeviceInitialised = false;
if (init(_devConfig))
{
// Everything is OK -> enable device
_isDeviceInitialised = true;
if (_isAutoStart)
{
if (!_isEnabled)
{
Debug(_log, "Not enabled -> enable device");
enable();
}
}
}
}
void LedDevice::stop()
{
Debug(_log, "Stop device");
this->stopEnableAttemptsTimer();
this->disable();
this->stopRefreshTimer();
Info(_log, " Stopped LedDevice '%s'", QSTRING_CSTR(_activeDeviceType));
}
int LedDevice::open()
{
_isDeviceReady = true;
int retval = 0;
return retval;
}
int LedDevice::close()
{
_isDeviceReady = false;
int retval = 0;
return retval;
}
void LedDevice::setInError(const QString& errorMsg, bool isRecoverable)
{
_isOn = false;
_isDeviceInError = true;
_isDeviceReady = false;
_isEnabled = false;
this->stopRefreshTimer();
if (isRecoverable)
{
_isDeviceRecoverable = isRecoverable;
}
Error(_log, "Device disabled, device '%s' signals error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(errorMsg));
emit enableStateChanged(_isEnabled);
}
void LedDevice::enable()
{
Debug(_log, "Enable device %s'", QSTRING_CSTR(_activeDeviceType));
if (!_isEnabled)
{
if (_enableAttemptsTimer != nullptr && _enableAttemptsTimer->isActive())
{
_enableAttemptsTimer->stop();
}
_isDeviceInError = false;
if (!_isDeviceInitialised)
{
_isDeviceInitialised = init(_devConfig);
}
if (!_isDeviceReady)
{
open();
}
bool isEnableFailed(true);
if (_isDeviceReady)
{
if (switchOn())
{
stopEnableAttemptsTimer();
_isEnabled = true;
isEnableFailed = false;
emit enableStateChanged(_isEnabled);
Info(_log, "LedDevice '%s' enabled", QSTRING_CSTR(_activeDeviceType));
}
}
if (isEnableFailed)
{
emit enableStateChanged(false);
if (_maxEnableAttempts > 0 && _isDeviceRecoverable)
{
Debug(_log, "Device's enablement failed - Start retry timer. Retried already done [%d], isEnabled: [%d]", _enableAttempts, _isEnabled);
startEnableAttemptsTimer();
}
else
{
Debug(_log, "Device's enablement failed");
}
}
}
}
void LedDevice::disable()
{
Debug(_log, "Disable device %s'", QSTRING_CSTR(_activeDeviceType));
if (_isEnabled)
{
_isEnabled = false;
this->stopEnableAttemptsTimer();
this->stopRefreshTimer();
switchOff();
close();
emit enableStateChanged(_isEnabled);
}
}
void LedDevice::setActiveDeviceType(const QString& deviceType)
{
_activeDeviceType = deviceType;
}
bool LedDevice::init(const QJsonObject& deviceConfig)
{
Debug(_log, "deviceConfig: [%s]", QString(QJsonDocument(_devConfig).toJson(QJsonDocument::Compact)).toUtf8().constData());
setLedCount(deviceConfig[CONFIG_CURRENT_LED_COUNT].toInt(DEFAULT_LED_COUNT)); // property injected to reflect real led count
setColorOrder(deviceConfig[CONFIG_COLOR_ORDER].toString(DEFAULT_COLOR_ORDER));
setLatchTime(deviceConfig[CONFIG_LATCH_TIME].toInt(_latchTime_ms));
setRewriteTime(deviceConfig[CONFIG_REWRITE_TIME].toInt(_refreshTimerInterval_ms));
setAutoStart(deviceConfig[CONFIG_AUTOSTART].toBool(DEFAULT_IS_AUTOSTART));
setEnableAttempts(deviceConfig[CONFIG_ENABLE_ATTEMPTS].toInt(DEFAULT_MAX_ENABLE_ATTEMPTS),
std::chrono::seconds(deviceConfig[CONFIG_ENABLE_ATTEMPTS_INTERVALL].toInt(DEFAULT_ENABLE_ATTEMPTS_INTERVAL.count()))
);
return true;
}
void LedDevice::startRefreshTimer()
{
if (_refreshTimerInterval_ms > 0)
{
if (_isDeviceReady && _isOn)
{
// setup refreshTimer
if (_refreshTimer == nullptr)
{
_refreshTimer = new QTimer(this);
_refreshTimer->setTimerType(Qt::PreciseTimer);
connect(_refreshTimer, &QTimer::timeout, this, &LedDevice::rewriteLEDs);
}
_refreshTimer->setInterval(_refreshTimerInterval_ms);
_refreshTimer->start();
}
else
{
Debug(_log, "Device is not ready to start a refresh timer");
}
}
}
void LedDevice::stopRefreshTimer()
{
if (_refreshTimer != nullptr)
{
_refreshTimer->stop();
delete _refreshTimer;
_refreshTimer = nullptr;
}
}
void LedDevice::startEnableAttemptsTimer()
{
++_enableAttempts;
if (_isDeviceRecoverable)
{
if (_enableAttempts <= _maxEnableAttempts)
{
if (_enableAttemptTimerInterval.count() > 0)
{
// setup enable retry timer
if (_enableAttemptsTimer == nullptr)
{
_enableAttemptsTimer = new QTimer(this);
_enableAttemptsTimer->setTimerType(Qt::PreciseTimer);
connect(_enableAttemptsTimer, &QTimer::timeout, this, &LedDevice::enable);
}
_enableAttemptsTimer->setInterval(static_cast<int>(_enableAttemptTimerInterval.count() * 1000)); //NOLINT
Info(_log, "Start %d. attempt of %d to enable the device in %d seconds", _enableAttempts, _maxEnableAttempts, _enableAttemptTimerInterval.count());
_enableAttemptsTimer->start();
}
}
else
{
Error(_log, "Device disabled. Maximum number of %d attempts enabling the device reached. Tried for %d seconds.", _maxEnableAttempts, _enableAttempts * _enableAttemptTimerInterval.count());
_enableAttempts = 0;
}
}
}
void LedDevice::stopEnableAttemptsTimer()
{
if (_enableAttemptsTimer != nullptr)
{
Debug(_log, "Stopping enable retry timer");
_enableAttemptsTimer->stop();
delete _enableAttemptsTimer;
_enableAttemptsTimer = nullptr;
_enableAttempts = 0;
}
}
int LedDevice::updateLeds(std::vector<ColorRgb> ledValues)
{
int retval = 0;
if (!_isEnabled || !_isOn || !_isDeviceReady || _isDeviceInError)
{
// LedDevice NOT ready!
retval = -1;
}
else
{
qint64 elapsedTimeMs = _lastWriteTime.msecsTo(QDateTime::currentDateTime());
if (_latchTime_ms == 0 || elapsedTimeMs >= _latchTime_ms)
{
retval = write(ledValues);
_lastWriteTime = QDateTime::currentDateTime();
// if device requires refreshing, save Led-Values and restart the timer
if (_isRefreshEnabled && _isEnabled)
{
_lastLedValues = ledValues;
this->startRefreshTimer();
}
}
else
{
// Skip write as elapsedTime < latchTime
if (_isRefreshEnabled)
{
//Stop timer to allow for next non-refresh update
this->stopRefreshTimer();
}
}
}
return retval;
}
int LedDevice::rewriteLEDs()
{
int retval = -1;
if (_isEnabled && _isOn && _isDeviceReady && !_isDeviceInError)
{
if (!_lastLedValues.empty())
{
retval = write(_lastLedValues);
_lastWriteTime = QDateTime::currentDateTime();
}
}
else
{
// If Device is not ready stop timer
this->stopRefreshTimer();
}
return retval;
}
int LedDevice::writeBlack(int numberOfWrites)
{
Debug(_log, "Set LED strip to black to switch of LEDs");
return writeColor(ColorRgb::BLACK, numberOfWrites);
}
int LedDevice::writeColor(const ColorRgb& color, int numberOfWrites)
{
int rc = -1;
for (int i = 0; i < numberOfWrites; i++)
{
if (_latchTime_ms > 0)
{
// Wait latch time before writing black
QEventLoop loop;
QTimer::singleShot(_latchTime_ms, &loop, &QEventLoop::quit);
loop.exec();
}
_lastLedValues = std::vector<ColorRgb>(static_cast<unsigned long>(_ledCount), color);
rc = write(_lastLedValues);
}
return rc;
}
bool LedDevice::switchOn()
{
bool rc{ false };
if (_isOn)
{
Debug(_log, "Device %s is already on. Skipping.", QSTRING_CSTR(_activeDeviceType));
rc = true;
}
else
{
if (_isDeviceReady)
{
Info(_log, "Switching device %s ON", QSTRING_CSTR(_activeDeviceType));
if (storeState())
{
if (powerOn())
{
Info(_log, "Device %s is ON", QSTRING_CSTR(_activeDeviceType));
_isOn = true;
emit enableStateChanged(_isEnabled);
rc = true;
}
else
{
Warning(_log, "Failed switching device %s ON", QSTRING_CSTR(_activeDeviceType));
}
}
}
}
return rc;
}
bool LedDevice::switchOff()
{
bool rc{ false };
if (!_isOn)
{
rc = true;
}
else
{
if (_isDeviceInitialised)
{
Info(_log, "Switching device %s OFF", QSTRING_CSTR(_activeDeviceType));
// Disable device to ensure no standard LED updates are written/processed
_isOn = false;
rc = true;
if (_isDeviceReady)
{
if (_isRestoreOrigState)
{
//Restore devices state
restoreState();
}
else
{
if (powerOff())
{
Info(_log, "Device %s is OFF", QSTRING_CSTR(_activeDeviceType));
}
else
{
Warning(_log, "Failed switching device %s OFF", QSTRING_CSTR(_activeDeviceType));
}
}
}
}
}
return rc;
}
bool LedDevice::powerOff()
{
bool rc{ true };
if (!_isStayOnAfterStreaming)
{
Debug(_log, "Power Off: %s", QSTRING_CSTR(_activeDeviceType));
// Simulate power-off by writing a final "Black" to have a defined outcome
if (writeBlack() < 0)
{
rc = false;
}
}
return rc;
}
bool LedDevice::powerOn()
{
bool rc{ true };
Debug(_log, "Power On: %s", QSTRING_CSTR(_activeDeviceType));
return rc;
}
bool LedDevice::storeState()
{
bool rc{ true };
#if 0
if (_isRestoreOrigState)
{
// Save device's original state
// _originalStateValues = get device's state;
// store original power on/off state, if available
}
#endif
return rc;
}
bool LedDevice::restoreState()
{
bool rc{ true };
#if 0
if (_isRestoreOrigState)
{
// Restore device's original state
// update device using _originalStateValues
// update original power on/off state, if supported
}
#endif
return rc;
}
QJsonObject LedDevice::discover(const QJsonObject& /*params*/)
{
QJsonObject devicesDiscovered;
devicesDiscovered.insert("ledDeviceType", _activeDeviceType);
QJsonArray deviceList;
devicesDiscovered.insert("devices", deviceList);
Debug(_log, "devicesDiscovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData());
return devicesDiscovered;
}
QString LedDevice::discoverFirst()
{
QString deviceDiscovered;
Debug(_log, "deviceDiscovered: [%s]", QSTRING_CSTR(deviceDiscovered));
return deviceDiscovered;
}
QJsonObject LedDevice::getProperties(const QJsonObject& params)
{
Debug(_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
QJsonObject properties;
QJsonObject deviceProperties;
properties.insert("properties", deviceProperties);
Debug(_log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData());
return properties;
}
void LedDevice::setLogger(Logger* log)
{
_log = log;
}
void LedDevice::setLedCount(int ledCount)
{
assert(ledCount >= 0);
_ledCount = static_cast<uint>(ledCount);
_ledRGBCount = _ledCount * sizeof(ColorRgb);
_ledRGBWCount = _ledCount * sizeof(ColorRgbw);
Debug(_log, "LedCount set to %d", _ledCount);
}
void LedDevice::setColorOrder(const QString& colorOrder)
{
_colorOrder = colorOrder;
Debug(_log, "ColorOrder set to %s", QSTRING_CSTR(_colorOrder.toUpper()));
}
void LedDevice::setLatchTime(int latchTime_ms)
{
assert(latchTime_ms >= 0);
_latchTime_ms = latchTime_ms;
Debug(_log, "LatchTime set to %dms", _latchTime_ms);
}
void LedDevice::setAutoStart(bool isAutoStart)
{
_isAutoStart = isAutoStart;
Debug(_log, "AutoStart %s", (_isAutoStart ? "enabled" : "disabled"));
}
void LedDevice::setRewriteTime(int rewriteTime_ms)
{
_refreshTimerInterval_ms = qMax(rewriteTime_ms, 0);
if (_refreshTimerInterval_ms > 0)
{
_isRefreshEnabled = true;
if (_refreshTimerInterval_ms <= _latchTime_ms)
{
int new_refresh_timer_interval = _latchTime_ms + 10; //NOLINT
Warning(_log, "latchTime(%d) is bigger/equal rewriteTime(%d), set rewriteTime to %dms", _latchTime_ms, _refreshTimerInterval_ms, new_refresh_timer_interval);
_refreshTimerInterval_ms = new_refresh_timer_interval;
}
Debug(_log, "Refresh interval = %dms", _refreshTimerInterval_ms);
startRefreshTimer();
}
else
{
_isRefreshEnabled = false;
stopRefreshTimer();
}
}
void LedDevice::setEnableAttempts(int maxEnableRetries, std::chrono::seconds enableRetryTimerInterval)
{
stopEnableAttemptsTimer();
maxEnableRetries = qMax(maxEnableRetries, 0);
_enableAttempts = 0;
_maxEnableAttempts = maxEnableRetries;
_enableAttemptTimerInterval = enableRetryTimerInterval;
Debug(_log, "Max enable retries: %d, enable retry interval = %llds", _maxEnableAttempts, _enableAttemptTimerInterval.count());
}
void LedDevice::printLedValues(const std::vector<ColorRgb>& ledValues)
{
std::cout << "LedValues [" << ledValues.size() << "] [";
for (const ColorRgb& color : ledValues)
{
std::cout << color;
}
std::cout << "]" << std::endl;
}
QString LedDevice::uint8_t_to_hex_string(const uint8_t* data, const int size, int number)
{
if (number <= 0 || number > size)
{
number = size;
}
QByteArray bytes(reinterpret_cast<const char*>(data), number);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
return bytes.toHex(':');
#else
return bytes.toHex();
#endif
}
QString LedDevice::toHex(const QByteArray& data, int number)
{
if (number <= 0 || number > data.size())
{
number = data.size();
}
#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
return data.left(number).toHex(':');
#else
return data.left(number).toHex();
#endif
}
bool LedDevice::isInitialised() const
{
return _isDeviceInitialised;
}
bool LedDevice::isReady() const
{
return _isDeviceReady;
}
bool LedDevice::isInError() const
{
return _isDeviceInError;
}
int LedDevice::getLatchTime() const
{
return _latchTime_ms;
}
int LedDevice::getRewriteTime() const
{
return _refreshTimerInterval_ms;
}
int LedDevice::getLedCount() const
{
return static_cast<int>(_ledCount);
}
QString LedDevice::getActiveDeviceType() const
{
return _activeDeviceType;
}
QString LedDevice::getColorOrder() const
{
return _colorOrder;
}
bool LedDevice::componentState() const {
return _isEnabled;
}