Implement ftdi led device

This commit is contained in:
Ainur Timerbaev 2023-04-13 12:32:04 +01:00
parent 2f09f9a0b8
commit 913d636542
12 changed files with 457 additions and 162 deletions

View File

@ -35,6 +35,6 @@ function installAndUpgrade()
if [[ $CI_NAME == 'osx' || $CI_NAME == 'darwin' ]]; then if [[ $CI_NAME == 'osx' || $CI_NAME == 'darwin' ]]; then
echo "Install dependencies" echo "Install dependencies"
brew update brew update
dependencies=("qt5" "python" "libusb" "cmake" "doxygen") dependencies=("qt5" "python" "libusb" "cmake" "doxygen", "libftdi")
installAndUpgrade "${dependencies[@]}" installAndUpgrade "${dependencies[@]}"
fi fi

View File

@ -32,7 +32,7 @@ jobs:
if: ${{ matrix.language == 'cpp' }} if: ${{ matrix.language == 'cpp' }}
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install --yes git cmake build-essential qtbase5-dev libqt5serialport5-dev libqt5sql5-sqlite libqt5svg5-dev libqt5x11extras5-dev libusb-1.0-0-dev python3-dev libcec-dev libxcb-image0-dev libxcb-util0-dev libxcb-shm0-dev libxcb-render0-dev libxcb-randr0-dev libxrandr-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libasound2-dev libturbojpeg0-dev libjpeg-dev libssl-dev sudo apt-get install --yes git cmake build-essential qtbase5-dev libqt5serialport5-dev libqt5sql5-sqlite libqt5svg5-dev libqt5x11extras5-dev libusb-1.0-0-dev python3-dev libcec-dev libxcb-image0-dev libxcb-util0-dev libxcb-shm0-dev libxcb-render0-dev libxcb-randr0-dev libxrandr-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libasound2-dev libturbojpeg0-dev libjpeg-dev libssl-dev libftdi1-dev
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v2 uses: github/codeql-action/init@v2

View File

@ -88,6 +88,7 @@ SET ( DEFAULT_DEV_SPI OFF )
SET ( DEFAULT_DEV_TINKERFORGE OFF ) SET ( DEFAULT_DEV_TINKERFORGE OFF )
SET ( DEFAULT_DEV_USB_HID OFF ) SET ( DEFAULT_DEV_USB_HID OFF )
SET ( DEFAULT_DEV_WS281XPWM OFF ) SET ( DEFAULT_DEV_WS281XPWM OFF )
SET ( DEFAULT_DEV_FTDI ON )
# Services # Services
SET ( DEFAULT_EFFECTENGINE ON ) SET ( DEFAULT_EFFECTENGINE ON )
@ -117,6 +118,7 @@ IF ( ${CMAKE_SYSTEM} MATCHES "Linux" )
ELSEIF ( WIN32 ) ELSEIF ( WIN32 )
SET ( DEFAULT_DX ON ) SET ( DEFAULT_DX ON )
SET ( DEFAULT_MF ON ) SET ( DEFAULT_MF ON )
SET ( DEFAULT_DEV_FTDI OFF )
ELSE() ELSE()
SET ( DEFAULT_FB OFF ) SET ( DEFAULT_FB OFF )
SET ( DEFAULT_V4L2 OFF ) SET ( DEFAULT_V4L2 OFF )
@ -341,6 +343,9 @@ message(STATUS "ENABLE_DEV_USB_HID = ${ENABLE_DEV_USB_HID}")
option(ENABLE_DEV_WS281XPWM "Enable the WS281x-PWM device" ${DEFAULT_DEV_WS281XPWM} ) option(ENABLE_DEV_WS281XPWM "Enable the WS281x-PWM device" ${DEFAULT_DEV_WS281XPWM} )
message(STATUS "ENABLE_DEV_WS281XPWM = ${ENABLE_DEV_WS281XPWM}") message(STATUS "ENABLE_DEV_WS281XPWM = ${ENABLE_DEV_WS281XPWM}")
option(ENABLE_DEV_FTDI "Enable the FTDI devices" ${DEFAULT_DEV_FTDI} )
message(STATUS "ENABLE_DEV_FTDI = ${ENABLE_DEV_FTDI}")
removeIndent() removeIndent()
message(STATUS "Services options:") message(STATUS "Services options:")

View File

@ -61,9 +61,12 @@
// Define to enable the Serial devices // Define to enable the Serial devices
#cmakedefine ENABLE_DEV_SERIAL #cmakedefine ENABLE_DEV_SERIAL
// Define to enable the SPI devices // Define to enable the SPI spidev devices
#cmakedefine ENABLE_DEV_SPI #cmakedefine ENABLE_DEV_SPI
// Define to enable the SPI ftdi devices
#cmakedefine ENABLE_DEV_FTDI
// Define to enable the Tinkerforge devices // Define to enable the Tinkerforge devices
#cmakedefine ENABLE_DEV_TINKERFORGE #cmakedefine ENABLE_DEV_TINKERFORGE

