From 097f613ac70807ee9fffdc2deabc41ec28e8342d Mon Sep 17 00:00:00 2001 From: Ainur Timerbaev Date: Sat, 25 Feb 2023 22:07:57 +0000 Subject: [PATCH] Implement ftdi apa102 device --- CMakeLists.txt | 4 + sources/leddevice/CMakeLists.txt | 24 ++ sources/leddevice/LedDevice.cpp | 2 - sources/leddevice/LedDeviceSchemas.qrc | 1 + .../dev_ftdi/LedDeviceAPA102_ftdi.cpp | 62 +++++ .../leddevice/dev_ftdi/LedDeviceAPA102_ftdi.h | 47 ++++ sources/leddevice/dev_ftdi/ProviderFtdi.cpp | 237 ++++++++++++++++++ sources/leddevice/dev_ftdi/ProviderFtdi.h | 101 ++++++++ .../leddevice/schemas/schema-apa102_ftdi.json | 20 ++ 9 files changed, 496 insertions(+), 2 deletions(-) create mode 100644 sources/leddevice/dev_ftdi/LedDeviceAPA102_ftdi.cpp create mode 100644 sources/leddevice/dev_ftdi/LedDeviceAPA102_ftdi.h create mode 100644 sources/leddevice/dev_ftdi/ProviderFtdi.cpp create mode 100644 sources/leddevice/dev_ftdi/ProviderFtdi.h create mode 100644 sources/leddevice/schemas/schema-apa102_ftdi.json 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..4a5e3f3af 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,21 @@ endif() if(USE_PRECOMPILED_HEADERS AND COMMAND target_precompile_headers) target_precompile_headers(leddevice REUSE_FROM precompiled_hyperhdr_headers) endif() + + +# if(ENABLE_WS281XPWM) +# add_library(ws281x +# ${CMAKE_CURRENT_SOURCE_DIR}/external/rpi_ws281x/mailbox.c ${CMAKE_CURRENT_SOURCE_DIR}/external/rpi_ws281x/ws2811.c +# ${CMAKE_CURRENT_SOURCE_DIR}/external/rpi_ws281x/pwm.c ${CMAKE_CURRENT_SOURCE_DIR}/external/rpi_ws281x/dma.c +# ${CMAKE_CURRENT_SOURCE_DIR}/external/rpi_ws281x/pcm.c +# ${CMAKE_CURRENT_SOURCE_DIR}/external/rpi_ws281x/rpihw.c) +# 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 +}