diff --git a/CMakeLists.txt b/CMakeLists.txt
index c862770f9..058c3f9fc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -38,6 +38,7 @@ SET ( DEFAULT_BONJOUR ON )
SET ( DEFAULT_MQTT ON )
SET ( DEFAULT_STATIC_QT_PLUGINS OFF )
SET ( DEFAULT_PRECOMPILED_HEADERS ON )
+SET ( DEFAULT_ENABLE_FTDIDEV OFF )
# Configure CCache if available
find_program(CCACHE_FOUND ccache)
@@ -326,6 +327,9 @@ colorMe("ENABLE_SPIDEV = " ${ENABLE_SPIDEV})
option(ENABLE_WS281XPWM "Enable the WS281x-PWM device" ${DEFAULT_WS281XPWM} )
colorMe("ENABLE_WS281XPWM = " ${ENABLE_WS281XPWM})
+option(ENABLE_FTDIDEV "Enable the FTDI device" ${DEFAULT_ENABLE_FTDIDEV})
+colorMe("ENABLE_FTDIDEV = " ${ENABLE_FTDIDEV})
+
message( STATUS "\n${CyanColor}SOFTWARE GRABBERS${ColorReset}")
option(ENABLE_DX "Enable Windows DirectX 11 system grabber" ${DEFAULT_DX})
diff --git a/sources/leddevice/CMakeLists.txt b/sources/leddevice/CMakeLists.txt
index 1b971c07f..94480847a 100644
--- a/sources/leddevice/CMakeLists.txt
+++ b/sources/leddevice/CMakeLists.txt
@@ -18,6 +18,7 @@ include_directories(
dev_spi
dev_rpi_pwm
dev_tinker
+ dev_ftdi
)
FILE ( GLOB Leddevice_SOURCES
@@ -43,6 +44,10 @@ if ( ENABLE_SPIDEV )
FILE ( GLOB Leddevice_SPI_SOURCES "${CURRENT_SOURCE_DIR}/dev_spi/*.h" "${CURRENT_SOURCE_DIR}/dev_spi/*.cpp")
endif()
+if (ENABLE_FTDIDEV)
+ FILE ( GLOB Leddevice_FTDI_SOURCES "${CURRENT_SOURCE_DIR}/dev_ftdi/*.h" "${CURRENT_SOURCE_DIR}/dev_ftdi/*.cpp")
+endif()
+
if ( ENABLE_WS281XPWM )
include_directories(../../dependencies/external/rpi_ws281x)
FILE ( GLOB Leddevice_PWM_SOURCES "${CURRENT_SOURCE_DIR}/dev_rpi_pwm/*.h" "${CURRENT_SOURCE_DIR}/dev_rpi_pwm/*.cpp")
@@ -58,6 +63,7 @@ SET( Leddevice_SOURCES
${Leddevice_TINKER_SOURCES}
${Leddevice_SPI_SOURCES}
${Leddevice_PWM_SOURCES}
+ ${Leddevice_FTDI_SOURCES}
)
# auto generate header file that include all available leddevice headers
@@ -124,3 +130,12 @@ endif()
if(USE_PRECOMPILED_HEADERS AND COMMAND target_precompile_headers)
target_precompile_headers(leddevice REUSE_FROM precompiled_hyperhdr_headers)
endif()
+
+
+if( ENABLE_FTDIDEV )
+ FIND_PACKAGE(PkgConfig REQUIRED)
+ pkg_check_modules(LIB_FTDI REQUIRED libftdi1)
+ add_library(libftdi1 SHARED IMPORTED)
+ target_include_directories(leddevice PUBLIC ${LIB_FTDI_INCLUDE_DIRS})
+ target_link_libraries(leddevice ${LIB_FTDI_LINK_LIBRARIES})
+endif()
diff --git a/sources/leddevice/LedDevice.cpp b/sources/leddevice/LedDevice.cpp
index 5b0e01778..b8e3f4870 100644
--- a/sources/leddevice/LedDevice.cpp
+++ b/sources/leddevice/LedDevice.cpp
@@ -49,8 +49,6 @@ void LedDevice::start()
{
Info(_log, "Start LedDevice '%s'.", QSTRING_CSTR(_activeDeviceType));
- close();
-
_isDeviceInitialised = false;
// General initialisation and configuration of LedDevice
if (init(_devConfig))
diff --git a/sources/leddevice/LedDeviceSchemas.qrc b/sources/leddevice/LedDeviceSchemas.qrc
index 3df33f8d7..2ad77000f 100644
--- a/sources/leddevice/LedDeviceSchemas.qrc
+++ b/sources/leddevice/LedDeviceSchemas.qrc
@@ -36,5 +36,6 @@
schemas/schema-yeelight.json
schemas/schema-cololight.json
schemas/schema-awa_spi.json
+ schemas/schema-apa102_ftdi.json
diff --git a/sources/leddevice/dev_ftdi/LedDeviceAPA102_ftdi.cpp b/sources/leddevice/dev_ftdi/LedDeviceAPA102_ftdi.cpp
new file mode 100644
index 000000000..e9bd4e20a
--- /dev/null
+++ b/sources/leddevice/dev_ftdi/LedDeviceAPA102_ftdi.cpp
@@ -0,0 +1,62 @@
+#include "LedDeviceAPA102_ftdi.h"
+
+
+LedDeviceAPA102_ftdi::LedDeviceAPA102_ftdi(const QJsonObject &deviceConfig) : ProviderFtdi(deviceConfig)
+{
+}
+
+LedDevice *LedDeviceAPA102_ftdi::construct(const QJsonObject &deviceConfig)
+{
+ return new LedDeviceAPA102_ftdi(deviceConfig);
+}
+
+bool LedDeviceAPA102_ftdi::init(const QJsonObject &deviceConfig)
+{
+ bool isInitOK = false;
+
+ // Initialise sub-class
+ if (ProviderFtdi::init(deviceConfig))
+ {
+ CreateHeader();
+ isInitOK = true;
+ }
+ return isInitOK;
+}
+
+void LedDeviceAPA102_ftdi::CreateHeader()
+{
+ const unsigned int startFrameSize = 4;
+ const unsigned int endFrameSize = qMax(((_ledCount + 15) / 16), 4);
+ const unsigned int APAbufferSize = (_ledCount * 4) + startFrameSize + endFrameSize;
+
+ _ledBuffer.resize(0, 0xFF);
+ _ledBuffer.resize(APAbufferSize, 0xFF);
+ _ledBuffer[0] = 0x00;
+ _ledBuffer[1] = 0x00;
+ _ledBuffer[2] = 0x00;
+ _ledBuffer[3] = 0x00;
+
+ Debug(_log, "APA102 buffer created. Led's number: %d", _ledCount);
+}
+
+int LedDeviceAPA102_ftdi::write(const std::vector &ledValues)
+{
+ if (_ledCount != ledValues.size())
+ {
+ Warning(_log, "APA102 led's number has changed (old: %d, new: %d). Rebuilding buffer.", _ledCount, ledValues.size());
+ _ledCount = ledValues.size();
+
+ CreateHeader();
+ }
+
+ for (signed iLed = 0; iLed < static_cast(_ledCount); ++iLed)
+ {
+ const ColorRgb &rgb = ledValues[iLed];
+ _ledBuffer[4 + iLed * 4 + 0] = 0xFF;
+ _ledBuffer[4 + iLed * 4 + 1] = rgb.red;
+ _ledBuffer[4 + iLed * 4 + 2] = rgb.green;
+ _ledBuffer[4 + iLed * 4 + 3] = rgb.blue;
+ }
+
+ return writeBytes(_ledBuffer.size(), _ledBuffer.data());
+}
diff --git a/sources/leddevice/dev_ftdi/LedDeviceAPA102_ftdi.h b/sources/leddevice/dev_ftdi/LedDeviceAPA102_ftdi.h
new file mode 100644
index 000000000..c84531ae0
--- /dev/null
+++ b/sources/leddevice/dev_ftdi/LedDeviceAPA102_ftdi.h
@@ -0,0 +1,47 @@
+#ifndef LEDEVICET_APA102_H
+#define LEDEVICET_APA102_H
+#include "ProviderFtdi.h"
+
+class LedDeviceAPA102_ftdi : public ProviderFtdi
+{
+ Q_OBJECT
+
+public:
+
+ ///
+ /// @brief Constructs an APA102 LED-device
+ ///
+ /// @param deviceConfig Device's configuration as JSON-Object
+ ///
+ explicit LedDeviceAPA102_ftdi(const QJsonObject& deviceConfig);
+
+ ///
+ /// @brief Constructs the LED-device
+ ///
+ /// @param[in] deviceConfig Device's configuration as JSON-Object
+ /// @return LedDevice constructed
+ static LedDevice* construct(const QJsonObject& deviceConfig);
+
+private:
+
+ ///
+ /// @brief Initialise the device's configuration
+ ///
+ /// @param[in] deviceConfig the JSON device configuration
+ /// @return True, if success
+ ///
+ bool init(const QJsonObject& deviceConfig) override;
+
+ void CreateHeader();
+
+ ///
+ /// @brief Writes the RGB-Color values to the LEDs.
+ ///
+ /// @param[in] ledValues The RGB-color per LED
+ /// @return Zero on success, else negative
+ ///
+ int write(const std::vector& ledValues) override;
+
+};
+
+#endif // LEDEVICET_APA102_H
diff --git a/sources/leddevice/dev_ftdi/ProviderFtdi.cpp b/sources/leddevice/dev_ftdi/ProviderFtdi.cpp
new file mode 100644
index 000000000..7cc355b4a
--- /dev/null
+++ b/sources/leddevice/dev_ftdi/ProviderFtdi.cpp
@@ -0,0 +1,237 @@
+// LedDevice includes
+#include
+#include "ProviderFtdi.h"
+
+#include
+
+#define ANY_FTDI_VENDOR 0x0
+#define ANY_FTDI_PRODUCT 0x0
+
+ProviderFtdi::ProviderFtdi(const QJsonObject &deviceConfig)
+ : LedDevice(deviceConfig),
+ _baudRate_Hz(1000000)
+{
+}
+
+bool ProviderFtdi::init(const QJsonObject &deviceConfig)
+{
+ bool isInitOK = false;
+
+ // Initialise sub-class
+ if (LedDevice::init(deviceConfig))
+ {
+
+ Debug(_log, "DeviceType : %s", QSTRING_CSTR(this->getActiveDeviceType()));
+ Debug(_log, "LedCount : %d", this->getLedCount());
+ Debug(_log, "RefreshTime : %d", _refreshTimerInterval_ms);
+
+ _baudRate_Hz = deviceConfig["rate"].toInt();
+ _maxRetry = _devConfig["maxRetry"].toInt(60);
+
+ Debug(_log, "Baud rate : %d", _baudRate_Hz);
+ Debug(_log, "Retry limit : %d", _maxRetry);
+
+ isInitOK = true;
+ }
+ return isInitOK;
+}
+
+ProviderFtdi::~ProviderFtdi()
+{
+}
+
+int ProviderFtdi::open()
+{
+ int rc = 0;
+ if (_isDeviceReady)
+ {
+ Debug(_log, "Is already opened");
+ return rc;
+ }
+ Debug(_log, "Opening FTDI device");
+
+ if ((_ftdic = ftdi_new()) == NULL)
+ {
+ this->setInError(ftdi_get_error_string(_ftdic));
+ return -1;
+ }
+ Debug(_log, "ftdi_set_interface");
+ if ((rc = ftdi_set_interface(_ftdic, INTERFACE_ANY)) < 0)
+ {
+ this->setInError(ftdi_get_error_string(_ftdic));
+ return rc;
+ }
+ Debug(_log, "ftdi_usb_find_all");
+ struct ftdi_device_list *devlist;
+ int devices_found = 0;
+ if ((devices_found = ftdi_usb_find_all(_ftdic, &devlist, ANY_FTDI_VENDOR, ANY_FTDI_PRODUCT)) < 0)
+ {
+ this->setInError(ftdi_get_error_string(_ftdic));
+ return -1;
+ }
+ if (devices_found < 1)
+ {
+ this->setInError("No FTDI devices detected");
+ return -1;
+ }
+
+ if ((rc = ftdi_usb_open_dev(_ftdic, devlist[0].dev)) < 0)
+ {
+ ftdi_list_free(&devlist);
+ this->setInError(ftdi_get_error_string(_ftdic));
+ return rc;
+ }
+ ftdi_list_free(&devlist);
+
+ /* doing this disable resets things if they were in a bad state */
+ if ((rc = ftdi_disable_bitbang(_ftdic)) < 0)
+ {
+ this->setInError(ftdi_get_error_string(_ftdic));
+ return rc;
+ }
+
+ if ((rc = ftdi_setflowctrl(_ftdic, SIO_DISABLE_FLOW_CTRL)) < 0)
+ {
+ this->setInError(ftdi_get_error_string(_ftdic));
+ return rc;
+ }
+
+ if ((rc = ftdi_set_bitmode(_ftdic, 0xff, BITMODE_MPSSE)) < 0)
+ {
+ this->setInError(ftdi_get_error_string(_ftdic));
+ return rc;
+ }
+
+ double reference_clock = 60e6;
+
+ int divisor = (reference_clock / 2 / _baudRate_Hz) - 1;
+
+ if ((rc = this->writeByte(DIS_DIV_5)) != 1)
+ {
+ return rc;
+ }
+ if ((rc = this->writeByte(TCK_DIVISOR)) != 1)
+ {
+ return rc;
+ }
+ if ((rc = this->writeByte(divisor)) != 1)
+ {
+ return rc;
+ }
+ if ((rc = this->writeByte(divisor >> 8)) != 1)
+ {
+ return rc;
+ }
+ if ((rc = this->writeByte(0x80)) != 1)
+ {
+ return rc;
+ }
+ if ((rc = this->writeByte(0x00)) != 1)
+ {
+ return rc;
+ }
+ if ((rc = this->writeByte(0x0b)) != 1)
+ {
+ return rc;
+ }
+ _isDeviceReady = true;
+ return rc;
+}
+
+int ProviderFtdi::close()
+{
+ _isDeviceReady = false;
+ if (_ftdic)
+ {
+ Debug(_log, "Closing FTDI device");
+ Debug(_log, "ftdi_usb_close");
+ ftdi_usb_close(_ftdic);
+ Debug(_log, "ftdi_free");
+ ftdi_free(_ftdic);
+ }
+ else
+ {
+ Debug(_log, "Closing FTDI device before calling open(), ignoring");
+ }
+
+ return 0;
+}
+
+bool ProviderFtdi::powerOff()
+{
+ if (_isDeviceReady)
+ {
+ return writeBlack(1) >= 0;
+ }
+ else
+ {
+ return true;
+ }
+}
+
+void ProviderFtdi::setInError(const QString &errorMsg)
+{
+ this->close();
+
+ LedDevice::setInError(errorMsg);
+}
+
+int ProviderFtdi::writeByte(uint8_t data)
+{
+ int rc = ftdi_write_data(_ftdic, &data, 1);
+ if (rc != 1)
+ {
+ this->setInError(ftdi_get_error_string(_ftdic));
+ }
+ return rc;
+}
+int ProviderFtdi::writeBytes(const qint64 size, const uint8_t *data)
+{
+ int rc = 0;
+
+ int count_arg = size - 1;
+
+ if ((rc = this->writeByte(MPSSE_DO_WRITE | MPSSE_WRITE_NEG)) != 1)
+ {
+ return rc;
+ }
+ if ((rc = this->writeByte(count_arg)) != 1)
+ {
+ return rc;
+ }
+ if ((rc = this->writeByte(count_arg >> 8)) != 1)
+ {
+ return rc;
+ }
+
+ if ((rc = ftdi_write_data(_ftdic, data, size)) != size)
+ {
+ this->setInError(ftdi_get_error_string(_ftdic));
+ return rc;
+ }
+
+ return rc;
+}
+
+QString ProviderFtdi::discoverFirst()
+{
+ return "auto";
+}
+
+QJsonObject ProviderFtdi::discover(const QJsonObject & /*params*/)
+{
+ QJsonObject devicesDiscovered;
+ QJsonArray deviceList;
+
+ deviceList.push_back(
+ QJsonObject{
+ {"value", "auto"},
+ {"name", "Auto"}});
+
+ devicesDiscovered.insert("ledDeviceType", _activeDeviceType);
+ devicesDiscovered.insert("devices", deviceList);
+
+ Debug(_log, "Serial devices discovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData());
+
+ return devicesDiscovered;
+}
diff --git a/sources/leddevice/dev_ftdi/ProviderFtdi.h b/sources/leddevice/dev_ftdi/ProviderFtdi.h
new file mode 100644
index 000000000..ce12f1e34
--- /dev/null
+++ b/sources/leddevice/dev_ftdi/ProviderFtdi.h
@@ -0,0 +1,101 @@
+#ifndef PROVIDERFtdi_H
+#define PROVIDERFtdi_H
+
+// LedDevice includes
+#include
+
+#include
+
+///
+/// The ProviderFtdi implements an abstract base-class for LedDevices using a Ftdi-device.
+///
+class ProviderFtdi : public LedDevice
+{
+ Q_OBJECT
+
+public:
+
+ ///
+ /// @brief Constructs a Ftdi LED-device
+ ///
+ ProviderFtdi(const QJsonObject& deviceConfig);
+
+ ///
+ /// @brief Destructor of the UDP LED-device
+ ///
+ ~ProviderFtdi() override;
+
+protected:
+
+ ///
+ /// @brief Initialise the Ftdi device's configuration and network address details
+ ///
+ /// @param[in] deviceConfig the JSON device configuration
+ /// @return True, if success
+ ///
+ bool init(const QJsonObject& deviceConfig) override;
+
+ ///
+ /// @brief Opens the output device.
+ ///
+ /// @return Zero on success (i.e. device is ready), else negative
+ ///
+ int open() override;
+
+ ///
+ /// @brief Closes the UDP device.
+ ///
+ /// @return Zero on success (i.e. device is closed), else negative
+ ///
+ int close() override;
+
+ ///
+ /// @brief Power-/turn off a Ftdi-device
+ ///
+ /// The off-state is simulated by writing "Black to LED"
+ ///
+ /// @return True, if success
+ ///
+ bool powerOff() override;
+
+ ///
+ /// @brief Discover first devices of a serial device available (for configuration)
+ ///
+ /// @return A string of the device found
+ ///
+ QString discoverFirst() override;
+
+ /// @param[in] params Parameters used to overwrite discovery default behaviour
+ ///
+ /// @return A JSON structure holding a list of devices found
+ ///
+ QJsonObject discover(const QJsonObject& params) override;
+
+ ///
+ /// @brief Write the given bytes to the Ftdi-device
+ ///
+ /// @param[in[ size The length of the data
+ /// @param[in] data The data
+ /// @return Zero on success, else negative
+ ///
+ int writeBytes(const qint64 size, const uint8_t* data);
+
+ /// The Ftdi serial-device
+ struct ftdi_context *_ftdic;
+ /// The used baud-rate of the output device
+ qint32 _baudRate_Hz;
+
+protected slots:
+
+ ///
+ /// @brief Set device in error state
+ ///
+ /// @param errorMsg The error message to be logged
+ ///
+ void setInError(const QString& errorMsg) override;
+
+private:
+ int writeByte(uint8_t data);
+};
+
+#endif // PROVIDERFtdi_H
diff --git a/sources/leddevice/schemas/schema-apa102_ftdi.json b/sources/leddevice/schemas/schema-apa102_ftdi.json
new file mode 100644
index 000000000..dd149d15f
--- /dev/null
+++ b/sources/leddevice/schemas/schema-apa102_ftdi.json
@@ -0,0 +1,20 @@
+{
+ "type":"object",
+ "required":true,
+ "properties":{
+ "output": {
+ "type": "string",
+ "title":"edt_dev_spec_outputPath_title",
+ "default":"auto",
+ "propertyOrder" : 1
+ },
+
+ "rate": {
+ "type": "integer",
+ "title":"edt_dev_spec_baudrate_title",
+ "default": 2000000,
+ "propertyOrder" : 2
+ }
+ },
+ "additionalProperties": true
+}