View File

@ -153,7 +153,7 @@
"conf_leds_note_layout_overwrite": "Note: Overwrite creates a default layout for {{plural:$1| one LED| all $1 LEDs}} given by the hardware LED count", "conf_leds_note_layout_overwrite": "Note: Overwrite creates a default layout for {{plural:$1| one LED| all $1 LEDs}} given by the hardware LED count",
"conf_leds_optgroup_RPiGPIO": "RPi GPIO", "conf_leds_optgroup_RPiGPIO": "RPi GPIO",
"conf_leds_optgroup_RPiPWM": "RPi PWM", "conf_leds_optgroup_RPiPWM": "RPi PWM",
"conf_leds_optgroup_RPiSPI": "RPi SPI", "conf_leds_optgroup_RPiSPI": "SPI",
"conf_leds_optgroup_debug": "Debug", "conf_leds_optgroup_debug": "Debug",
"conf_leds_optgroup_network": "Network", "conf_leds_optgroup_network": "Network",
"conf_leds_optgroup_other": "Other", "conf_leds_optgroup_other": "Other",
@ -567,6 +567,11 @@
"edt_dev_enum_sub_min_cool_adjust": "Subtract cool white", "edt_dev_enum_sub_min_cool_adjust": "Subtract cool white",
"edt_dev_enum_sub_min_warm_adjust": "Subtract warm white", "edt_dev_enum_sub_min_warm_adjust": "Subtract warm white",
"edt_dev_enum_subtract_minimum": "Subtract minimum", "edt_dev_enum_subtract_minimum": "Subtract minimum",
"edt_dev_enum_cold_white": "Cold white",
"edt_dev_enum_neutral_white": "Neutral white",
"edt_dev_enum_auto": "Auto",
"edt_dev_enum_auto_max": "Auto max",
"edt_dev_enum_auto_accurate": "Auto accurate",
"edt_dev_enum_white_off": "White off", "edt_dev_enum_white_off": "White off",
"edt_dev_general_autostart_title": "Autostart", "edt_dev_general_autostart_title": "Autostart",
"edt_dev_general_autostart_title_info": "The LED device is switched-on during startup or not", "edt_dev_general_autostart_title_info": "The LED device is switched-on during startup or not",

View File

@ -18,7 +18,7 @@ var bottomRight2bottomLeft = null;
var bottomLeft2topLeft = null; var bottomLeft2topLeft = null;
var toggleKeystoneCorrectionArea = false; var toggleKeystoneCorrectionArea = false;
var devRPiSPI = ['apa102', 'apa104', 'ws2801', 'lpd6803', 'lpd8806', 'p9813', 'sk6812spi', 'sk6822spi', 'sk9822', 'ws2812spi']; var devSPI = ['apa102', 'apa104', 'ws2801', 'lpd6803', 'lpd8806', 'p9813', 'sk6812spi', 'sk6822spi', 'sk9822', 'ws2812spi'];
var devRPiPWM = ['ws281x']; var devRPiPWM = ['ws281x'];
var devRPiGPIO = ['piblaster']; var devRPiGPIO = ['piblaster'];
var devNET = ['atmoorb', 'cololight', 'fadecandy', 'philipshue', 'nanoleaf', 'razer', 'tinkerforge', 'tpm2net', 'udpe131', 'udpartnet', 'udpddp', 'udph801', 'udpraw', 'wled', 'yeelight']; var devNET = ['atmoorb', 'cololight', 'fadecandy', 'philipshue', 'nanoleaf', 'razer', 'tinkerforge', 'tpm2net', 'udpe131', 'udpartnet', 'udpddp', 'udph801', 'udpraw', 'wled', 'yeelight'];
@ -1139,7 +1139,15 @@ $(document).ready(function () {
}); });
hwLedCountDefault = 1; hwLedCountDefault = 1;
colorOrderDefault = "rgb";
switch (ledType) {
case "sk6812spi":
colorOrderDefault = "grb";
break;
default:
colorOrderDefault = "rgb";
}
break; break;
case "philipshue": case "philipshue":
@ -1647,7 +1655,7 @@ $(document).ready(function () {
optArr[5] = []; optArr[5] = [];
for (var idx = 0; idx < ledDevices.length; idx++) { for (var idx = 0; idx < ledDevices.length; idx++) {
if ($.inArray(ledDevices[idx], devRPiSPI) != -1) if ($.inArray(ledDevices[idx], devSPI) != -1)
optArr[0].push(ledDevices[idx]); optArr[0].push(ledDevices[idx]);
else if ($.inArray(ledDevices[idx], devRPiPWM) != -1) else if ($.inArray(ledDevices[idx], devRPiPWM) != -1)
optArr[1].push(ledDevices[idx]); optArr[1].push(ledDevices[idx]);
@ -1899,7 +1907,7 @@ var updateOutputSelectList = function (ledType, discoveryInfo) {
ledTypeGroup = "devNET"; ledTypeGroup = "devNET";
} else if ($.inArray(ledType, devSerial) != -1) { } else if ($.inArray(ledType, devSerial) != -1) {
ledTypeGroup = "devSerial"; ledTypeGroup = "devSerial";
} else if ($.inArray(ledType, devRPiSPI) != -1) { } else if ($.inArray(ledType, devSPI) != -1) {
ledTypeGroup = "devRPiSPI"; ledTypeGroup = "devRPiSPI";
} else if ($.inArray(ledType, devRPiGPIO) != -1) { } else if ($.inArray(ledType, devRPiGPIO) != -1) {
ledTypeGroup = "devRPiGPIO"; ledTypeGroup = "devRPiGPIO";

View File

@ -87,14 +87,14 @@ cd $HYPERION_HOME
```console ```console
sudo apt-get update sudo apt-get update
sudo apt-get install git cmake build-essential qtbase5-dev libqt5serialport5-dev libqt5sql5-sqlite libqt5svg5-dev libqt5x11extras5-dev libusb-1.0-0-dev python3-dev libasound2-dev libturbojpeg0-dev libjpeg-dev libssl-dev sudo apt-get install git cmake build-essential qtbase5-dev libqt5serialport5-dev libqt5sql5-sqlite libqt5svg5-dev libqt5x11extras5-dev libusb-1.0-0-dev python3-dev libasound2-dev libturbojpeg0-dev libjpeg-dev libssl-dev libftdi1-dev
``` ```
**Ubuntu (22.04+) - Qt6 based** **Ubuntu (22.04+) - Qt6 based**
```console ```console
sudo apt-get update sudo apt-get update
sudo apt-get install git cmake build-essential qt6-base-dev libqt6serialport6-dev libvulkan-dev libgl1-mesa-dev libusb-1.0-0-dev python3-dev libasound2-dev libturbojpeg0-dev libjpeg-dev libssl-dev pkg-config sudo apt-get install git cmake build-essential qt6-base-dev libqt6serialport6-dev libvulkan-dev libgl1-mesa-dev libusb-1.0-0-dev python3-dev libasound2-dev libturbojpeg0-dev libjpeg-dev libssl-dev pkg-config libftdi1-dev
``` ```
**For Linux X11/XCB grabber support** **For Linux X11/XCB grabber support**
@ -136,7 +136,7 @@ See [AUR](https://aur.archlinux.org/packages/?O=0&SeB=nd&K=hyperion&outdated=&SB
The following dependencies are needed to build hyperion.ng on fedora. The following dependencies are needed to build hyperion.ng on fedora.
```console ```console
sudo dnf -y groupinstall "Development Tools" sudo dnf -y groupinstall "Development Tools"
sudo dnf install python3-devel qt-devel qt5-qtbase-devel qt5-qtserialport-devel xrandr xcb-util-image-devel qt5-qtx11extras-devel alsa-lib-devel turbojpeg-devel libusb-devel xcb-util-devel dbus-devel openssl-devel fedora-packager rpmdevtools gcc libcec-devel sudo dnf install python3-devel qt-devel qt5-qtbase-devel qt5-qtserialport-devel xrandr xcb-util-image-devel qt5-qtx11extras-devel alsa-lib-devel turbojpeg-devel libusb-devel xcb-util-devel dbus-devel openssl-devel fedora-packager rpmdevtools gcc libcec-devel libftdi1-dev
``` ```
After installing the dependencies, you can continue with the compile instructions later on this page (the more detailed way..). After installing the dependencies, you can continue with the compile instructions later on this page (the more detailed way..).
@ -145,7 +145,7 @@ To install on OS X you either need Homebrew or Macport but Homebrew is the recom
First you need to install the dependencies: First you need to install the dependencies:
```console ```console
brew install qt5 python3 cmake libusb doxygen brew install qt5 python3 cmake libusb doxygen libftdi
``` ```
## Windows ## Windows

View File

@ -11,7 +11,12 @@ namespace RGBW {
SUBTRACT_MINIMUM, SUBTRACT_MINIMUM,
SUB_MIN_WARM_ADJUST, SUB_MIN_WARM_ADJUST,
SUB_MIN_COOL_ADJUST, SUB_MIN_COOL_ADJUST,
WHITE_OFF WHITE_OFF,
COLD_WHITE,
NEUTRAL_WHITE,
AUTO,
AUTO_MAX,
AUTO_ACCURATE
}; };
WhiteAlgorithm stringToWhiteAlgorithm(const QString& str); WhiteAlgorithm stringToWhiteAlgorithm(const QString& str);

View File

@ -42,7 +42,7 @@ if ( ENABLE_DEV_SERIAL )
FILE ( GLOB Leddevice_SERIAL_SOURCES "${CURRENT_SOURCE_DIR}/dev_serial/*.h" "${CURRENT_SOURCE_DIR}/dev_serial/*.cpp") FILE ( GLOB Leddevice_SERIAL_SOURCES "${CURRENT_SOURCE_DIR}/dev_serial/*.h" "${CURRENT_SOURCE_DIR}/dev_serial/*.cpp")
endif() endif()
if ( ENABLE_DEV_SPI ) if ( ENABLE_DEV_SPI OR ENABLE_DEV_FTDI )
FILE ( GLOB Leddevice_SPI_SOURCES "${CURRENT_SOURCE_DIR}/dev_spi/*.h" "${CURRENT_SOURCE_DIR}/dev_spi/*.cpp") FILE ( GLOB Leddevice_SPI_SOURCES "${CURRENT_SOURCE_DIR}/dev_spi/*.h" "${CURRENT_SOURCE_DIR}/dev_spi/*.cpp")
endif() endif()
@ -164,3 +164,10 @@ if(ENABLE_MDNS)
target_link_libraries(leddevice mdns) target_link_libraries(leddevice mdns)
endif() endif()
if( ENABLE_DEV_FTDI )
find_package(PkgConfig REQUIRED)
pkg_check_modules(LIB_FTDI REQUIRED IMPORTED_TARGET libftdi1 )
target_include_directories(leddevice PRIVATE PkgConfig::LIB_FTDI)
target_link_libraries(leddevice PkgConfig::LIB_FTDI)
endif()

View File

@ -1,195 +1,359 @@
 // Local Hyperion includes
#include "ProviderSpi.h"
#ifdef ENABLE_DEV_SPI
// STL includes // STL includes
#include <cstring> #include <cstring>
#include <cstdio> #include <cstdio>
#include <iostream> #include <iostream>
#include <cerrno> #include <cerrno>
// Linux includes // Linux includes
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h> #include <unistd.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
// Local Hyperion includes
#include "ProviderSpi.h"
#include <utils/Logger.h>
// qt includes // qt includes
#include <QDir> #include <QDir>
#endif
#ifdef ENABLE_DEV_FTDI
#include <ftdi.h>
#include <libusb.h>
#include <utils/WaitTime.h>
#define FTDI_CHECK_RESULT(statement) if (statement) {setInError(ftdi_get_error_string(_ftdic)); return retVal;}
#define ANY_FTDI_VENDOR 0x0
#define ANY_FTDI_PRODUCT 0x0
namespace Pin {
// enumerate the AD bus for convenience.
enum bus_t {
SK = 0x01, // ADBUS0, SPI data clock
DO = 0x02, // ADBUS1, SPI data out
CS = 0x08, // ADBUS3, SPI chip select, active low
};
}
const unsigned char pinInitialState = Pin::CS;
// Use these pins as outputs
const unsigned char pinDirection = Pin::SK | Pin::DO | Pin::CS;
#endif
#include <utils/Logger.h>
// Constants // Constants
namespace { namespace {
const bool verbose = false; const bool verbose = false;
#ifdef ENABLE_DEV_SPI
// SPI discovery service
const char DISCOVERY_DIRECTORY[] = "/dev/";
const char DISCOVERY_FILEPATTERN[] = "spidev*";
#endif
// SPI discovery service const QString ImplementationSPIDEV = QString("spidev");
const char DISCOVERY_DIRECTORY[] = "/dev/"; const QString ImplementationFTDI = QString("ftdi");
const char DISCOVERY_FILEPATTERN[] = "spidev*";
} //End of constants } //End of constants
ProviderSpi::ProviderSpi(const QJsonObject &deviceConfig) ProviderSpi::ProviderSpi(const QJsonObject &deviceConfig)
: LedDevice(deviceConfig) : LedDevice(deviceConfig), _deviceName("/dev/spidev0.0"), _baudRate_Hz(1000000)
, _deviceName("/dev/spidev0.0") #ifdef ENABLE_DEV_SPI
, _baudRate_Hz(1000000) , _fid(-1)
, _fid(-1) , _spiMode(SPI_MODE_0)
, _spiMode(SPI_MODE_0) , _spiDataInvert(false)
, _spiDataInvert(false) #endif
{ , _spiImplementation(SPIDEV) {
memset(&_spi, 0, sizeof(_spi)); #ifdef ENABLE_DEV_SPI
_latchTime_ms = 1; memset(&_spi, 0, sizeof(_spi));
_latchTime_ms = 1;
#endif
} }
ProviderSpi::~ProviderSpi() ProviderSpi::~ProviderSpi() {
{
} }
bool ProviderSpi::init(const QJsonObject &deviceConfig) bool ProviderSpi::init(const QJsonObject &deviceConfig) {
{ bool isInitOK = false;
bool isInitOK = false;
// Initialise sub-class // Initialise sub-class
if ( LedDevice::init(deviceConfig) ) if (LedDevice::init(deviceConfig)) {
{ _deviceName = deviceConfig["output"].toString(_deviceName);
_deviceName = deviceConfig["output"].toString(_deviceName); _baudRate_Hz = deviceConfig["rate"].toInt(_baudRate_Hz);
_baudRate_Hz = deviceConfig["rate"].toInt(_baudRate_Hz); #ifdef ENABLE_DEV_SPI
_spiMode = deviceConfig["spimode"].toInt(_spiMode); _spiMode = deviceConfig["spimode"].toInt(_spiMode);
_spiDataInvert = deviceConfig["invert"].toBool(_spiDataInvert); _spiDataInvert = deviceConfig["invert"].toBool(_spiDataInvert);
Debug(_log, "_spiDataInvert [%d], _spiMode [%d]", _spiDataInvert, _spiMode);
#endif
bool isFtdiImplementation = _deviceName.startsWith("d:")
or _deviceName.startsWith("i:")
or _deviceName.startsWith("s:");
_spiImplementation = isFtdiImplementation ? FTDI : SPIDEV;
Debug(_log, "_baudRate_Hz [%d], _latchTime_ms [%d]", _baudRate_Hz, _latchTime_ms); Debug(_log, "_baudRate_Hz [%d], _latchTime_ms [%d]", _baudRate_Hz, _latchTime_ms);
Debug(_log, "_spiDataInvert [%d], _spiMode [%d]", _spiDataInvert, _spiMode);
isInitOK = true;
} isInitOK = true;
return isInitOK; }
return isInitOK;
} }
int ProviderSpi::open() int ProviderSpi::open() {
{ int retVal = -1;
int retval = -1; QString errortext;
QString errortext; _isDeviceReady = false;
_isDeviceReady = false; if (_spiImplementation == SPIDEV) {
#ifdef ENABLE_DEV_SPI
const int bitsPerWord = 8;
const int bitsPerWord = 8; _fid = ::open(QSTRING_CSTR(_deviceName), O_RDWR);
_fid = ::open(QSTRING_CSTR(_deviceName), O_RDWR); if (_fid < 0)
{
errortext = QString ("Failed to open device (%1). Error message: %2").arg(_deviceName, strerror(errno));
retVal = -1;
}
else
{
if (ioctl(_fid, SPI_IOC_WR_MODE, &_spiMode) == -1 || ioctl(_fid, SPI_IOC_RD_MODE, &_spiMode) == -1)
{
retVal = -2;
}
else
{
if (ioctl(_fid, SPI_IOC_WR_BITS_PER_WORD, &bitsPerWord) == -1 || ioctl(_fid, SPI_IOC_RD_BITS_PER_WORD, &bitsPerWord) == -1)
{
retVal = -4;
}
else
{
if (ioctl(_fid, SPI_IOC_WR_MAX_SPEED_HZ, &_baudRate_Hz) == -1 || ioctl(_fid, SPI_IOC_RD_MAX_SPEED_HZ, &_baudRate_Hz) == -1)
{
retVal = -6;
}
else
{
// Everything OK -> enable device
_isDeviceReady = true;
retVal = 0;
}
}
}
if ( retVal < 0 )
{
errortext = QString ("Failed to open device (%1). Error Code: %2").arg(_deviceName).arg(retVal);
}
}
if (_fid < 0) if ( retVal < 0 )
{ {
errortext = QString ("Failed to open device (%1). Error message: %2").arg(_deviceName, strerror(errno)); this->setInError( errortext );
retval = -1; }
} #endif
else } else if (_spiImplementation == FTDI) {
{ #ifdef ENABLE_DEV_FTDI
if (ioctl(_fid, SPI_IOC_WR_MODE, &_spiMode) == -1 || ioctl(_fid, SPI_IOC_RD_MODE, &_spiMode) == -1)
{
retval = -2;
}
else
{
if (ioctl(_fid, SPI_IOC_WR_BITS_PER_WORD, &bitsPerWord) == -1 || ioctl(_fid, SPI_IOC_RD_BITS_PER_WORD, &bitsPerWord) == -1)
{
retval = -4;
}
else
{
if (ioctl(_fid, SPI_IOC_WR_MAX_SPEED_HZ, &_baudRate_Hz) == -1 || ioctl(_fid, SPI_IOC_RD_MAX_SPEED_HZ, &_baudRate_Hz) == -1)
{
retval = -6;
}
else
{
// Everything OK -> enable device
_isDeviceReady = true;
retval = 0;
}
}
}
if ( retval < 0 )
{
errortext = QString ("Failed to open device (%1). Error Code: %2").arg(_deviceName).arg(retval);
}
}
if ( retval < 0 ) _ftdic = ftdi_new();
{
this->setInError( errortext );
}
return retval; Debug(_log, "Opening FTDI device=%s", QSTRING_CSTR(_deviceName));
FTDI_CHECK_RESULT((retVal = ftdi_usb_open_string(_ftdic, QSTRING_CSTR(_deviceName))) < 0);
/* doing this disable resets things if they were in a bad state */
FTDI_CHECK_RESULT((retVal = ftdi_disable_bitbang(_ftdic)) < 0);
FTDI_CHECK_RESULT((retVal = ftdi_setflowctrl(_ftdic, SIO_DISABLE_FLOW_CTRL)) < 0);
FTDI_CHECK_RESULT((retVal = ftdi_set_bitmode(_ftdic, 0x00, BITMODE_RESET)) < 0);
FTDI_CHECK_RESULT((retVal = ftdi_set_bitmode(_ftdic, 0xff, BITMODE_MPSSE)) < 0);
double reference_clock = 60e6;
int divisor = (reference_clock / 2 / _baudRate_Hz) - 1;
std::vector<uint8_t> buf = {
DIS_DIV_5,
TCK_DIVISOR,
static_cast<unsigned char>(divisor),
static_cast<unsigned char>(divisor >> 8),
SET_BITS_LOW, // opcode: set low bits (ADBUS[0-7]
pinInitialState, // argument: inital pin state
pinDirection
};
FTDI_CHECK_RESULT((retVal = ftdi_write_data(_ftdic, buf.data(), buf.size())) != buf.size());
_isDeviceReady = true;
#endif
}
return retVal;
} }
int ProviderSpi::close() int ProviderSpi::close() {
{ // LedDevice specific closing activities
// LedDevice specific closing activities int retVal = 0;
int retval = 0; _isDeviceReady = false;
_isDeviceReady = false; if (_spiImplementation == SPIDEV) {
#ifdef ENABLE_DEV_SPI
// Test, if device requires closing // Test, if device requires closing
if ( _fid > -1 ) if ( _fid > -1 )
{ {
// Close device // Close device
if ( ::close(_fid) != 0 ) if ( ::close(_fid) != 0 )
{ {
Error( _log, "Failed to close device (%s). Error message: %s", QSTRING_CSTR(_deviceName), strerror(errno) ); Error( _log, "Failed to close device (%s). Error message: %s", QSTRING_CSTR(_deviceName), strerror(errno) );
retval = -1; retVal = -1;
} }
} }
return retval; #endif
} else if (_spiImplementation == FTDI) {
#ifdef ENABLE_DEV_FTDI
if (_ftdic != nullptr) {
Debug(_log, "Closing FTDI device");
// Delay to give time to push color black from writeBlack() into the led,
// otherwise frame transmission will be terminated half way through
wait(30);
ftdi_set_bitmode(_ftdic, 0x00, BITMODE_RESET);
ftdi_usb_close(_ftdic);
ftdi_free(_ftdic);
_ftdic = nullptr;
}
#endif
}
return retVal;
} }
int ProviderSpi::writeBytes(unsigned size, const uint8_t * data) int ProviderSpi::writeBytes(unsigned size, const uint8_t *data) {
{ int retVal = 0;
if (_fid < 0) if (_spiImplementation == SPIDEV) {
{ #ifdef ENABLE_DEV_SPI
return -1; if (_fid < 0)
} {
return -1;
}
uint8_t * newdata {nullptr}; uint8_t * newdata {nullptr};
_spi.tx_buf = __u64(data); _spi.tx_buf = __u64(data);
_spi.len = __u32(size); _spi.len = __u32(size);
if (_spiDataInvert) if (_spiDataInvert)
{ {
newdata = static_cast<uint8_t *>(malloc(size)); newdata = static_cast<uint8_t *>(malloc(size));
for (unsigned i = 0; i<size; i++) { for (unsigned i = 0; i<size; i++) {
newdata[i] = data[i] ^ 0xff; newdata[i] = data[i] ^ 0xff;
} }
_spi.tx_buf = __u64(newdata); _spi.tx_buf = __u64(newdata);
} }
int retVal = ioctl(_fid, SPI_IOC_MESSAGE(1), &_spi); retVal = ioctl(_fid, SPI_IOC_MESSAGE(1), &_spi);
ErrorIf((retVal < 0), _log, "SPI failed to write. errno: %d, %s", errno, strerror(errno) ); ErrorIf((retVal < 0), _log, "SPI failed to write. errno: %d, %s", errno, strerror(errno) );
free (newdata); free (newdata);
#endif
} else if (_spiImplementation == FTDI) {
#ifdef ENABLE_DEV_FTDI
int count_arg = size - 1;
std::vector<uint8_t> buf = {
SET_BITS_LOW,
pinInitialState & ~Pin::CS,
pinDirection,
MPSSE_DO_WRITE | MPSSE_WRITE_NEG,
static_cast<unsigned char>(count_arg),
static_cast<unsigned char>(count_arg >> 8),
// LED's data will be inserted here
SET_BITS_LOW,
pinInitialState | Pin::CS,
pinDirection
};
// insert before last SET_BITS_LOW command
// SET_BITS_LOW takes 2 arguments, so we're inserting data in -3 position from the end
buf.insert(buf.end() - 3, &data[0], &data[size]);
return retVal; FTDI_CHECK_RESULT((retVal = ftdi_write_data(_ftdic, buf.data(), buf.size())) != buf.size());
#endif
}
return retVal;
} }
QJsonObject ProviderSpi::discover(const QJsonObject& /*params*/) QJsonObject ProviderSpi::discover(const QJsonObject & /*params*/) {
{ QJsonObject devicesDiscovered;
QJsonObject devicesDiscovered; devicesDiscovered.insert("ledDeviceType", _activeDeviceType);
devicesDiscovered.insert("ledDeviceType", _activeDeviceType ); QJsonArray deviceList;
QJsonArray deviceList; #ifdef ENABLE_DEV_SPI
QDir deviceDirectory (DISCOVERY_DIRECTORY);
QStringList deviceFilter(DISCOVERY_FILEPATTERN);
deviceDirectory.setNameFilters(deviceFilter);
deviceDirectory.setSorting(QDir::Name);
QFileInfoList deviceFiles = deviceDirectory.entryInfoList(QDir::System);
QDir deviceDirectory (DISCOVERY_DIRECTORY); QFileInfoList::const_iterator deviceFileIterator;
QStringList deviceFilter(DISCOVERY_FILEPATTERN); for (deviceFileIterator = deviceFiles.constBegin(); deviceFileIterator != deviceFiles.constEnd(); ++deviceFileIterator)
deviceDirectory.setNameFilters(deviceFilter); {
deviceDirectory.setSorting(QDir::Name); QJsonObject deviceInfo;
QFileInfoList deviceFiles = deviceDirectory.entryInfoList(QDir::System); deviceInfo.insert("deviceName", (*deviceFileIterator).fileName().remove(0,6));
deviceInfo.insert("systemLocation", (*deviceFileIterator).absoluteFilePath());
deviceList.append(deviceInfo);
}
#endif
#ifdef ENABLE_DEV_FTDI
struct ftdi_device_list *devlist;
struct ftdi_context *ftdic;
QFileInfoList::const_iterator deviceFileIterator; ftdic = ftdi_new();
for (deviceFileIterator = deviceFiles.constBegin(); deviceFileIterator != deviceFiles.constEnd(); ++deviceFileIterator)
{
QJsonObject deviceInfo;
deviceInfo.insert("deviceName", (*deviceFileIterator).fileName().remove(0,6));
deviceInfo.insert("systemLocation", (*deviceFileIterator).absoluteFilePath());
deviceList.append(deviceInfo);
}
devicesDiscovered.insert("devices", deviceList);
DebugIf(verbose,_log, "devicesDiscovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData()); if (ftdi_usb_find_all(ftdic, &devlist, ANY_FTDI_VENDOR, ANY_FTDI_PRODUCT) > 0) {
struct ftdi_device_list *curdev = devlist;
QMap<QString, uint8_t> deviceIndexes;
return devicesDiscovered; while (curdev) {
libusb_device_descriptor desc;
int rc = libusb_get_device_descriptor(curdev->dev, &desc);
if (rc == 0) {
QString vendorIdentifier = QString("0x%1").arg(desc.idVendor, 4, 16, QChar{'0'});
QString productIdentifier = QString("0x%1").arg(desc.idProduct, 4, 16, QChar{'0'});
QString vendorAndProduct = QString("%1:%2")
.arg(vendorIdentifier)
.arg(productIdentifier);
uint8_t deviceIndex = deviceIndexes.value(vendorAndProduct, 0);
char serial_string[128] = {0};
char manufacturer_string[128] = {0};
char description_string[128] = {0};
ftdi_usb_get_strings2(ftdic, curdev->dev, manufacturer_string, 128, description_string, 128,
serial_string, 128);
QString serialNumber{serial_string};
QString ftdiOpenString;
if (!serialNumber.isEmpty()) {
ftdiOpenString = QString("s:%1:%2").arg(vendorAndProduct).arg(serialNumber);
} else {
ftdiOpenString = QString("i:%1:%2").arg(vendorAndProduct).arg(deviceIndex);
}
deviceList.push_back(QJsonObject{
{"ftdiOpenString", ftdiOpenString},
{"vendorIdentifier", vendorIdentifier},
{"productIdentifier", productIdentifier},
{"deviceIndex", deviceIndex},
{"serialNumber", serialNumber},
{"manufacturer", manufacturer_string},
{"description", description_string},
{"deviceName", description_string},
{"systemLocation", ftdiOpenString}
});
deviceIndexes.insert(vendorAndProduct, deviceIndex + 1);
}
curdev = curdev->next;
}
}
ftdi_list_free(&devlist);
ftdi_free(ftdic);
#endif
devicesDiscovered.insert("devices", deviceList);
DebugIf(verbose, _log, "devicesDiscovered: [%s]",
QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData());
return devicesDiscovered;
} }

View File

@ -1,11 +1,22 @@
#pragma once #pragma once
#include "HyperionConfig.h"
// Linux-SPI includes #ifdef ENABLE_DEV_SPI
#include <linux/spi/spidev.h> // Linux-SPI includes
#include <linux/spi/spidev.h>
#endif
#ifdef ENABLE_DEV_FTDI
#include <ftdi.h>
#endif
// Hyperion includes // Hyperion includes
#include <leddevice/LedDevice.h> #include <leddevice/LedDevice.h>
enum SpiImplementation { SPIDEV, FTDI };
/// ///
/// The ProviderSpi implements an abstract base-class for LedDevices using the SPI-device. /// The ProviderSpi implements an abstract base-class for LedDevices using the SPI-device.
/// ///
@ -66,7 +77,7 @@ protected:
/// The used baudrate of the output device /// The used baudrate of the output device
int _baudRate_Hz; int _baudRate_Hz;
#ifdef ENABLE_DEV_SPI
/// The File Identifier of the opened output device (or -1 if not opened) /// The File Identifier of the opened output device (or -1 if not opened)
int _fid; int _fid;
@ -78,4 +89,12 @@ protected:
/// The transfer structure for writing to the spi-device /// The transfer structure for writing to the spi-device
spi_ioc_transfer _spi; spi_ioc_transfer _spi;
#endif
#ifdef ENABLE_DEV_FTDI
/// The Ftdi serial-device
struct ftdi_context *_ftdic;
#endif
SpiImplementation _spiImplementation;
}; };

View File

@ -3,6 +3,8 @@
#include <utils/RgbToRgbw.h> #include <utils/RgbToRgbw.h>
#include <utils/Logger.h> #include <utils/Logger.h>
#define ROUND_DIVIDE(number, denom) (((number) + (denom) / 2) / (denom))
namespace RGBW { namespace RGBW {
WhiteAlgorithm stringToWhiteAlgorithm(const QString& str) WhiteAlgorithm stringToWhiteAlgorithm(const QString& str)
@ -19,7 +21,27 @@ WhiteAlgorithm stringToWhiteAlgorithm(const QString& str)
{ {
return WhiteAlgorithm::SUB_MIN_COOL_ADJUST; return WhiteAlgorithm::SUB_MIN_COOL_ADJUST;
} }
if (str.isEmpty() || str == "white_off") if (str == "cold_white")
{
return WhiteAlgorithm::COLD_WHITE;
}
if (str == "neutral_white")
{
return WhiteAlgorithm::NEUTRAL_WHITE;
}
if (str == "auto")
{
return WhiteAlgorithm::AUTO;
}
if (str == "auto_max")
{
return WhiteAlgorithm::AUTO_MAX;
}
if (str == "auto_accurate")
{
return WhiteAlgorithm::AUTO_ACCURATE;
}
if (str.isEmpty() || str == "white_off")
{ {
return WhiteAlgorithm::WHITE_OFF; return WhiteAlgorithm::WHITE_OFF;
} }
@ -77,6 +99,63 @@ void Rgb_to_Rgbw(ColorRgb input, ColorRgbw * output, WhiteAlgorithm algorithm)
output->white = 0; output->white = 0;
break; break;
} }
case WhiteAlgorithm::AUTO_MAX:
{
output->red = input.red;
output->green = input.green;
output->blue = input.blue;
output->white = input.red > input.green ? (input.red > input.blue ? input.red : input.blue) : (input.green > input.blue ? input.green : input.blue);
break;
}
case WhiteAlgorithm::AUTO_ACCURATE:
{
output->white = input.red < input.green ? (input.red < input.blue ? input.red : input.blue) : (input.green < input.blue ? input.green : input.blue);
output->red = input.red - output->white;
output->green = input.green - output->white;
output->blue = input.blue - output->white;
break;
}
case WhiteAlgorithm::AUTO:
{
output->red = input.red;
output->green = input.green;
output->blue = input.blue;
output->white = input.red < input.green ? (input.red < input.blue ? input.red : input.blue) : (input.green < input.blue ? input.green : input.blue);
break;
}
case WhiteAlgorithm::NEUTRAL_WHITE:
case WhiteAlgorithm::COLD_WHITE:
{
//cold white config
uint8_t gain = 0xFF;
uint8_t red = 0xA0;
uint8_t green = 0xA0;
uint8_t blue = 0xA0;
if (algorithm == WhiteAlgorithm::NEUTRAL_WHITE) {
gain = 0xFF;
red = 0xB0;
green = 0xB0;
blue = 0x70;
}
uint8_t _r = qMin((uint32_t)(ROUND_DIVIDE(red * input.red, 0xFF)), (uint32_t)0xFF);
uint8_t _g = qMin((uint32_t)(ROUND_DIVIDE(green * input.green, 0xFF)), (uint32_t)0xFF);
uint8_t _b = qMin((uint32_t)(ROUND_DIVIDE(blue * input.blue, 0xFF)), (uint32_t)0xFF);
output->white = qMin(_r, qMin(_g, _b));
output->red = input.red - _r;
output->green = input.green - _g;
output->blue = input.blue - _b;
uint8_t _w = qMin((uint32_t)(ROUND_DIVIDE(gain * output->white, 0xFF)), (uint32_t)0xFF);
output->white = _w;
break;
}
default: default:
break; break;
} }