From 05e72b17fdbceedbe4a9766ff59bbdf567fef162 Mon Sep 17 00:00:00 2001 From: Ph0N37Ic5 Date: Sat, 24 Feb 2018 13:20:25 +0100 Subject: [PATCH 01/17] Added encoding for WS2812b. --- plugins/spi/SPIOutput.cpp | 93 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/plugins/spi/SPIOutput.cpp b/plugins/spi/SPIOutput.cpp index 336372e387..b9a3275ca8 100644 --- a/plugins/spi/SPIOutput.cpp +++ b/plugins/spi/SPIOutput.cpp @@ -87,11 +87,13 @@ const uint16_t SPIOutput::WS2801_SLOTS_PER_PIXEL = 3; const uint16_t SPIOutput::LPD8806_SLOTS_PER_PIXEL = 3; const uint16_t SPIOutput::P9813_SLOTS_PER_PIXEL = 3; const uint16_t SPIOutput::APA102_SLOTS_PER_PIXEL = 3; +const uint16_t SPIOutput::WS2812b_SLOTS_PER_PIXEL = 3; // Number of bytes that each pixel uses on the SPI wires // (if it differs from 1:1 with colors) const uint16_t SPIOutput::P9813_SPI_BYTES_PER_PIXEL = 4; const uint16_t SPIOutput::APA102_SPI_BYTES_PER_PIXEL = 4; +const uint16_t SPIOutput::WS2812b_SPI_BYTES_PER_PIXEL = 9; const uint16_t SPIOutput::APA102_START_FRAME_BYTES = 4; @@ -316,6 +318,12 @@ bool SPIOutput::InternalWriteDMX(const DmxBuffer &buffer) { case 8: CombinedAPA102Control(buffer); break; + case 9: + IndividualWS2812bControl(buffer); + break; + case 10: + CombinedWS2812bControl(buffer); + break; default: break; } @@ -653,6 +661,91 @@ uint8_t SPIOutput::CalculateAPA102LatchBytes(uint16_t pixel_count) { return latch_bytes; } +void SPIOutput::IndividualWS2812bControl(const DmxBuffer &buffer) { + const unsigned int first_slot = m_start_address - 1; // 0 offset + if (buffer.Size() - first_slot < WS2812b_SLOTS_PER_PIXEL) { + OLA_INFO << "Insufficient DMX data, required " << WS2812b_SLOTS_PER_PIXEL + << ", got " << buffer.Size() - first_slot; + return; + } + + // We always check out the entire string length, even if we only have data + // for part of it + const unsigned int output_length = m_pixel_count * WS2812b_SLOTS_PER_PIXEL; + uint8_t *output = m_backend->Checkout(m_output_number, output_length); + if (!output) { + OLA_INFO << "Unable to create output buffer of required length: " << output_length; + return; + } + + const unsigned int length = std::min(m_pixel_count * WS2812b_SLOTS_PER_PIXEL, + buffer.Size() - first_slot); + + for (unsigned int i = 0; i < length / WS2812b_SLOTS_PER_PIXEL; i++) { + // Convert RGB to GRB + unsigned int offset = first_slot + i * WS2812b_SLOTS_PER_PIXEL; + uint8_t r = buffer.Get(offset); + uint8_t g = buffer.Get(offset + 1); + uint8_t b = buffer.Get(offset + 2); + output[i * WS2812b_SPI_BYTES_PER_PIXEL] = 0x24 | ((g & 0x1) << 1) | ((g & 0x2) << 3) | ((g & 0x4) << 5); + output[i * WS2812b_SPI_BYTES_PER_PIXEL + 1] = 0x49 | ((g & 0x8) >> 1) | ((g & 0x10) << 1); + output[i * WS2812b_SPI_BYTES_PER_PIXEL + 2] = 0x92 | ((g & 0x20) >> 5) | ((g & 0x40) >> 3) | ((g & 0x80) >> 1); + output[i * WS2812b_SPI_BYTES_PER_PIXEL + 3] = 0x24 | ((r & 0x1) << 1) | ((r & 0x2) << 3) | ((r & 0x4) << 5); + output[i * WS2812b_SPI_BYTES_PER_PIXEL + 4] = 0x49 | ((r & 0x8) >> 1) | ((r & 0x10) << 1); + output[i * WS2812b_SPI_BYTES_PER_PIXEL + 5] = 0x92 | ((r & 0x20) >> 5) | ((r & 0x40) >> 3) | ((r & 0x80) >> 1); + output[i * WS2812b_SPI_BYTES_PER_PIXEL + 6] = 0x24 | ((b & 0x1) << 1) | ((b & 0x2) << 3) | ((b & 0x4) << 5); + output[i * WS2812b_SPI_BYTES_PER_PIXEL + 7] = 0x49 | ((b & 0x8) >> 1) | ((b & 0x10) << 1); + output[i * WS2812b_SPI_BYTES_PER_PIXEL + 8] = 0x92 | ((b & 0x20) >> 5) | ((b & 0x40) >> 3) | ((b & 0x80) >> 1); + } + + // write output back... + m_backend->Commit(m_output_number); +} + +void SPIOutput::CombinedWS2812bControl(const DmxBuffer &buffer) { + const unsigned int first_slot = m_start_address - 1; // 0 offset + if (buffer.Size() - first_slot < WS2812b_SLOTS_PER_PIXEL) { + OLA_INFO << "Insufficient DMX data, required " << WS2812b_SLOTS_PER_PIXEL + << ", got " << buffer.Size() - first_slot; + return; + } + + // We always check out the entire string length, even if we only have data + // for part of it + const unsigned int output_length = m_pixel_count * WS2801_SLOTS_PER_PIXEL; + uint8_t *output = m_backend->Checkout(m_output_number, output_length); + if (!output) { + OLA_INFO << "Unable to create output buffer of required length: " << output_length; + return; + } + + // Grab RGB data for conversion to GBR + uint8_t r = buffer.Get(first_slot); + uint8_t g = buffer.Get(first_slot + 1); + uint8_t b = buffer.Get(first_slot + 2); + + // create Pixel Data + uint8_t pixel_data[WS2812b_SPI_BYTES_PER_PIXEL]; + pixel_data[0] = 0x24 | ((g & 0x1) << 1) | ((g & 0x2) << 3) | ((g & 0x4) << 5); + pixel_data[1] = 0x49 | ((g & 0x8) >> 1) | ((g & 0x10) << 1); + pixel_data[2] = 0x92 | ((g & 0x20) >> 5) | ((g & 0x40) >> 3) | ((g & 0x80) >> 1); + pixel_data[3] = 0x24 | ((r & 0x1) << 1) | ((r & 0x2) << 3) | ((r & 0x4) << 5); + pixel_data[4] = 0x49 | ((r & 0x8) >> 1) | ((r & 0x10) << 1); + pixel_data[5] = 0x92 | ((r & 0x20) >> 5) | ((r & 0x40) >> 3) | ((r & 0x80) >> 1); + pixel_data[6] = 0x24 | ((b & 0x1) << 1) | ((b & 0x2) << 3) | ((b & 0x4) << 5); + pixel_data[7] = 0x49 | ((b & 0x8) >> 1) | ((b & 0x10) << 1); + pixel_data[8] = 0x92 | ((b & 0x20) >> 5) | ((b & 0x40) >> 3) | ((b & 0x80) >> 1); + + // set all pixel to same value + for (uint16_t i = 0; i < m_pixel_count; i++) { + uint16_t spi_offset = (i * WS2812b_SPI_BYTES_PER_PIXEL); + memcpy(&output[spi_offset], pixel_data, + WS2812b_SPI_BYTES_PER_PIXEL); + } + + // write output back... + m_backend->Commit(m_output_number); +} RDMResponse *SPIOutput::GetDeviceInfo(const RDMRequest *request) { return ResponderHelper::GetDeviceInfo( From fdfdeb9c540d19272499345db91a741a35296893 Mon Sep 17 00:00:00 2001 From: Ph0N37Ic5 Date: Sat, 24 Feb 2018 13:27:37 +0100 Subject: [PATCH 02/17] Update SPIOutput.h --- plugins/spi/SPIOutput.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/spi/SPIOutput.h b/plugins/spi/SPIOutput.h index 698b052745..7a889b0a19 100644 --- a/plugins/spi/SPIOutput.h +++ b/plugins/spi/SPIOutput.h @@ -113,7 +113,9 @@ class SPIOutput: public ola::rdm::DiscoverableRDMControllerInterface { void CombinedP9813Control(const DmxBuffer &buffer); void IndividualAPA102Control(const DmxBuffer &buffer); void CombinedAPA102Control(const DmxBuffer &buffer); - + void IndividualWS2812bControl(const DmxBuffer &buffer); + void CombinedWS2812bControl(const DmxBuffer &buffer); + unsigned int LPD8806BufferSize() const; void WriteSPIData(const uint8_t *data, unsigned int length); @@ -186,7 +188,9 @@ class SPIOutput: public ola::rdm::DiscoverableRDMControllerInterface { static const uint16_t APA102_SLOTS_PER_PIXEL; static const uint16_t APA102_SPI_BYTES_PER_PIXEL; static const uint16_t APA102_START_FRAME_BYTES; - + static const uint16_t WS2812b_SLOTS_PER_PIXEL; + static const uint16_t WS2812b_SPI_BYTES_PER_PIXEL; + static const ola::rdm::ResponderOps::ParamHandler PARAM_HANDLERS[]; }; From 960ee4b731ac936cc5559496cac3ae62a118d72b Mon Sep 17 00:00:00 2001 From: Ph0N37Ic5 Date: Sat, 24 Feb 2018 18:30:03 +0100 Subject: [PATCH 03/17] Changes to be committed: modified: .gitignore modified: .travis.yml modified: plugins/spi/SPIOutput.cpp modified: plugins/spi/SPIOutput.h modified: plugins/spi/SPIOutputTest.cpp -Syncronized upstream changes. -Applied code standards. -All caps constants. -Bug fix. -WS2812B_DMX_SLOTS_PER_PIXEL changed to WS2812B_SPI_BYTES_PER_PIXEL -Cleanup. -Removed spi_offset variable, set once, used once. -Split out conversion to separate function. --- .gitignore | 1 + .travis.yml | 3 +- plugins/spi/SPIOutput.cpp | 386 +++++++++++++++++++++++++------ plugins/spi/SPIOutput.h | 38 ++- plugins/spi/SPIOutputTest.cpp | 421 +++++++++++++++++++++++++++++++++- 5 files changed, 766 insertions(+), 83 deletions(-) diff --git a/.gitignore b/.gitignore index c154b0a5fc..5d4f3e5173 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.gcda *.gcno *.gcov +*.gch *.la *.lo *.log diff --git a/.travis.yml b/.travis.yml index a1e9616146..b447b14250 100644 --- a/.travis.yml +++ b/.travis.yml @@ -305,7 +305,8 @@ before_install: - if [ "$TASK" == "spellintian" -o "$TASK" == "spellintian-duplicates" ]; then sudo add-apt-repository ppa:waja/trusty-backports -y; sudo apt-get update -qq; sudo apt-get install lintian -y; fi # Install a late enough lintian after_failure: - - cat ${TRAVIS_BUILD_DIR}/ola-*/_build/sub/test-suite.log + - if [ -f ${TRAVIS_BUILD_DIR}/ola-*/_build/test-suite.log ]; then cat ${TRAVIS_BUILD_DIR}/ola-*/_build/test-suite.log; fi + - if [ -f ${TRAVIS_BUILD_DIR}/ola-*/_build/sub/test-suite.log ]; then cat ${TRAVIS_BUILD_DIR}/ola-*/_build/sub/test-suite.log; fi after_success: - if [ "$TASK" = "coverage" ]; then coveralls --gcov /usr/bin/gcov-6 -b . -E '.*Test\.cpp$' -E '.*\.pb\.cc$' -E '.*\.pb\.cpp$' -E '.*\.pb\.h$' -E '.*\.yy\.cpp$' -E '.*\.tab\.cpp$' -E '.*\.tab\.h$' -E '.*/doxygen/examples.*$' --gcov-options '\-lp' > /dev/null; fi diff --git a/plugins/spi/SPIOutput.cpp b/plugins/spi/SPIOutput.cpp index b9a3275ca8..8b095027ed 100644 --- a/plugins/spi/SPIOutput.cpp +++ b/plugins/spi/SPIOutput.cpp @@ -69,7 +69,6 @@ using ola::rdm::RDMResponse; using ola::rdm::ResponderHelper; using ola::rdm::UID; using ola::rdm::UIDSet; -using std::auto_ptr; using std::min; using std::string; using std::vector; @@ -87,15 +86,17 @@ const uint16_t SPIOutput::WS2801_SLOTS_PER_PIXEL = 3; const uint16_t SPIOutput::LPD8806_SLOTS_PER_PIXEL = 3; const uint16_t SPIOutput::P9813_SLOTS_PER_PIXEL = 3; const uint16_t SPIOutput::APA102_SLOTS_PER_PIXEL = 3; -const uint16_t SPIOutput::WS2812b_SLOTS_PER_PIXEL = 3; +const uint16_t SPIOutput::APA102_PB_SLOTS_PER_PIXEL = 4; +const uint16_t SPIOutput::WS2812B_SLOTS_PER_PIXEL = 3; // Number of bytes that each pixel uses on the SPI wires // (if it differs from 1:1 with colors) const uint16_t SPIOutput::P9813_SPI_BYTES_PER_PIXEL = 4; const uint16_t SPIOutput::APA102_SPI_BYTES_PER_PIXEL = 4; -const uint16_t SPIOutput::WS2812b_SPI_BYTES_PER_PIXEL = 9; +const uint16_t SPIOutput::WS2812B_SPI_BYTES_PER_PIXEL = 9; const uint16_t SPIOutput::APA102_START_FRAME_BYTES = 4; +const uint8_t SPIOutput::APA102_LEDFRAME_START_MARK = 0xE0; SPIOutput::RDMOps *SPIOutput::RDMOps::instance = NULL; @@ -125,6 +126,9 @@ const ola::rdm::ResponderOps::ParamHandler { ola::rdm::PID_DMX_PERSONALITY_DESCRIPTION, &SPIOutput::GetPersonalityDescription, NULL}, + { ola::rdm::PID_SLOT_INFO, + &SPIOutput::GetSlotInfo, + NULL}, { ola::rdm::PID_DMX_START_ADDRESS, &SPIOutput::GetDmxStartAddress, &SPIOutput::SetDmxStartAddress}, @@ -182,26 +186,98 @@ SPIOutput::SPIOutput(const UID &uid, SPIBackendInterface *backend, m_spi_device_name = FilenameFromPathOrPath(m_backend->DevicePath()); PersonalityCollection::PersonalityList personalities; - personalities.push_back(Personality(m_pixel_count * WS2801_SLOTS_PER_PIXEL, - "WS2801 Individual Control")); - personalities.push_back(Personality(WS2801_SLOTS_PER_PIXEL, - "WS2801 Combined Control")); - personalities.push_back(Personality(m_pixel_count * LPD8806_SLOTS_PER_PIXEL, - "LPD8806 Individual Control")); - personalities.push_back(Personality(LPD8806_SLOTS_PER_PIXEL, - "LPD8806 Combined Control")); - personalities.push_back(Personality(m_pixel_count * P9813_SLOTS_PER_PIXEL, - "P9813 Individual Control")); - personalities.push_back(Personality(P9813_SLOTS_PER_PIXEL, - "P9813 Combined Control")); - personalities.push_back(Personality(m_pixel_count * APA102_SLOTS_PER_PIXEL, - "APA102 Individual Control")); - personalities.push_back(Personality(APA102_SLOTS_PER_PIXEL, - "APA102 Combined Control")); + // personality description is max 32 characters + + ola::rdm::SlotDataCollection::SlotDataList sd_rgb_combined; + sd_rgb_combined.push_back( + ola::rdm::SlotData::PrimarySlot(ola::rdm::SD_COLOR_ADD_RED, 0)); + sd_rgb_combined.push_back( + ola::rdm::SlotData::PrimarySlot(ola::rdm::SD_COLOR_ADD_GREEN, 0)); + sd_rgb_combined.push_back( + ola::rdm::SlotData::PrimarySlot(ola::rdm::SD_COLOR_ADD_BLUE, 0)); + ola::rdm::SlotDataCollection sdc_rgb_combined; + sdc_rgb_combined = ola::rdm::SlotDataCollection(sd_rgb_combined); + + ola::rdm::SlotDataCollection::SlotDataList sd_irgb_combined; + sd_irgb_combined.push_back( + ola::rdm::SlotData::PrimarySlot(ola::rdm::SD_INTENSITY, 0)); + sd_irgb_combined.push_back( + ola::rdm::SlotData::PrimarySlot(ola::rdm::SD_COLOR_ADD_RED, 0)); + sd_irgb_combined.push_back( + ola::rdm::SlotData::PrimarySlot(ola::rdm::SD_COLOR_ADD_GREEN, 0)); + sd_irgb_combined.push_back( + ola::rdm::SlotData::PrimarySlot(ola::rdm::SD_COLOR_ADD_BLUE, 0)); + ola::rdm::SlotDataCollection sdc_irgb_combined; + sdc_irgb_combined = ola::rdm::SlotDataCollection(sd_irgb_combined); + + personalities.insert( + personalities.begin() + PERS_WS2801_INDIVIDUAL - 1, + Personality(m_pixel_count * WS2801_SLOTS_PER_PIXEL, + "WS2801 Individual Control")); + personalities.insert( + personalities.begin() + PERS_WS2801_COMBINED - 1, + Personality(WS2801_SLOTS_PER_PIXEL, + "WS2801 Combined Control", + sdc_rgb_combined)); + + personalities.insert( + personalities.begin() + PERS_LDP8806_INDIVIDUAL - 1, + Personality(m_pixel_count * LPD8806_SLOTS_PER_PIXEL, + "LPD8806 Individual Control")); + personalities.insert( + personalities.begin() + PERS_LDP8806_COMBINED - 1, + Personality(LPD8806_SLOTS_PER_PIXEL, + "LPD8806 Combined Control", + sdc_rgb_combined)); + + personalities.insert( + personalities.begin() + PERS_P9813_INDIVIDUAL - 1, + Personality(m_pixel_count * P9813_SLOTS_PER_PIXEL, + "P9813 Individual Control")); + personalities.insert( + personalities.begin() + PERS_P9813_COMBINED - 1, + Personality(P9813_SLOTS_PER_PIXEL, + "P9813 Combined Control", + sdc_rgb_combined)); + + personalities.insert( + personalities.begin() + PERS_APA102_INDIVIDUAL - 1, + Personality(m_pixel_count * APA102_SLOTS_PER_PIXEL, + "APA102 Individual Control")); + + personalities.insert( + personalities.begin() + PERS_APA102_COMBINED - 1, + Personality(APA102_SLOTS_PER_PIXEL, + "APA102 Combined Control", + sdc_rgb_combined)); + + personalities.insert( + personalities.begin() + PERS_APA102_PB_INDIVIDUAL - 1, + Personality(m_pixel_count * APA102_PB_SLOTS_PER_PIXEL, + "APA102 Pixel Brightness Individ.")); + + personalities.insert( + personalities.begin() + PERS_APA102_PB_COMBINED - 1, + Personality(APA102_PB_SLOTS_PER_PIXEL, + "APA102 Pixel Brightness Combined", + sdc_irgb_combined)); + + personalities.insert( + personalities.begin() + PERS_WS2812B_PB_INDIVIDUAL - 1, + Personality(m_pixel_count * WS2812B_PB_SLOTS_PER_PIXEL, + "WS2812b Individual Control")); + + personalities.insert( + personalities.begin() + PERS_WS2812B_PB_COMBINED - 1, + Personality(WS2812B_PB_SLOTS_PER_PIXEL, + "WS2812b Combined Control", + sdc_irgb_combined)); + + m_personality_collection.reset(new PersonalityCollection(personalities)); m_personality_manager.reset(new PersonalityManager( m_personality_collection.get())); - m_personality_manager->SetActivePersonality(1); + m_personality_manager->SetActivePersonality(PERS_WS2801_INDIVIDUAL); #ifdef HAVE_GETLOADAVG m_sensors.push_back(new LoadSensor(ola::system::LOAD_AVERAGE_1_MIN, @@ -294,42 +370,43 @@ void SPIOutput::SendRDMRequest(RDMRequest *request, bool SPIOutput::InternalWriteDMX(const DmxBuffer &buffer) { switch (m_personality_manager->ActivePersonalityNumber()) { - case 1: + case PERS_WS2801_INDIVIDUAL: IndividualWS2801Control(buffer); break; - case 2: + case PERS_WS2801_COMBINED: CombinedWS2801Control(buffer); break; - case 3: + case PERS_LDP8806_INDIVIDUAL: IndividualLPD8806Control(buffer); break; - case 4: + case PERS_LDP8806_COMBINED: CombinedLPD8806Control(buffer); break; - case 5: + case PERS_P9813_INDIVIDUAL: IndividualP9813Control(buffer); break; - case 6: + case PERS_P9813_COMBINED: CombinedP9813Control(buffer); break; - case 7: + case PERS_APA102_INDIVIDUAL: IndividualAPA102Control(buffer); break; - case 8: + case PERS_APA102_COMBINED: CombinedAPA102Control(buffer); break; - case 9: - IndividualWS2812bControl(buffer); - break; - case 10: - CombinedWS2812bControl(buffer); - break; + case PERS_APA102_PB_INDIVIDUAL: + IndividualAPA102ControlPixelBrightness(buffer); + break; + case PERS_APA102_PB_COMBINED: + CombinedAPA102ControlPixelBrightness(buffer); + break; default: break; } return true; } + void SPIOutput::IndividualWS2801Control(const DmxBuffer &buffer) { // We always check out the entire string length, even if we only have data // for part of it @@ -383,7 +460,7 @@ void SPIOutput::IndividualLPD8806Control(const DmxBuffer &buffer) { if (!output) return; - const unsigned int length = std::min(m_pixel_count * LPD8806_SLOTS_PER_PIXEL, + const unsigned int length = min(m_pixel_count * LPD8806_SLOTS_PER_PIXEL, buffer.Size() - first_slot); for (unsigned int i = 0; i < length / LPD8806_SLOTS_PER_PIXEL; i++) { @@ -526,7 +603,7 @@ void SPIOutput::IndividualAPA102Control(const DmxBuffer &buffer) { const unsigned int first_slot = m_start_address - 1; // 0 offset // only do something if at least 1 pixel can be updated.. - if (buffer.Size() - first_slot < APA102_SLOTS_PER_PIXEL) { + if ((buffer.Size() - first_slot) < APA102_SLOTS_PER_PIXEL) { OLA_INFO << "Insufficient DMX data, required " << APA102_SLOTS_PER_PIXEL << ", got " << buffer.Size() - first_slot; return; @@ -587,6 +664,81 @@ void SPIOutput::IndividualAPA102Control(const DmxBuffer &buffer) { m_backend->Commit(m_output_number); } + +void SPIOutput::IndividualAPA102ControlPixelBrightness( + const DmxBuffer &buffer) { + // some detailed information on the protocol: + // https://cpldcpu.wordpress.com/2014/11/30/understanding-the-apa102-superled/ + // Data-Struct + // StartFrame: 4 bytes = 32 bits zeros (APA102_START_FRAME_BYTES) + // LEDFrame: + // 1 byte START_MARK + pixel brightness + // 3 bytes color info (Blue, Green, Red) + // EndFrame: (n/2)bits; n = pixel_count + + // calculate DMX-start-address + const unsigned int first_slot = m_start_address - 1; // 0 offset + + // only do something if at least 1 pixel can be updated.. + if ((buffer.Size() - first_slot) < APA102_PB_SLOTS_PER_PIXEL) { + OLA_INFO << "Insufficient DMX data, required " << APA102_PB_SLOTS_PER_PIXEL + << ", got " << buffer.Size() - first_slot; + return; + } + + // We always check out the entire string length, even if we only have data + // for part of it + uint16_t output_length = (m_pixel_count * APA102_SPI_BYTES_PER_PIXEL); + // only add the APA102_START_FRAME_BYTES on the first port!! + if (m_output_number == 0) { + output_length += APA102_START_FRAME_BYTES; + } + uint8_t *output = m_backend->Checkout( + m_output_number, + output_length, + CalculateAPA102LatchBytes(m_pixel_count)); + + // only update SPI data if possible + if (!output) { + return; + } + + // only write to APA102_START_FRAME_BYTES on the first port!! + if (m_output_number == 0) { + // set APA102_START_FRAME_BYTES to zero + memset(output, 0, APA102_START_FRAME_BYTES); + } + + for (uint16_t i = 0; i < m_pixel_count; i++) { + // Convert RGB to APA102 Pixel + uint16_t offset = first_slot + (i * APA102_PB_SLOTS_PER_PIXEL); + + uint16_t spi_offset = (i * APA102_SPI_BYTES_PER_PIXEL); + // only skip APA102_START_FRAME_BYTES on the first port!! + if (m_output_number == 0) { + // We need to avoid the first 4 bytes of the buffer since that acts as a + // start of frame delimiter + spi_offset += APA102_START_FRAME_BYTES; + } + // set pixel data + // only write pixel data if buffer has complete data for this pixel: + if ((buffer.Size() - offset) >= APA102_PB_SLOTS_PER_PIXEL) { + // first Byte: + // 3 bits start mark (111) (APA102_LEDFRAME_START_MARK) + + // 5 bits pixel brightness (datasheet name: global brightness) + output[spi_offset + 0] = (SPIOutput::APA102_LEDFRAME_START_MARK | + CalculateAPA102PixelBrightness(buffer.Get(offset + 0))); + // Convert RGB to APA102 Pixel + output[spi_offset + 1] = buffer.Get(offset + 3); // blue + output[spi_offset + 2] = buffer.Get(offset + 2); // green + output[spi_offset + 3] = buffer.Get(offset + 1); // red + } + } + + // write output back + m_backend->Commit(m_output_number); +} + void SPIOutput::CombinedAPA102Control(const DmxBuffer &buffer) { // for Protocol details see IndividualAPA102Control @@ -594,7 +746,7 @@ void SPIOutput::CombinedAPA102Control(const DmxBuffer &buffer) { const uint16_t first_slot = m_start_address - 1; // 0 offset // check if enough data is there. - if (buffer.Size() - first_slot < APA102_SLOTS_PER_PIXEL) { + if ((buffer.Size() - first_slot) < APA102_SLOTS_PER_PIXEL) { OLA_INFO << "Insufficient DMX data, required " << APA102_SLOTS_PER_PIXEL << ", got " << buffer.Size() - first_slot; return; @@ -644,6 +796,68 @@ void SPIOutput::CombinedAPA102Control(const DmxBuffer &buffer) { m_backend->Commit(m_output_number); } + +void SPIOutput::CombinedAPA102ControlPixelBrightness(const DmxBuffer &buffer) { + // for Protocol details see IndividualAPA102Control + // calculate DMX-start-address + const uint16_t first_slot = m_start_address - 1; // 0 offset + + // check if enough data is there. + if ((buffer.Size() - first_slot) < APA102_PB_SLOTS_PER_PIXEL) { + OLA_INFO << "Insufficient DMX data, required " << APA102_PB_SLOTS_PER_PIXEL + << ", got " << buffer.Size() - first_slot; + return; + } + + // We always check out the entire string length, even if we only have data + // for part of it + uint16_t output_length = (m_pixel_count * APA102_SPI_BYTES_PER_PIXEL); + // only add the APA102_START_FRAME_BYTES on the first port!! + if (m_output_number == 0) { + output_length += APA102_START_FRAME_BYTES; + } + uint8_t *output = m_backend->Checkout( + m_output_number, + output_length, + CalculateAPA102LatchBytes(m_pixel_count)); + + // only update SPI data if possible + if (!output) { + return; + } + + // only write to APA102_START_FRAME_BYTES on the first port!! + if (m_output_number == 0) { + // set APA102_START_FRAME_BYTES to zero + memset(output, 0, APA102_START_FRAME_BYTES); + } + + // create Pixel Data + uint8_t pixel_data[APA102_SPI_BYTES_PER_PIXEL]; + // first Byte: + // 3 bits start mark (111) (APA102_LEDFRAME_START_MARK) + + // 5 bits pixel brightness (datasheet name: global brightness) + pixel_data[0] = (SPIOutput::APA102_LEDFRAME_START_MARK | + CalculateAPA102PixelBrightness(buffer.Get(first_slot + 0))); + // color data + pixel_data[1] = buffer.Get(first_slot + 3); // Get Blue + pixel_data[2] = buffer.Get(first_slot + 2); // Get Green + pixel_data[3] = buffer.Get(first_slot + 1); // Get Red + + // set all pixel to same value + for (uint16_t i = 0; i < m_pixel_count; i++) { + uint16_t spi_offset = (i * APA102_SPI_BYTES_PER_PIXEL); + if (m_output_number == 0) { + spi_offset += APA102_START_FRAME_BYTES; + } + memcpy(&output[spi_offset], pixel_data, + APA102_SPI_BYTES_PER_PIXEL); + } + + // write output back... + m_backend->Commit(m_output_number); +} + /** * Calculate Latch Bytes for APA102: * Use at least half the pixel count bits @@ -661,41 +875,58 @@ uint8_t SPIOutput::CalculateAPA102LatchBytes(uint16_t pixel_count) { return latch_bytes; } +/** + * Calculate Pixel Brightness for APA102: + * Map Input to Output range: + * Input is 8bit value (0..255) + * Output is 5bit value (0..31) + */ +uint8_t SPIOutput::CalculateAPA102PixelBrightness(uint8_t brightness) { + return (brightness >> 3); +} + void SPIOutput::IndividualWS2812bControl(const DmxBuffer &buffer) { const unsigned int first_slot = m_start_address - 1; // 0 offset - if (buffer.Size() - first_slot < WS2812b_SLOTS_PER_PIXEL) { - OLA_INFO << "Insufficient DMX data, required " << WS2812b_SLOTS_PER_PIXEL + if (buffer.Size() - first_slot < WS2812B_SLOTS_PER_PIXEL) { + OLA_INFO << "Insufficient DMX data, required " << WS2812B_SLOTS_PER_PIXEL << ", got " << buffer.Size() - first_slot; return; } // We always check out the entire string length, even if we only have data // for part of it - const unsigned int output_length = m_pixel_count * WS2812b_SLOTS_PER_PIXEL; + const unsigned int output_length = m_pixel_count * WS2812B_SPI_BYTES_PER_PIXEL; uint8_t *output = m_backend->Checkout(m_output_number, output_length); if (!output) { OLA_INFO << "Unable to create output buffer of required length: " << output_length; return; } - const unsigned int length = std::min(m_pixel_count * WS2812b_SLOTS_PER_PIXEL, + const unsigned int length = std::min(m_pixel_count * WS2812B_SLOTS_PER_PIXEL, buffer.Size() - first_slot); - for (unsigned int i = 0; i < length / WS2812b_SLOTS_PER_PIXEL; i++) { + for (unsigned int i = 0; i < length / WS2812B_SLOTS_PER_PIXEL; i++) { // Convert RGB to GRB - unsigned int offset = first_slot + i * WS2812b_SLOTS_PER_PIXEL; + unsigned int offset = first_slot + i * WS2812B_SLOTS_PER_PIXEL; uint8_t r = buffer.Get(offset); uint8_t g = buffer.Get(offset + 1); uint8_t b = buffer.Get(offset + 2); - output[i * WS2812b_SPI_BYTES_PER_PIXEL] = 0x24 | ((g & 0x1) << 1) | ((g & 0x2) << 3) | ((g & 0x4) << 5); - output[i * WS2812b_SPI_BYTES_PER_PIXEL + 1] = 0x49 | ((g & 0x8) >> 1) | ((g & 0x10) << 1); - output[i * WS2812b_SPI_BYTES_PER_PIXEL + 2] = 0x92 | ((g & 0x20) >> 5) | ((g & 0x40) >> 3) | ((g & 0x80) >> 1); - output[i * WS2812b_SPI_BYTES_PER_PIXEL + 3] = 0x24 | ((r & 0x1) << 1) | ((r & 0x2) << 3) | ((r & 0x4) << 5); - output[i * WS2812b_SPI_BYTES_PER_PIXEL + 4] = 0x49 | ((r & 0x8) >> 1) | ((r & 0x10) << 1); - output[i * WS2812b_SPI_BYTES_PER_PIXEL + 5] = 0x92 | ((r & 0x20) >> 5) | ((r & 0x40) >> 3) | ((r & 0x80) >> 1); - output[i * WS2812b_SPI_BYTES_PER_PIXEL + 6] = 0x24 | ((b & 0x1) << 1) | ((b & 0x2) << 3) | ((b & 0x4) << 5); - output[i * WS2812b_SPI_BYTES_PER_PIXEL + 7] = 0x49 | ((b & 0x8) >> 1) | ((b & 0x10) << 1); - output[i * WS2812b_SPI_BYTES_PER_PIXEL + 8] = 0x92 | ((b & 0x20) >> 5) | ((b & 0x40) >> 3) | ((b & 0x80) >> 1); + uint8_t low = 0, mid = 0, high = 0; + + WS2812bByteMapper(g, *low, *mid, *high); + output[i * WS2812B_SPI_BYTES_PER_PIXEL] = low; + output[i * WS2812B_SPI_BYTES_PER_PIXEL + 1] = mid; + output[i * WS2812B_SPI_BYTES_PER_PIXEL + 2] = high; + + WS2812bByteMapper(r, *low, *mid, *high); + output[i * WS2812B_SPI_BYTES_PER_PIXEL + 3] = low; + output[i * WS2812B_SPI_BYTES_PER_PIXEL + 4] = mid; + output[i * WS2812B_SPI_BYTES_PER_PIXEL + 5] = high; + + WS2812bByteMapper(b, *low, *mid, *high); + output[i * WS2812B_SPI_BYTES_PER_PIXEL + 6] = low; + output[i * WS2812B_SPI_BYTES_PER_PIXEL + 7] = mid; + output[i * WS2812B_SPI_BYTES_PER_PIXEL + 8] = high; } // write output back... @@ -704,8 +935,8 @@ void SPIOutput::IndividualWS2812bControl(const DmxBuffer &buffer) { void SPIOutput::CombinedWS2812bControl(const DmxBuffer &buffer) { const unsigned int first_slot = m_start_address - 1; // 0 offset - if (buffer.Size() - first_slot < WS2812b_SLOTS_PER_PIXEL) { - OLA_INFO << "Insufficient DMX data, required " << WS2812b_SLOTS_PER_PIXEL + if (buffer.Size() - first_slot < WS2812B_SLOTS_PER_PIXEL) { + OLA_INFO << "Insufficient DMX data, required " << WS2812B_SLOTS_PER_PIXEL << ", got " << buffer.Size() - first_slot; return; } @@ -723,34 +954,49 @@ void SPIOutput::CombinedWS2812bControl(const DmxBuffer &buffer) { uint8_t r = buffer.Get(first_slot); uint8_t g = buffer.Get(first_slot + 1); uint8_t b = buffer.Get(first_slot + 2); + uint8_t low = 0, mid = 0, high = 0; // create Pixel Data - uint8_t pixel_data[WS2812b_SPI_BYTES_PER_PIXEL]; - pixel_data[0] = 0x24 | ((g & 0x1) << 1) | ((g & 0x2) << 3) | ((g & 0x4) << 5); - pixel_data[1] = 0x49 | ((g & 0x8) >> 1) | ((g & 0x10) << 1); - pixel_data[2] = 0x92 | ((g & 0x20) >> 5) | ((g & 0x40) >> 3) | ((g & 0x80) >> 1); - pixel_data[3] = 0x24 | ((r & 0x1) << 1) | ((r & 0x2) << 3) | ((r & 0x4) << 5); - pixel_data[4] = 0x49 | ((r & 0x8) >> 1) | ((r & 0x10) << 1); - pixel_data[5] = 0x92 | ((r & 0x20) >> 5) | ((r & 0x40) >> 3) | ((r & 0x80) >> 1); - pixel_data[6] = 0x24 | ((b & 0x1) << 1) | ((b & 0x2) << 3) | ((b & 0x4) << 5); - pixel_data[7] = 0x49 | ((b & 0x8) >> 1) | ((b & 0x10) << 1); - pixel_data[8] = 0x92 | ((b & 0x20) >> 5) | ((b & 0x40) >> 3) | ((b & 0x80) >> 1); + uint8_t pixel_data[WS2812B_SPI_BYTES_PER_PIXEL]; + + WS2812bByteMapper(g, *low, *mid, *high); + pixel_data[0] = low; + pixel_data[1] = mid; + pixel_data[2] = high; + + WS2812bByteMapper(r, *low, *mid, *high); + pixel_data[3] = low; + pixel_data[4] = mid; + pixel_data[5] = high; + + WS2812bByteMapper(b, *low, *mid, *high); + pixel_data[6] = low; + pixel_data[7] = mid; + pixel_data[8] = high; // set all pixel to same value for (uint16_t i = 0; i < m_pixel_count; i++) { - uint16_t spi_offset = (i * WS2812b_SPI_BYTES_PER_PIXEL); - memcpy(&output[spi_offset], pixel_data, - WS2812b_SPI_BYTES_PER_PIXEL); + memcpy(&output[i * WS2812B_SPI_BYTES_PER_PIXEL], pixel_data, + WS2812B_SPI_BYTES_PER_PIXEL); } // write output back... m_backend->Commit(m_output_number); } +void SPIOutput::WS2812bByteMapper(uint8_t input, uint8_t &low, uint8_t &mid, uint8_t &high) +{ + low = 0x24 | ((input & 0x1) << 1) | ((input & 0x2) << 3) | ((input & 0x4) << 5); + mid = 0x49 | ((input & 0x8) >> 1) | ((input & 0x10) << 1); + high = 0x92 | ((input & 0x20) >> 5) | ((input & 0x40) >> 3) | ((input & 0x80) >> 1); +} + + RDMResponse *SPIOutput::GetDeviceInfo(const RDMRequest *request) { return ResponderHelper::GetDeviceInfo( request, ola::rdm::OLA_SPI_DEVICE_MODEL, - ola::rdm::PRODUCT_CATEGORY_FIXTURE, 4, + ola::rdm::PRODUCT_CATEGORY_FIXTURE, + 5, // RDM software version (increment on personality changes) m_personality_manager.get(), m_start_address, 0, m_sensors.size()); @@ -799,6 +1045,10 @@ RDMResponse *SPIOutput::GetPersonalityDescription(const RDMRequest *request) { request, m_personality_manager.get()); } +RDMResponse *SPIOutput::GetSlotInfo(const RDMRequest *request) { + return ResponderHelper::GetSlotInfo(request, m_personality_manager.get()); +} + RDMResponse *SPIOutput::GetDmxStartAddress(const RDMRequest *request) { return ResponderHelper::GetDmxAddress(request, m_personality_manager.get(), m_start_address); diff --git a/plugins/spi/SPIOutput.h b/plugins/spi/SPIOutput.h index 7a889b0a19..741ac8922a 100644 --- a/plugins/spi/SPIOutput.h +++ b/plugins/spi/SPIOutput.h @@ -38,6 +38,29 @@ namespace spi { class SPIOutput: public ola::rdm::DiscoverableRDMControllerInterface { public: + // definitions for all SPI Personalities + // keep personality order + // - it is important for backwards compatibility + // the integer representation is used to store the configuration in files + // and RDM Personality IDs should also be stable. + // new ones can be added at end. + // remember to increment RDM-Version in + // SPIOutput.cpp SPIOutput::GetDeviceInfo() + enum SPI_PERSONALITY { + PERS_WS2801_INDIVIDUAL = 1, + PERS_WS2801_COMBINED = 2, + PERS_LDP8806_INDIVIDUAL = 3, + PERS_LDP8806_COMBINED = 4, + PERS_P9813_INDIVIDUAL = 5, + PERS_P9813_COMBINED = 6, + PERS_APA102_INDIVIDUAL = 7, + PERS_APA102_COMBINED = 8, + PERS_APA102_PB_INDIVIDUAL, + PERS_APA102_PB_COMBINED, + PERS_WS2812B_INDIVIDUAL, + PERS_WS2812B_COMBINED, + }; + struct Options { std::string device_label; uint8_t pixel_count; @@ -113,9 +136,11 @@ class SPIOutput: public ola::rdm::DiscoverableRDMControllerInterface { void CombinedP9813Control(const DmxBuffer &buffer); void IndividualAPA102Control(const DmxBuffer &buffer); void CombinedAPA102Control(const DmxBuffer &buffer); + void IndividualAPA102ControlPixelBrightness(const DmxBuffer &buffer); + void CombinedAPA102ControlPixelBrightness(const DmxBuffer &buffer); void IndividualWS2812bControl(const DmxBuffer &buffer); void CombinedWS2812bControl(const DmxBuffer &buffer); - + unsigned int LPD8806BufferSize() const; void WriteSPIData(const uint8_t *data, unsigned int length); @@ -140,6 +165,8 @@ class SPIOutput: public ola::rdm::DiscoverableRDMControllerInterface { const ola::rdm::RDMRequest *request); ola::rdm::RDMResponse *GetPersonalityDescription( const ola::rdm::RDMRequest *request); + ola::rdm::RDMResponse *GetSlotInfo( + const ola::rdm::RDMRequest *request); ola::rdm::RDMResponse *GetDmxStartAddress( const ola::rdm::RDMRequest *request); ola::rdm::RDMResponse *SetDmxStartAddress( @@ -176,6 +203,7 @@ class SPIOutput: public ola::rdm::DiscoverableRDMControllerInterface { // Helpers uint8_t P9813CreateFlag(uint8_t red, uint8_t green, uint8_t blue); static uint8_t CalculateAPA102LatchBytes(uint16_t pixel_count); + static uint8_t CalculateAPA102PixelBrightness(uint8_t brightness); static const uint8_t SPI_MODE; static const uint8_t SPI_BITS_PER_WORD; @@ -186,11 +214,13 @@ class SPIOutput: public ola::rdm::DiscoverableRDMControllerInterface { static const uint16_t P9813_SLOTS_PER_PIXEL; static const uint16_t P9813_SPI_BYTES_PER_PIXEL; static const uint16_t APA102_SLOTS_PER_PIXEL; + static const uint16_t APA102_PB_SLOTS_PER_PIXEL; static const uint16_t APA102_SPI_BYTES_PER_PIXEL; static const uint16_t APA102_START_FRAME_BYTES; - static const uint16_t WS2812b_SLOTS_PER_PIXEL; - static const uint16_t WS2812b_SPI_BYTES_PER_PIXEL; - + static const uint8_t APA102_LEDFRAME_START_MARK; + static const uint16_t WS2812B_SLOTS_PER_PIXEL; + static const uint16_t WS2812B_SPI_BYTES_PER_PIXEL; + static const ola::rdm::ResponderOps::ParamHandler PARAM_HANDLERS[]; }; diff --git a/plugins/spi/SPIOutputTest.cpp b/plugins/spi/SPIOutputTest.cpp index ea93d8bf23..c39038864c 100644 --- a/plugins/spi/SPIOutputTest.cpp +++ b/plugins/spi/SPIOutputTest.cpp @@ -47,6 +47,8 @@ class SPIOutputTest: public CppUnit::TestFixture { CPPUNIT_TEST(testCombinedP9813Control); CPPUNIT_TEST(testIndividualAPA102Control); CPPUNIT_TEST(testCombinedAPA102Control); + CPPUNIT_TEST(testIndividualAPA102ControlPixelBrightness); + CPPUNIT_TEST(testCombinedAPA102ControlPixelBrightness); CPPUNIT_TEST_SUITE_END(); public: @@ -65,6 +67,8 @@ class SPIOutputTest: public CppUnit::TestFixture { void testCombinedP9813Control(); void testIndividualAPA102Control(); void testCombinedAPA102Control(); + void testIndividualAPA102ControlPixelBrightness(); + void testCombinedAPA102ControlPixelBrightness(); private: UID m_uid; @@ -83,16 +87,24 @@ void SPIOutputTest::setUp() { void SPIOutputTest::testDescription() { FakeSPIBackend backend(2); SPIOutput output1(m_uid, &backend, SPIOutput::Options(0, "Test SPI Device")); + SPIOutput::Options options(1, "Test SPI Device"); options.pixel_count = 32; SPIOutput output2(m_uid, &backend, options); + // check default constructor values OLA_ASSERT_EQ( string("Output 0, WS2801 Individual Control, 75 slots @ 1." " (707a:00000000)"), output1.Description()); OLA_ASSERT_EQ(static_cast(1), output1.GetStartAddress()); + OLA_ASSERT_EQ( + static_cast(SPIOutput::PERS_WS2801_INDIVIDUAL), + output1.GetPersonality()); + // Test for backwards compatibility OLA_ASSERT_EQ(static_cast(1), output1.GetPersonality()); + + // check default constructor values for output2 OLA_ASSERT_EQ( string("Output 1, WS2801 Individual Control, 96 slots @ 1." " (707a:00000000)"), @@ -100,12 +112,16 @@ void SPIOutputTest::testDescription() { // change the start address & personality output1.SetStartAddress(10); - output1.SetPersonality(3); + output1.SetPersonality(SPIOutput::PERS_LDP8806_INDIVIDUAL); OLA_ASSERT_EQ( string("Output 0, LPD8806 Individual Control, 75 slots @ 10." " (707a:00000000)"), output1.Description()); OLA_ASSERT_EQ(static_cast(10), output1.GetStartAddress()); + OLA_ASSERT_EQ( + static_cast(SPIOutput::PERS_LDP8806_INDIVIDUAL), + output1.GetPersonality()); + // Test for backwards compatibility OLA_ASSERT_EQ(static_cast(3), output1.GetPersonality()); } @@ -118,6 +134,7 @@ void SPIOutputTest::testIndividualWS2801Control() { SPIOutput::Options options(0, "Test SPI Device"); options.pixel_count = 2; SPIOutput output(m_uid, &backend, options); + output.SetPersonality(SPIOutput::PERS_WS2801_INDIVIDUAL); DmxBuffer buffer; unsigned int length = 0; @@ -174,7 +191,7 @@ void SPIOutputTest::testCombinedWS2801Control() { SPIOutput::Options options(0, "Test SPI Device"); options.pixel_count = 2; SPIOutput output(m_uid, &backend, options); - output.SetPersonality(2); + output.SetPersonality(SPIOutput::PERS_WS2801_COMBINED); DmxBuffer buffer; buffer.SetFromString("255,128,0,10,20,30"); @@ -223,7 +240,7 @@ void SPIOutputTest::testIndividualLPD8806Control() { SPIOutput::Options options(0, "Test SPI Device"); options.pixel_count = 2; SPIOutput output(m_uid, &backend, options); - output.SetPersonality(3); + output.SetPersonality(SPIOutput::PERS_LDP8806_INDIVIDUAL); DmxBuffer buffer; unsigned int length = 0; @@ -278,7 +295,7 @@ void SPIOutputTest::testCombinedLPD8806Control() { SPIOutput::Options options(0, "Test SPI Device"); options.pixel_count = 2; SPIOutput output(m_uid, &backend, options); - output.SetPersonality(4); + output.SetPersonality(SPIOutput::PERS_LDP8806_COMBINED); DmxBuffer buffer; buffer.SetFromString("255,128,0,10,20,30"); @@ -325,7 +342,7 @@ void SPIOutputTest::testIndividualP9813Control() { SPIOutput::Options options(0, "Test SPI Device"); options.pixel_count = 2; SPIOutput output(m_uid, &backend, options); - output.SetPersonality(5); + output.SetPersonality(SPIOutput::PERS_P9813_INDIVIDUAL); DmxBuffer buffer; unsigned int length = 0; @@ -384,7 +401,7 @@ void SPIOutputTest::testCombinedP9813Control() { SPIOutput::Options options(0, "Test SPI Device"); options.pixel_count = 2; SPIOutput output(m_uid, &backend, options); - output.SetPersonality(6); + output.SetPersonality(SPIOutput::PERS_P9813_COMBINED); DmxBuffer buffer; buffer.SetFromString("255,128,0,10,20,30"); @@ -430,8 +447,8 @@ void SPIOutputTest::testCombinedP9813Control() { * Test DMX writes in the individual APA102 mode. */ void SPIOutputTest::testIndividualAPA102Control() { - // personality 7= Individual APA102 - const uint16_t this_test_personality = 7; + // personality Individual APA102 + const uint16_t this_test_personality = SPIOutput::PERS_APA102_INDIVIDUAL; // setup Backend FakeSPIBackend backend(2); SPIOutput::Options options(0, "Test SPI Device"); @@ -624,8 +641,7 @@ void SPIOutputTest::testIndividualAPA102Control() { * Test DMX writes in the combined APA102 mode. */ void SPIOutputTest::testCombinedAPA102Control() { - // personality 8= Combined APA102 - const uint16_t this_test_personality = 8; + const uint16_t this_test_personality = SPIOutput::PERS_APA102_COMBINED; // setup Backend FakeSPIBackend backend(2); SPIOutput::Options options(0, "Test SPI Device"); @@ -738,3 +754,388 @@ void SPIOutputTest::testCombinedAPA102Control() { // check if the output writes are 1 OLA_ASSERT_EQ(1u, backend.Writes(1)); } + +/** + * Test DMX writes in the individual APA102 Pixel Brightness mode. + */ +void SPIOutputTest::testIndividualAPA102ControlPixelBrightness() { + const uint16_t this_test_personality = SPIOutput::PERS_APA102_PB_INDIVIDUAL; + // setup Backend + FakeSPIBackend backend(2); + SPIOutput::Options options(0, "Test SPI Device"); + // setup pixel_count to 2 (enough to test all cases) + options.pixel_count = 2; + // setup SPIOutput + SPIOutput output(m_uid, &backend, options); + // set personality + output.SetPersonality(this_test_personality); + + // simulate incoming DMX data with this buffer + DmxBuffer buffer; + // setup a pointer to the returned data (the fake SPI data stream) + unsigned int length = 0; + const uint8_t *data = NULL; + + // test1 + // setup some 'DMX' data + buffer.SetFromString("255, 1, 10, 100"); + // simulate incoming data + output.WriteDMX(buffer); + // get fake SPI data stream + data = backend.GetData(0, &length); + // this is the expected spi data stream: + const uint8_t EXPECTED1[] = { 0, 0, 0, 0, // StartFrame + 0xFF, 0x64, 0x0A, 0x01, // first Pixel + 0x00, 0x00, 0x00, 0x00, // second Pixel + 0}; // EndFrame + // check for Equality + OLA_ASSERT_DATA_EQUALS(EXPECTED1, arraysize(EXPECTED1), data, length); + // check if the output writes are 1 + OLA_ASSERT_EQ(1u, backend.Writes(0)); + + // test2 + buffer.SetFromString("255, 255, 128, 0, 255, 10, 20, 30"); + output.WriteDMX(buffer); + data = backend.GetData(0, &length); + const uint8_t EXPECTED2[] = { 0, 0, 0, 0, + 0xFF, 0x00, 0x80, 0xFF, + 0xFF, 0x1E, 0x14, 0x0A, + 0}; + OLA_ASSERT_DATA_EQUALS(EXPECTED2, arraysize(EXPECTED2), data, length); + OLA_ASSERT_EQ(2u, backend.Writes(0)); + + // test3 + // test what happens when only new data for the first leds is available. + // later data should be not modified so for pixel2 data set in test2 is valid + buffer.SetFromString("255, 34, 56, 78"); + output.WriteDMX(buffer); + data = backend.GetData(0, &length); + const uint8_t EXPECTED3[] = { 0, 0, 0, 0, + 0xFF, 0x4E, 0x38, 0x22, + 0xFF, 0x1E, 0x14, 0x0A, + 0}; + OLA_ASSERT_DATA_EQUALS(EXPECTED3, arraysize(EXPECTED3), data, length); + OLA_ASSERT_EQ(3u, backend.Writes(0)); + + // test4 + // tests what happens if fewer then needed information are received + buffer.SetFromString("7, 9, 11"); + output.WriteDMX(buffer); + data = backend.GetData(0, &length); + // check that the returns are the same as test3 (nothing changed) + OLA_ASSERT_DATA_EQUALS(EXPECTED3, arraysize(EXPECTED3), data, length); + OLA_ASSERT_EQ(3u, backend.Writes(0)); + + // test5 + // test with changed StartAddress + // set StartAddress + output.SetStartAddress(3); + // values 1 & 2 should not be visible in SPI data stream + buffer.SetFromString("1,2, 255,3,4,5, 255,6,7,8"); + output.WriteDMX(buffer); + data = backend.GetData(0, &length); + const uint8_t EXPECTED5[] = { 0, 0, 0, 0, + 0xFF, 0x05, 0x04, 0x03, + 0xFF, 0x08, 0x07, 0x06, + 0}; + OLA_ASSERT_DATA_EQUALS(EXPECTED5, arraysize(EXPECTED5), data, length); + OLA_ASSERT_EQ(4u, backend.Writes(0)); + // change StartAddress back to default + output.SetStartAddress(1); + + // test6 + // Check nothing changed on the other output. + OLA_ASSERT_EQ(reinterpret_cast(NULL), + backend.GetData(1, &length)); + OLA_ASSERT_EQ(0u, backend.Writes(1)); + + // test7 + // test for multiple ports + // StartFrame is only allowed on first port. + SPIOutput::Options options1(1, "second SPI Device"); + // setup pixel_count to 2 (enough to test all cases) + options1.pixel_count = 2; + // setup SPIOutput + SPIOutput output1(m_uid, &backend, options1); + // set personality + output1.SetPersonality(this_test_personality); + // setup some 'DMX' data + buffer.SetFromString("255, 1, 10, 100, 255, 100, 10, 1"); + // simulate incoming data + output1.WriteDMX(buffer); + // get fake SPI data stream + data = backend.GetData(1, &length); + // this is the expected spi data stream: + // StartFrame is missing --> port is >0 ! + const uint8_t EXPECTED7[] = { // 0, 0, 0, 0, // StartFrame + 0xFF, 0x64, 0x0A, 0x01, // first Pixel + 0xFF, 0x01, 0x0A, 0x64, // second Pixel + 0}; // EndFrame + // check for Equality + OLA_ASSERT_DATA_EQUALS(EXPECTED7, arraysize(EXPECTED7), data, length); + // check if the output writes are 1 + OLA_ASSERT_EQ(1u, backend.Writes(1)); + + // test8 + // create new output with pixel_count=16 and check data length + // setup pixel_count to 16 + options.pixel_count = 16; + // setup SPIOutput + SPIOutput output2(m_uid, &backend, options); + // set personality + output2.SetPersonality(this_test_personality); + buffer.SetFromString( + "255,0,0,0, 255,0,0,0, 255,0,0,0, 255,0,0,0, 255,0,0,0," + "255,0,0,0, 255,0,0,0, 255,0,0,0, 255,0,0,0, 255,0,0,0," + "255,0,0,0, 255,0,0,0, 255,0,0,0, 255,0,0,0, 255,0,0,0," + "255,0,0,0"); + output2.WriteDMX(buffer); + data = backend.GetData(0, &length); + const uint8_t EXPECTED8[] = { 0, 0, 0, 0, + 0xFF, 0, 0, 0, // Pixel 1 + 0xFF, 0, 0, 0, // Pixel 2 + 0xFF, 0, 0, 0, // Pixel 3 + 0xFF, 0, 0, 0, // Pixel 4 + 0xFF, 0, 0, 0, // Pixel 5 + 0xFF, 0, 0, 0, // Pixel 6 + 0xFF, 0, 0, 0, // Pixel 7 + 0xFF, 0, 0, 0, // Pixel 8 + 0xFF, 0, 0, 0, // Pixel 9 + 0xFF, 0, 0, 0, // Pixel 10 + 0xFF, 0, 0, 0, // Pixel 11 + 0xFF, 0, 0, 0, // Pixel 12 + 0xFF, 0, 0, 0, // Pixel 13 + 0xFF, 0, 0, 0, // Pixel 14 + 0xFF, 0, 0, 0, // Pixel 15 + 0xFF, 0, 0, 0, // Pixel 16 + 0}; + OLA_ASSERT_DATA_EQUALS(EXPECTED8, arraysize(EXPECTED8), data, length); + OLA_ASSERT_EQ(5u, backend.Writes(0)); + + // test9 + // create new output with pixel_count=17 and check data length + // setup pixel_count to 17 + options.pixel_count = 17; + // setup SPIOutput + SPIOutput output3(m_uid, &backend, options); + // set personality + output3.SetPersonality(this_test_personality); + // generate dmx data + buffer.SetFromString( + "255,0,0,0, 255,0,0,0, 255,0,0,0, 255,0,0,0, 255,0,0,0," + "255,0,0,0, 255,0,0,0, 255,0,0,0, 255,0,0,0, 255,0,0,0," + "255,0,0,0, 255,0,0,0, 255,0,0,0, 255,0,0,0, 255,0,0,0," + "255,0,0,0, 255,0,0,0"); + output3.WriteDMX(buffer); + data = backend.GetData(0, &length); + const uint8_t EXPECTED9[] = { 0, 0, 0, 0, + 0xFF, 0, 0, 0, // Pixel 1 + 0xFF, 0, 0, 0, // Pixel 2 + 0xFF, 0, 0, 0, // Pixel 3 + 0xFF, 0, 0, 0, // Pixel 4 + 0xFF, 0, 0, 0, // Pixel 5 + 0xFF, 0, 0, 0, // Pixel 6 + 0xFF, 0, 0, 0, // Pixel 7 + 0xFF, 0, 0, 0, // Pixel 8 + 0xFF, 0, 0, 0, // Pixel 9 + 0xFF, 0, 0, 0, // Pixel 10 + 0xFF, 0, 0, 0, // Pixel 11 + 0xFF, 0, 0, 0, // Pixel 12 + 0xFF, 0, 0, 0, // Pixel 13 + 0xFF, 0, 0, 0, // Pixel 14 + 0xFF, 0, 0, 0, // Pixel 15 + 0xFF, 0, 0, 0, // Pixel 16 + 0xFF, 0, 0, 0, // Pixel 17 + 0, 0}; // now we have two latch bytes... + OLA_ASSERT_DATA_EQUALS(EXPECTED9, arraysize(EXPECTED9), data, length); + OLA_ASSERT_EQ(6u, backend.Writes(0)); + + // test 10 + // test different pixel brightness levels + // generate dmx data + buffer.SetFromString( + " 0,0,0,0, 8,0,0,0, 16,0,0,0, 42,0,0,0, 84,0,0,0," + "127,0,0,0, 167,0,0,0, 206,0,0,0, 240,0,0,0, 247,0,0,0," + "248,0,0,0, 255,0,0,0, 255,0,0,0, 255,0,0,0, 255,0,0,0," + "255,0,0,0, 255,0,0,0"); + output3.WriteDMX(buffer); + data = backend.GetData(0, &length); + const uint8_t EXPECTED10[] = { 0, 0, 0, 0, + 0xE0 | 0, 0, 0, 0, // Pixel 1 + 0xE0 | 1, 0, 0, 0, // Pixel 2 + 0xE0 | 2, 0, 0, 0, // Pixel 3 + 0xE0 | 5, 0, 0, 0, // Pixel 4 + 0xE0 | 10, 0, 0, 0, // Pixel 5 + 0xE0 | 15, 0, 0, 0, // Pixel 6 + 0xE0 | 20, 0, 0, 0, // Pixel 7 + 0xE0 | 25, 0, 0, 0, // Pixel 8 + 0xE0 | 30, 0, 0, 0, // Pixel 9 + 0xE0 | 30, 0, 0, 0, // Pixel 10 + 0xE0 | 31, 0, 0, 0, // Pixel 11 + 0xE0 | 31, 0, 0, 0, // Pixel 12 + 0xE0 | 31, 0, 0, 0, // Pixel 13 + 0xE0 | 31, 0, 0, 0, // Pixel 14 + 0xE0 | 31, 0, 0, 0, // Pixel 15 + 0xE0 | 31, 0, 0, 0, // Pixel 16 + 0xE0 | 31, 0, 0, 0, // Pixel 17 + 0, 0}; // now we have two latch bytes... + OLA_ASSERT_DATA_EQUALS(EXPECTED10, arraysize(EXPECTED10), data, length); + OLA_ASSERT_EQ(7u, backend.Writes(0)); +} + +/** + * Test DMX writes in the combined APA102 mode. + */ +void SPIOutputTest::testCombinedAPA102ControlPixelBrightness() { + const uint16_t this_test_personality = SPIOutput::PERS_APA102_PB_COMBINED; + // setup Backend + FakeSPIBackend backend(2); + SPIOutput::Options options(0, "Test SPI Device"); + // setup pixel_count to 2 (enough to test all cases) + options.pixel_count = 2; + // setup SPIOutput + SPIOutput output(m_uid, &backend, options); + // set personality to 8= Combined APA102 + output.SetPersonality(this_test_personality); + + // simulate incoming dmx data with this buffer + DmxBuffer buffer; + // setup an pointer to the returned data (the fake SPI data stream) + unsigned int length = 0; + const uint8_t *data = NULL; + + // test1 + // setup some 'DMX' data + buffer.SetFromString("255, 1, 10, 100"); + // simulate incoming data + output.WriteDMX(buffer); + // get fake SPI data stream + data = backend.GetData(0, &length); + // this is the expected spi data stream: + const uint8_t EXPECTED1[] = { 0, 0, 0, 0, // StartFrame + 0xFF, 0x64, 0x0A, 0x01, // first Pixel + 0xFF, 0x64, 0x0A, 0x01, // second Pixel + 0}; // EndFrame + // check for Equality + OLA_ASSERT_DATA_EQUALS(EXPECTED1, arraysize(EXPECTED1), data, length); + // check if the output writes are 1 + OLA_ASSERT_EQ(1u, backend.Writes(0)); + + // test2 + buffer.SetFromString("255, 255, 128, 0, 255, 10, 20, 30"); + output.WriteDMX(buffer); + data = backend.GetData(0, &length); + const uint8_t EXPECTED2[] = { 0, 0, 0, 0, + 0xFF, 0x00, 0x80, 0xFF, + 0xFF, 0x00, 0x80, 0xFF, + 0}; + OLA_ASSERT_DATA_EQUALS(EXPECTED2, arraysize(EXPECTED2), data, length); + OLA_ASSERT_EQ(2u, backend.Writes(0)); + + // test3 + buffer.SetFromString("255, 34, 56, 78"); + output.WriteDMX(buffer); + data = backend.GetData(0, &length); + const uint8_t EXPECTED3[] = { 0, 0, 0, 0, + 0xFF, 0x4E, 0x38, 0x22, + 0xFF, 0x4E, 0x38, 0x22, + 0}; + OLA_ASSERT_DATA_EQUALS(EXPECTED3, arraysize(EXPECTED3), data, length); + OLA_ASSERT_EQ(3u, backend.Writes(0)); + + // test4 + // tests what happens if fewer then needed information are received + buffer.SetFromString("7, 9, 11"); + output.WriteDMX(buffer); + data = backend.GetData(0, &length); + // check that the returns are the same as test2 (nothing changed) + OLA_ASSERT_DATA_EQUALS(EXPECTED3, arraysize(EXPECTED3), data, length); + OLA_ASSERT_EQ(3u, backend.Writes(0)); + + // test5 + // test with other StartAddress + // set StartAddress + output.SetStartAddress(3); + // values 1 & 2 should not be visible in SPI data stream + buffer.SetFromString("1,2, 255,3,4,5, 255,6,7,8"); + output.WriteDMX(buffer); + data = backend.GetData(0, &length); + const uint8_t EXPECTED5[] = { 0, 0, 0, 0, + 0xFF, 0x05, 0x04, 0x03, + 0xFF, 0x05, 0x04, 0x03, + 0}; + OLA_ASSERT_DATA_EQUALS(EXPECTED5, arraysize(EXPECTED5), data, length); + OLA_ASSERT_EQ(4u, backend.Writes(0)); + + // test6 + // Check nothing changed on the other output. + OLA_ASSERT_EQ(reinterpret_cast(NULL), + backend.GetData(1, &length)); + OLA_ASSERT_EQ(0u, backend.Writes(1)); + + // test7 + // test for multiple ports + // StartFrame is only allowed on first port. + SPIOutput::Options option1(1, "second SPI Device"); + // setup pixel_count to 2 (enough to test all cases) + option1.pixel_count = 2; + // setup SPIOutput + SPIOutput output1(m_uid, &backend, option1); + // set personality + output1.SetPersonality(this_test_personality); + // setup some 'DMX' data + buffer.SetFromString("255, 1, 10, 100"); + // simulate incoming data + output1.WriteDMX(buffer); + // get fake SPI data stream + data = backend.GetData(1, &length); + // this is the expected spi data stream: + // StartFrame is missing --> port is >0 ! + const uint8_t EXPECTED7[] = { // 0, 0, 0, 0, // StartFrame + 0xFF, 0x64, 0x0A, 0x01, // first Pixel + 0xFF, 0x64, 0x0A, 0x01, // second Pixel + 0}; // EndFrame + // check for Equality + OLA_ASSERT_DATA_EQUALS(EXPECTED7, arraysize(EXPECTED7), data, length); + // check if the output writes are 1 + OLA_ASSERT_EQ(1u, backend.Writes(1)); + + + // test8 + // create new output with pixel_count=20 and check data length + // and modified Pixel Brightness value + // setup pixel_count to 20 + options.pixel_count = 20; + // setup SPIOutput + SPIOutput output2(m_uid, &backend, options); + // set personality + output2.SetPersonality(this_test_personality); + buffer.SetFromString("127,0,0,0"); + output2.WriteDMX(buffer); + data = backend.GetData(0, &length); + const uint8_t EXPECTED8[] = { 0, 0, 0, 0, + 0xE0 | 15, 0, 0, 0, // Pixel 1 + 0xE0 | 15, 0, 0, 0, // Pixel 2 + 0xE0 | 15, 0, 0, 0, // Pixel 3 + 0xE0 | 15, 0, 0, 0, // Pixel 4 + 0xE0 | 15, 0, 0, 0, // Pixel 5 + 0xE0 | 15, 0, 0, 0, // Pixel 6 + 0xE0 | 15, 0, 0, 0, // Pixel 7 + 0xE0 | 15, 0, 0, 0, // Pixel 8 + 0xE0 | 15, 0, 0, 0, // Pixel 9 + 0xE0 | 15, 0, 0, 0, // Pixel 10 + 0xE0 | 15, 0, 0, 0, // Pixel 11 + 0xE0 | 15, 0, 0, 0, // Pixel 12 + 0xE0 | 15, 0, 0, 0, // Pixel 13 + 0xE0 | 15, 0, 0, 0, // Pixel 14 + 0xE0 | 15, 0, 0, 0, // Pixel 15 + 0xE0 | 15, 0, 0, 0, // Pixel 16 + 0xE0 | 15, 0, 0, 0, // Pixel 17 + 0xE0 | 15, 0, 0, 0, // Pixel 18 + 0xE0 | 15, 0, 0, 0, // Pixel 19 + 0xE0 | 15, 0, 0, 0, // Pixel 20 + 0, 0}; + // with > 16 Pixel we have two latch bytes... + OLA_ASSERT_DATA_EQUALS(EXPECTED8, arraysize(EXPECTED8), data, length); + OLA_ASSERT_EQ(5u, backend.Writes(0)); +} From 8e3ccf9013d437f2921dff8afcb33f68c34b9da5 Mon Sep 17 00:00:00 2001 From: Ph0N37Ic5 Date: Sat, 24 Feb 2018 18:43:54 +0100 Subject: [PATCH 04/17] Sync to upstream master. --- .gitignore | 1 + .travis.yml | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c154b0a5fc..5d4f3e5173 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.gcda *.gcno *.gcov +*.gch *.la *.lo *.log diff --git a/.travis.yml b/.travis.yml index a1e9616146..b447b14250 100644 --- a/.travis.yml +++ b/.travis.yml @@ -305,7 +305,8 @@ before_install: - if [ "$TASK" == "spellintian" -o "$TASK" == "spellintian-duplicates" ]; then sudo add-apt-repository ppa:waja/trusty-backports -y; sudo apt-get update -qq; sudo apt-get install lintian -y; fi # Install a late enough lintian after_failure: - - cat ${TRAVIS_BUILD_DIR}/ola-*/_build/sub/test-suite.log + - if [ -f ${TRAVIS_BUILD_DIR}/ola-*/_build/test-suite.log ]; then cat ${TRAVIS_BUILD_DIR}/ola-*/_build/test-suite.log; fi + - if [ -f ${TRAVIS_BUILD_DIR}/ola-*/_build/sub/test-suite.log ]; then cat ${TRAVIS_BUILD_DIR}/ola-*/_build/sub/test-suite.log; fi after_success: - if [ "$TASK" = "coverage" ]; then coveralls --gcov /usr/bin/gcov-6 -b . -E '.*Test\.cpp$' -E '.*\.pb\.cc$' -E '.*\.pb\.cpp$' -E '.*\.pb\.h$' -E '.*\.yy\.cpp$' -E '.*\.tab\.cpp$' -E '.*\.tab\.h$' -E '.*/doxygen/examples.*$' --gcov-options '\-lp' > /dev/null; fi From c94f8f154642552a14b5c44031805afbfbc53bcb Mon Sep 17 00:00:00 2001 From: Ph0N37Ic5 Date: Sat, 24 Feb 2018 18:45:10 +0100 Subject: [PATCH 05/17] Sync to upstream master. --- plugins/spi/SPIOutputTest.cpp | 421 +++++++++++++++++++++++++++++++++- 1 file changed, 411 insertions(+), 10 deletions(-) diff --git a/plugins/spi/SPIOutputTest.cpp b/plugins/spi/SPIOutputTest.cpp index ea93d8bf23..c39038864c 100644 --- a/plugins/spi/SPIOutputTest.cpp +++ b/plugins/spi/SPIOutputTest.cpp @@ -47,6 +47,8 @@ class SPIOutputTest: public CppUnit::TestFixture { CPPUNIT_TEST(testCombinedP9813Control); CPPUNIT_TEST(testIndividualAPA102Control); CPPUNIT_TEST(testCombinedAPA102Control); + CPPUNIT_TEST(testIndividualAPA102ControlPixelBrightness); + CPPUNIT_TEST(testCombinedAPA102ControlPixelBrightness); CPPUNIT_TEST_SUITE_END(); public: @@ -65,6 +67,8 @@ class SPIOutputTest: public CppUnit::TestFixture { void testCombinedP9813Control(); void testIndividualAPA102Control(); void testCombinedAPA102Control(); + void testIndividualAPA102ControlPixelBrightness(); + void testCombinedAPA102ControlPixelBrightness(); private: UID m_uid; @@ -83,16 +87,24 @@ void SPIOutputTest::setUp() { void SPIOutputTest::testDescription() { FakeSPIBackend backend(2); SPIOutput output1(m_uid, &backend, SPIOutput::Options(0, "Test SPI Device")); + SPIOutput::Options options(1, "Test SPI Device"); options.pixel_count = 32; SPIOutput output2(m_uid, &backend, options); + // check default constructor values OLA_ASSERT_EQ( string("Output 0, WS2801 Individual Control, 75 slots @ 1." " (707a:00000000)"), output1.Description()); OLA_ASSERT_EQ(static_cast(1), output1.GetStartAddress()); + OLA_ASSERT_EQ( + static_cast(SPIOutput::PERS_WS2801_INDIVIDUAL), + output1.GetPersonality()); + // Test for backwards compatibility OLA_ASSERT_EQ(static_cast(1), output1.GetPersonality()); + + // check default constructor values for output2 OLA_ASSERT_EQ( string("Output 1, WS2801 Individual Control, 96 slots @ 1." " (707a:00000000)"), @@ -100,12 +112,16 @@ void SPIOutputTest::testDescription() { // change the start address & personality output1.SetStartAddress(10); - output1.SetPersonality(3); + output1.SetPersonality(SPIOutput::PERS_LDP8806_INDIVIDUAL); OLA_ASSERT_EQ( string("Output 0, LPD8806 Individual Control, 75 slots @ 10." " (707a:00000000)"), output1.Description()); OLA_ASSERT_EQ(static_cast(10), output1.GetStartAddress()); + OLA_ASSERT_EQ( + static_cast(SPIOutput::PERS_LDP8806_INDIVIDUAL), + output1.GetPersonality()); + // Test for backwards compatibility OLA_ASSERT_EQ(static_cast(3), output1.GetPersonality()); } @@ -118,6 +134,7 @@ void SPIOutputTest::testIndividualWS2801Control() { SPIOutput::Options options(0, "Test SPI Device"); options.pixel_count = 2; SPIOutput output(m_uid, &backend, options); + output.SetPersonality(SPIOutput::PERS_WS2801_INDIVIDUAL); DmxBuffer buffer; unsigned int length = 0; @@ -174,7 +191,7 @@ void SPIOutputTest::testCombinedWS2801Control() { SPIOutput::Options options(0, "Test SPI Device"); options.pixel_count = 2; SPIOutput output(m_uid, &backend, options); - output.SetPersonality(2); + output.SetPersonality(SPIOutput::PERS_WS2801_COMBINED); DmxBuffer buffer; buffer.SetFromString("255,128,0,10,20,30"); @@ -223,7 +240,7 @@ void SPIOutputTest::testIndividualLPD8806Control() { SPIOutput::Options options(0, "Test SPI Device"); options.pixel_count = 2; SPIOutput output(m_uid, &backend, options); - output.SetPersonality(3); + output.SetPersonality(SPIOutput::PERS_LDP8806_INDIVIDUAL); DmxBuffer buffer; unsigned int length = 0; @@ -278,7 +295,7 @@ void SPIOutputTest::testCombinedLPD8806Control() { SPIOutput::Options options(0, "Test SPI Device"); options.pixel_count = 2; SPIOutput output(m_uid, &backend, options); - output.SetPersonality(4); + output.SetPersonality(SPIOutput::PERS_LDP8806_COMBINED); DmxBuffer buffer; buffer.SetFromString("255,128,0,10,20,30"); @@ -325,7 +342,7 @@ void SPIOutputTest::testIndividualP9813Control() { SPIOutput::Options options(0, "Test SPI Device"); options.pixel_count = 2; SPIOutput output(m_uid, &backend, options); - output.SetPersonality(5); + output.SetPersonality(SPIOutput::PERS_P9813_INDIVIDUAL); DmxBuffer buffer; unsigned int length = 0; @@ -384,7 +401,7 @@ void SPIOutputTest::testCombinedP9813Control() { SPIOutput::Options options(0, "Test SPI Device"); options.pixel_count = 2; SPIOutput output(m_uid, &backend, options); - output.SetPersonality(6); + output.SetPersonality(SPIOutput::PERS_P9813_COMBINED); DmxBuffer buffer; buffer.SetFromString("255,128,0,10,20,30"); @@ -430,8 +447,8 @@ void SPIOutputTest::testCombinedP9813Control() { * Test DMX writes in the individual APA102 mode. */ void SPIOutputTest::testIndividualAPA102Control() { - // personality 7= Individual APA102 - const uint16_t this_test_personality = 7; + // personality Individual APA102 + const uint16_t this_test_personality = SPIOutput::PERS_APA102_INDIVIDUAL; // setup Backend FakeSPIBackend backend(2); SPIOutput::Options options(0, "Test SPI Device"); @@ -624,8 +641,7 @@ void SPIOutputTest::testIndividualAPA102Control() { * Test DMX writes in the combined APA102 mode. */ void SPIOutputTest::testCombinedAPA102Control() { - // personality 8= Combined APA102 - const uint16_t this_test_personality = 8; + const uint16_t this_test_personality = SPIOutput::PERS_APA102_COMBINED; // setup Backend FakeSPIBackend backend(2); SPIOutput::Options options(0, "Test SPI Device"); @@ -738,3 +754,388 @@ void SPIOutputTest::testCombinedAPA102Control() { // check if the output writes are 1 OLA_ASSERT_EQ(1u, backend.Writes(1)); } + +/** + * Test DMX writes in the individual APA102 Pixel Brightness mode. + */ +void SPIOutputTest::testIndividualAPA102ControlPixelBrightness() { + const uint16_t this_test_personality = SPIOutput::PERS_APA102_PB_INDIVIDUAL; + // setup Backend + FakeSPIBackend backend(2); + SPIOutput::Options options(0, "Test SPI Device"); + // setup pixel_count to 2 (enough to test all cases) + options.pixel_count = 2; + // setup SPIOutput + SPIOutput output(m_uid, &backend, options); + // set personality + output.SetPersonality(this_test_personality); + + // simulate incoming DMX data with this buffer + DmxBuffer buffer; + // setup a pointer to the returned data (the fake SPI data stream) + unsigned int length = 0; + const uint8_t *data = NULL; + + // test1 + // setup some 'DMX' data + buffer.SetFromString("255, 1, 10, 100"); + // simulate incoming data + output.WriteDMX(buffer); + // get fake SPI data stream + data = backend.GetData(0, &length); + // this is the expected spi data stream: + const uint8_t EXPECTED1[] = { 0, 0, 0, 0, // StartFrame + 0xFF, 0x64, 0x0A, 0x01, // first Pixel + 0x00, 0x00, 0x00, 0x00, // second Pixel + 0}; // EndFrame + // check for Equality + OLA_ASSERT_DATA_EQUALS(EXPECTED1, arraysize(EXPECTED1), data, length); + // check if the output writes are 1 + OLA_ASSERT_EQ(1u, backend.Writes(0)); + + // test2 + buffer.SetFromString("255, 255, 128, 0, 255, 10, 20, 30"); + output.WriteDMX(buffer); + data = backend.GetData(0, &length); + const uint8_t EXPECTED2[] = { 0, 0, 0, 0, + 0xFF, 0x00, 0x80, 0xFF, + 0xFF, 0x1E, 0x14, 0x0A, + 0}; + OLA_ASSERT_DATA_EQUALS(EXPECTED2, arraysize(EXPECTED2), data, length); + OLA_ASSERT_EQ(2u, backend.Writes(0)); + + // test3 + // test what happens when only new data for the first leds is available. + // later data should be not modified so for pixel2 data set in test2 is valid + buffer.SetFromString("255, 34, 56, 78"); + output.WriteDMX(buffer); + data = backend.GetData(0, &length); + const uint8_t EXPECTED3[] = { 0, 0, 0, 0, + 0xFF, 0x4E, 0x38, 0x22, + 0xFF, 0x1E, 0x14, 0x0A, + 0}; + OLA_ASSERT_DATA_EQUALS(EXPECTED3, arraysize(EXPECTED3), data, length); + OLA_ASSERT_EQ(3u, backend.Writes(0)); + + // test4 + // tests what happens if fewer then needed information are received + buffer.SetFromString("7, 9, 11"); + output.WriteDMX(buffer); + data = backend.GetData(0, &length); + // check that the returns are the same as test3 (nothing changed) + OLA_ASSERT_DATA_EQUALS(EXPECTED3, arraysize(EXPECTED3), data, length); + OLA_ASSERT_EQ(3u, backend.Writes(0)); + + // test5 + // test with changed StartAddress + // set StartAddress + output.SetStartAddress(3); + // values 1 & 2 should not be visible in SPI data stream + buffer.SetFromString("1,2, 255,3,4,5, 255,6,7,8"); + output.WriteDMX(buffer); + data = backend.GetData(0, &length); + const uint8_t EXPECTED5[] = { 0, 0, 0, 0, + 0xFF, 0x05, 0x04, 0x03, + 0xFF, 0x08, 0x07, 0x06, + 0}; + OLA_ASSERT_DATA_EQUALS(EXPECTED5, arraysize(EXPECTED5), data, length); + OLA_ASSERT_EQ(4u, backend.Writes(0)); + // change StartAddress back to default + output.SetStartAddress(1); + + // test6 + // Check nothing changed on the other output. + OLA_ASSERT_EQ(reinterpret_cast(NULL), + backend.GetData(1, &length)); + OLA_ASSERT_EQ(0u, backend.Writes(1)); + + // test7 + // test for multiple ports + // StartFrame is only allowed on first port. + SPIOutput::Options options1(1, "second SPI Device"); + // setup pixel_count to 2 (enough to test all cases) + options1.pixel_count = 2; + // setup SPIOutput + SPIOutput output1(m_uid, &backend, options1); + // set personality + output1.SetPersonality(this_test_personality); + // setup some 'DMX' data + buffer.SetFromString("255, 1, 10, 100, 255, 100, 10, 1"); + // simulate incoming data + output1.WriteDMX(buffer); + // get fake SPI data stream + data = backend.GetData(1, &length); + // this is the expected spi data stream: + // StartFrame is missing --> port is >0 ! + const uint8_t EXPECTED7[] = { // 0, 0, 0, 0, // StartFrame + 0xFF, 0x64, 0x0A, 0x01, // first Pixel + 0xFF, 0x01, 0x0A, 0x64, // second Pixel + 0}; // EndFrame + // check for Equality + OLA_ASSERT_DATA_EQUALS(EXPECTED7, arraysize(EXPECTED7), data, length); + // check if the output writes are 1 + OLA_ASSERT_EQ(1u, backend.Writes(1)); + + // test8 + // create new output with pixel_count=16 and check data length + // setup pixel_count to 16 + options.pixel_count = 16; + // setup SPIOutput + SPIOutput output2(m_uid, &backend, options); + // set personality + output2.SetPersonality(this_test_personality); + buffer.SetFromString( + "255,0,0,0, 255,0,0,0, 255,0,0,0, 255,0,0,0, 255,0,0,0," + "255,0,0,0, 255,0,0,0, 255,0,0,0, 255,0,0,0, 255,0,0,0," + "255,0,0,0, 255,0,0,0, 255,0,0,0, 255,0,0,0, 255,0,0,0," + "255,0,0,0"); + output2.WriteDMX(buffer); + data = backend.GetData(0, &length); + const uint8_t EXPECTED8[] = { 0, 0, 0, 0, + 0xFF, 0, 0, 0, // Pixel 1 + 0xFF, 0, 0, 0, // Pixel 2 + 0xFF, 0, 0, 0, // Pixel 3 + 0xFF, 0, 0, 0, // Pixel 4 + 0xFF, 0, 0, 0, // Pixel 5 + 0xFF, 0, 0, 0, // Pixel 6 + 0xFF, 0, 0, 0, // Pixel 7 + 0xFF, 0, 0, 0, // Pixel 8 + 0xFF, 0, 0, 0, // Pixel 9 + 0xFF, 0, 0, 0, // Pixel 10 + 0xFF, 0, 0, 0, // Pixel 11 + 0xFF, 0, 0, 0, // Pixel 12 + 0xFF, 0, 0, 0, // Pixel 13 + 0xFF, 0, 0, 0, // Pixel 14 + 0xFF, 0, 0, 0, // Pixel 15 + 0xFF, 0, 0, 0, // Pixel 16 + 0}; + OLA_ASSERT_DATA_EQUALS(EXPECTED8, arraysize(EXPECTED8), data, length); + OLA_ASSERT_EQ(5u, backend.Writes(0)); + + // test9 + // create new output with pixel_count=17 and check data length + // setup pixel_count to 17 + options.pixel_count = 17; + // setup SPIOutput + SPIOutput output3(m_uid, &backend, options); + // set personality + output3.SetPersonality(this_test_personality); + // generate dmx data + buffer.SetFromString( + "255,0,0,0, 255,0,0,0, 255,0,0,0, 255,0,0,0, 255,0,0,0," + "255,0,0,0, 255,0,0,0, 255,0,0,0, 255,0,0,0, 255,0,0,0," + "255,0,0,0, 255,0,0,0, 255,0,0,0, 255,0,0,0, 255,0,0,0," + "255,0,0,0, 255,0,0,0"); + output3.WriteDMX(buffer); + data = backend.GetData(0, &length); + const uint8_t EXPECTED9[] = { 0, 0, 0, 0, + 0xFF, 0, 0, 0, // Pixel 1 + 0xFF, 0, 0, 0, // Pixel 2 + 0xFF, 0, 0, 0, // Pixel 3 + 0xFF, 0, 0, 0, // Pixel 4 + 0xFF, 0, 0, 0, // Pixel 5 + 0xFF, 0, 0, 0, // Pixel 6 + 0xFF, 0, 0, 0, // Pixel 7 + 0xFF, 0, 0, 0, // Pixel 8 + 0xFF, 0, 0, 0, // Pixel 9 + 0xFF, 0, 0, 0, // Pixel 10 + 0xFF, 0, 0, 0, // Pixel 11 + 0xFF, 0, 0, 0, // Pixel 12 + 0xFF, 0, 0, 0, // Pixel 13 + 0xFF, 0, 0, 0, // Pixel 14 + 0xFF, 0, 0, 0, // Pixel 15 + 0xFF, 0, 0, 0, // Pixel 16 + 0xFF, 0, 0, 0, // Pixel 17 + 0, 0}; // now we have two latch bytes... + OLA_ASSERT_DATA_EQUALS(EXPECTED9, arraysize(EXPECTED9), data, length); + OLA_ASSERT_EQ(6u, backend.Writes(0)); + + // test 10 + // test different pixel brightness levels + // generate dmx data + buffer.SetFromString( + " 0,0,0,0, 8,0,0,0, 16,0,0,0, 42,0,0,0, 84,0,0,0," + "127,0,0,0, 167,0,0,0, 206,0,0,0, 240,0,0,0, 247,0,0,0," + "248,0,0,0, 255,0,0,0, 255,0,0,0, 255,0,0,0, 255,0,0,0," + "255,0,0,0, 255,0,0,0"); + output3.WriteDMX(buffer); + data = backend.GetData(0, &length); + const uint8_t EXPECTED10[] = { 0, 0, 0, 0, + 0xE0 | 0, 0, 0, 0, // Pixel 1 + 0xE0 | 1, 0, 0, 0, // Pixel 2 + 0xE0 | 2, 0, 0, 0, // Pixel 3 + 0xE0 | 5, 0, 0, 0, // Pixel 4 + 0xE0 | 10, 0, 0, 0, // Pixel 5 + 0xE0 | 15, 0, 0, 0, // Pixel 6 + 0xE0 | 20, 0, 0, 0, // Pixel 7 + 0xE0 | 25, 0, 0, 0, // Pixel 8 + 0xE0 | 30, 0, 0, 0, // Pixel 9 + 0xE0 | 30, 0, 0, 0, // Pixel 10 + 0xE0 | 31, 0, 0, 0, // Pixel 11 + 0xE0 | 31, 0, 0, 0, // Pixel 12 + 0xE0 | 31, 0, 0, 0, // Pixel 13 + 0xE0 | 31, 0, 0, 0, // Pixel 14 + 0xE0 | 31, 0, 0, 0, // Pixel 15 + 0xE0 | 31, 0, 0, 0, // Pixel 16 + 0xE0 | 31, 0, 0, 0, // Pixel 17 + 0, 0}; // now we have two latch bytes... + OLA_ASSERT_DATA_EQUALS(EXPECTED10, arraysize(EXPECTED10), data, length); + OLA_ASSERT_EQ(7u, backend.Writes(0)); +} + +/** + * Test DMX writes in the combined APA102 mode. + */ +void SPIOutputTest::testCombinedAPA102ControlPixelBrightness() { + const uint16_t this_test_personality = SPIOutput::PERS_APA102_PB_COMBINED; + // setup Backend + FakeSPIBackend backend(2); + SPIOutput::Options options(0, "Test SPI Device"); + // setup pixel_count to 2 (enough to test all cases) + options.pixel_count = 2; + // setup SPIOutput + SPIOutput output(m_uid, &backend, options); + // set personality to 8= Combined APA102 + output.SetPersonality(this_test_personality); + + // simulate incoming dmx data with this buffer + DmxBuffer buffer; + // setup an pointer to the returned data (the fake SPI data stream) + unsigned int length = 0; + const uint8_t *data = NULL; + + // test1 + // setup some 'DMX' data + buffer.SetFromString("255, 1, 10, 100"); + // simulate incoming data + output.WriteDMX(buffer); + // get fake SPI data stream + data = backend.GetData(0, &length); + // this is the expected spi data stream: + const uint8_t EXPECTED1[] = { 0, 0, 0, 0, // StartFrame + 0xFF, 0x64, 0x0A, 0x01, // first Pixel + 0xFF, 0x64, 0x0A, 0x01, // second Pixel + 0}; // EndFrame + // check for Equality + OLA_ASSERT_DATA_EQUALS(EXPECTED1, arraysize(EXPECTED1), data, length); + // check if the output writes are 1 + OLA_ASSERT_EQ(1u, backend.Writes(0)); + + // test2 + buffer.SetFromString("255, 255, 128, 0, 255, 10, 20, 30"); + output.WriteDMX(buffer); + data = backend.GetData(0, &length); + const uint8_t EXPECTED2[] = { 0, 0, 0, 0, + 0xFF, 0x00, 0x80, 0xFF, + 0xFF, 0x00, 0x80, 0xFF, + 0}; + OLA_ASSERT_DATA_EQUALS(EXPECTED2, arraysize(EXPECTED2), data, length); + OLA_ASSERT_EQ(2u, backend.Writes(0)); + + // test3 + buffer.SetFromString("255, 34, 56, 78"); + output.WriteDMX(buffer); + data = backend.GetData(0, &length); + const uint8_t EXPECTED3[] = { 0, 0, 0, 0, + 0xFF, 0x4E, 0x38, 0x22, + 0xFF, 0x4E, 0x38, 0x22, + 0}; + OLA_ASSERT_DATA_EQUALS(EXPECTED3, arraysize(EXPECTED3), data, length); + OLA_ASSERT_EQ(3u, backend.Writes(0)); + + // test4 + // tests what happens if fewer then needed information are received + buffer.SetFromString("7, 9, 11"); + output.WriteDMX(buffer); + data = backend.GetData(0, &length); + // check that the returns are the same as test2 (nothing changed) + OLA_ASSERT_DATA_EQUALS(EXPECTED3, arraysize(EXPECTED3), data, length); + OLA_ASSERT_EQ(3u, backend.Writes(0)); + + // test5 + // test with other StartAddress + // set StartAddress + output.SetStartAddress(3); + // values 1 & 2 should not be visible in SPI data stream + buffer.SetFromString("1,2, 255,3,4,5, 255,6,7,8"); + output.WriteDMX(buffer); + data = backend.GetData(0, &length); + const uint8_t EXPECTED5[] = { 0, 0, 0, 0, + 0xFF, 0x05, 0x04, 0x03, + 0xFF, 0x05, 0x04, 0x03, + 0}; + OLA_ASSERT_DATA_EQUALS(EXPECTED5, arraysize(EXPECTED5), data, length); + OLA_ASSERT_EQ(4u, backend.Writes(0)); + + // test6 + // Check nothing changed on the other output. + OLA_ASSERT_EQ(reinterpret_cast(NULL), + backend.GetData(1, &length)); + OLA_ASSERT_EQ(0u, backend.Writes(1)); + + // test7 + // test for multiple ports + // StartFrame is only allowed on first port. + SPIOutput::Options option1(1, "second SPI Device"); + // setup pixel_count to 2 (enough to test all cases) + option1.pixel_count = 2; + // setup SPIOutput + SPIOutput output1(m_uid, &backend, option1); + // set personality + output1.SetPersonality(this_test_personality); + // setup some 'DMX' data + buffer.SetFromString("255, 1, 10, 100"); + // simulate incoming data + output1.WriteDMX(buffer); + // get fake SPI data stream + data = backend.GetData(1, &length); + // this is the expected spi data stream: + // StartFrame is missing --> port is >0 ! + const uint8_t EXPECTED7[] = { // 0, 0, 0, 0, // StartFrame + 0xFF, 0x64, 0x0A, 0x01, // first Pixel + 0xFF, 0x64, 0x0A, 0x01, // second Pixel + 0}; // EndFrame + // check for Equality + OLA_ASSERT_DATA_EQUALS(EXPECTED7, arraysize(EXPECTED7), data, length); + // check if the output writes are 1 + OLA_ASSERT_EQ(1u, backend.Writes(1)); + + + // test8 + // create new output with pixel_count=20 and check data length + // and modified Pixel Brightness value + // setup pixel_count to 20 + options.pixel_count = 20; + // setup SPIOutput + SPIOutput output2(m_uid, &backend, options); + // set personality + output2.SetPersonality(this_test_personality); + buffer.SetFromString("127,0,0,0"); + output2.WriteDMX(buffer); + data = backend.GetData(0, &length); + const uint8_t EXPECTED8[] = { 0, 0, 0, 0, + 0xE0 | 15, 0, 0, 0, // Pixel 1 + 0xE0 | 15, 0, 0, 0, // Pixel 2 + 0xE0 | 15, 0, 0, 0, // Pixel 3 + 0xE0 | 15, 0, 0, 0, // Pixel 4 + 0xE0 | 15, 0, 0, 0, // Pixel 5 + 0xE0 | 15, 0, 0, 0, // Pixel 6 + 0xE0 | 15, 0, 0, 0, // Pixel 7 + 0xE0 | 15, 0, 0, 0, // Pixel 8 + 0xE0 | 15, 0, 0, 0, // Pixel 9 + 0xE0 | 15, 0, 0, 0, // Pixel 10 + 0xE0 | 15, 0, 0, 0, // Pixel 11 + 0xE0 | 15, 0, 0, 0, // Pixel 12 + 0xE0 | 15, 0, 0, 0, // Pixel 13 + 0xE0 | 15, 0, 0, 0, // Pixel 14 + 0xE0 | 15, 0, 0, 0, // Pixel 15 + 0xE0 | 15, 0, 0, 0, // Pixel 16 + 0xE0 | 15, 0, 0, 0, // Pixel 17 + 0xE0 | 15, 0, 0, 0, // Pixel 18 + 0xE0 | 15, 0, 0, 0, // Pixel 19 + 0xE0 | 15, 0, 0, 0, // Pixel 20 + 0, 0}; + // with > 16 Pixel we have two latch bytes... + OLA_ASSERT_DATA_EQUALS(EXPECTED8, arraysize(EXPECTED8), data, length); + OLA_ASSERT_EQ(5u, backend.Writes(0)); +} From 58f0ed4ffb6285f6afad165f1083409706a4e4e7 Mon Sep 17 00:00:00 2001 From: Ph0N37Ic5 Date: Sat, 24 Feb 2018 18:48:26 +0100 Subject: [PATCH 06/17] Sync to upstream master, and updated code. Code updated according to suggestions in https://github.com/OpenLightingProject/ola/pull/1382 --- plugins/spi/SPIOutput.cpp | 388 +++++++++++++++++++++++++++++++------- plugins/spi/SPIOutput.h | 38 +++- 2 files changed, 353 insertions(+), 73 deletions(-) diff --git a/plugins/spi/SPIOutput.cpp b/plugins/spi/SPIOutput.cpp index b9a3275ca8..04e655da1a 100644 --- a/plugins/spi/SPIOutput.cpp +++ b/plugins/spi/SPIOutput.cpp @@ -69,7 +69,6 @@ using ola::rdm::RDMResponse; using ola::rdm::ResponderHelper; using ola::rdm::UID; using ola::rdm::UIDSet; -using std::auto_ptr; using std::min; using std::string; using std::vector; @@ -87,15 +86,17 @@ const uint16_t SPIOutput::WS2801_SLOTS_PER_PIXEL = 3; const uint16_t SPIOutput::LPD8806_SLOTS_PER_PIXEL = 3; const uint16_t SPIOutput::P9813_SLOTS_PER_PIXEL = 3; const uint16_t SPIOutput::APA102_SLOTS_PER_PIXEL = 3; -const uint16_t SPIOutput::WS2812b_SLOTS_PER_PIXEL = 3; +const uint16_t SPIOutput::APA102_PB_SLOTS_PER_PIXEL = 4; +const uint16_t SPIOutput::WS2812B_SLOTS_PER_PIXEL = 3; // Number of bytes that each pixel uses on the SPI wires // (if it differs from 1:1 with colors) const uint16_t SPIOutput::P9813_SPI_BYTES_PER_PIXEL = 4; const uint16_t SPIOutput::APA102_SPI_BYTES_PER_PIXEL = 4; -const uint16_t SPIOutput::WS2812b_SPI_BYTES_PER_PIXEL = 9; +const uint16_t SPIOutput::WS2812B_SPI_BYTES_PER_PIXEL = 9; const uint16_t SPIOutput::APA102_START_FRAME_BYTES = 4; +const uint8_t SPIOutput::APA102_LEDFRAME_START_MARK = 0xE0; SPIOutput::RDMOps *SPIOutput::RDMOps::instance = NULL; @@ -125,6 +126,9 @@ const ola::rdm::ResponderOps::ParamHandler { ola::rdm::PID_DMX_PERSONALITY_DESCRIPTION, &SPIOutput::GetPersonalityDescription, NULL}, + { ola::rdm::PID_SLOT_INFO, + &SPIOutput::GetSlotInfo, + NULL}, { ola::rdm::PID_DMX_START_ADDRESS, &SPIOutput::GetDmxStartAddress, &SPIOutput::SetDmxStartAddress}, @@ -182,26 +186,98 @@ SPIOutput::SPIOutput(const UID &uid, SPIBackendInterface *backend, m_spi_device_name = FilenameFromPathOrPath(m_backend->DevicePath()); PersonalityCollection::PersonalityList personalities; - personalities.push_back(Personality(m_pixel_count * WS2801_SLOTS_PER_PIXEL, - "WS2801 Individual Control")); - personalities.push_back(Personality(WS2801_SLOTS_PER_PIXEL, - "WS2801 Combined Control")); - personalities.push_back(Personality(m_pixel_count * LPD8806_SLOTS_PER_PIXEL, - "LPD8806 Individual Control")); - personalities.push_back(Personality(LPD8806_SLOTS_PER_PIXEL, - "LPD8806 Combined Control")); - personalities.push_back(Personality(m_pixel_count * P9813_SLOTS_PER_PIXEL, - "P9813 Individual Control")); - personalities.push_back(Personality(P9813_SLOTS_PER_PIXEL, - "P9813 Combined Control")); - personalities.push_back(Personality(m_pixel_count * APA102_SLOTS_PER_PIXEL, - "APA102 Individual Control")); - personalities.push_back(Personality(APA102_SLOTS_PER_PIXEL, - "APA102 Combined Control")); + // personality description is max 32 characters + + ola::rdm::SlotDataCollection::SlotDataList sd_rgb_combined; + sd_rgb_combined.push_back( + ola::rdm::SlotData::PrimarySlot(ola::rdm::SD_COLOR_ADD_RED, 0)); + sd_rgb_combined.push_back( + ola::rdm::SlotData::PrimarySlot(ola::rdm::SD_COLOR_ADD_GREEN, 0)); + sd_rgb_combined.push_back( + ola::rdm::SlotData::PrimarySlot(ola::rdm::SD_COLOR_ADD_BLUE, 0)); + ola::rdm::SlotDataCollection sdc_rgb_combined; + sdc_rgb_combined = ola::rdm::SlotDataCollection(sd_rgb_combined); + + ola::rdm::SlotDataCollection::SlotDataList sd_irgb_combined; + sd_irgb_combined.push_back( + ola::rdm::SlotData::PrimarySlot(ola::rdm::SD_INTENSITY, 0)); + sd_irgb_combined.push_back( + ola::rdm::SlotData::PrimarySlot(ola::rdm::SD_COLOR_ADD_RED, 0)); + sd_irgb_combined.push_back( + ola::rdm::SlotData::PrimarySlot(ola::rdm::SD_COLOR_ADD_GREEN, 0)); + sd_irgb_combined.push_back( + ola::rdm::SlotData::PrimarySlot(ola::rdm::SD_COLOR_ADD_BLUE, 0)); + ola::rdm::SlotDataCollection sdc_irgb_combined; + sdc_irgb_combined = ola::rdm::SlotDataCollection(sd_irgb_combined); + + personalities.insert( + personalities.begin() + PERS_WS2801_INDIVIDUAL - 1, + Personality(m_pixel_count * WS2801_SLOTS_PER_PIXEL, + "WS2801 Individual Control")); + personalities.insert( + personalities.begin() + PERS_WS2801_COMBINED - 1, + Personality(WS2801_SLOTS_PER_PIXEL, + "WS2801 Combined Control", + sdc_rgb_combined)); + + personalities.insert( + personalities.begin() + PERS_LDP8806_INDIVIDUAL - 1, + Personality(m_pixel_count * LPD8806_SLOTS_PER_PIXEL, + "LPD8806 Individual Control")); + personalities.insert( + personalities.begin() + PERS_LDP8806_COMBINED - 1, + Personality(LPD8806_SLOTS_PER_PIXEL, + "LPD8806 Combined Control", + sdc_rgb_combined)); + + personalities.insert( + personalities.begin() + PERS_P9813_INDIVIDUAL - 1, + Personality(m_pixel_count * P9813_SLOTS_PER_PIXEL, + "P9813 Individual Control")); + personalities.insert( + personalities.begin() + PERS_P9813_COMBINED - 1, + Personality(P9813_SLOTS_PER_PIXEL, + "P9813 Combined Control", + sdc_rgb_combined)); + + personalities.insert( + personalities.begin() + PERS_APA102_INDIVIDUAL - 1, + Personality(m_pixel_count * APA102_SLOTS_PER_PIXEL, + "APA102 Individual Control")); + + personalities.insert( + personalities.begin() + PERS_APA102_COMBINED - 1, + Personality(APA102_SLOTS_PER_PIXEL, + "APA102 Combined Control", + sdc_rgb_combined)); + + personalities.insert( + personalities.begin() + PERS_APA102_PB_INDIVIDUAL - 1, + Personality(m_pixel_count * APA102_PB_SLOTS_PER_PIXEL, + "APA102 Pixel Brightness Individ.")); + + personalities.insert( + personalities.begin() + PERS_APA102_PB_COMBINED - 1, + Personality(APA102_PB_SLOTS_PER_PIXEL, + "APA102 Pixel Brightness Combined", + sdc_irgb_combined)); + + personalities.insert( + personalities.begin() + PERS_WS2812B_PB_INDIVIDUAL - 1, + Personality(m_pixel_count * WS2812B_PB_SLOTS_PER_PIXEL, + "WS2812b Individual Control")); + + personalities.insert( + personalities.begin() + PERS_WS2812B_PB_COMBINED - 1, + Personality(WS2812B_PB_SLOTS_PER_PIXEL, + "WS2812b Combined Control", + sdc_irgb_combined)); + + m_personality_collection.reset(new PersonalityCollection(personalities)); m_personality_manager.reset(new PersonalityManager( m_personality_collection.get())); - m_personality_manager->SetActivePersonality(1); + m_personality_manager->SetActivePersonality(PERS_WS2801_INDIVIDUAL); #ifdef HAVE_GETLOADAVG m_sensors.push_back(new LoadSensor(ola::system::LOAD_AVERAGE_1_MIN, @@ -294,42 +370,43 @@ void SPIOutput::SendRDMRequest(RDMRequest *request, bool SPIOutput::InternalWriteDMX(const DmxBuffer &buffer) { switch (m_personality_manager->ActivePersonalityNumber()) { - case 1: + case PERS_WS2801_INDIVIDUAL: IndividualWS2801Control(buffer); break; - case 2: + case PERS_WS2801_COMBINED: CombinedWS2801Control(buffer); break; - case 3: + case PERS_LDP8806_INDIVIDUAL: IndividualLPD8806Control(buffer); break; - case 4: + case PERS_LDP8806_COMBINED: CombinedLPD8806Control(buffer); break; - case 5: + case PERS_P9813_INDIVIDUAL: IndividualP9813Control(buffer); break; - case 6: + case PERS_P9813_COMBINED: CombinedP9813Control(buffer); break; - case 7: + case PERS_APA102_INDIVIDUAL: IndividualAPA102Control(buffer); break; - case 8: + case PERS_APA102_COMBINED: CombinedAPA102Control(buffer); break; - case 9: - IndividualWS2812bControl(buffer); - break; - case 10: - CombinedWS2812bControl(buffer); - break; + case PERS_APA102_PB_INDIVIDUAL: + IndividualAPA102ControlPixelBrightness(buffer); + break; + case PERS_APA102_PB_COMBINED: + CombinedAPA102ControlPixelBrightness(buffer); + break; default: break; } return true; } + void SPIOutput::IndividualWS2801Control(const DmxBuffer &buffer) { // We always check out the entire string length, even if we only have data // for part of it @@ -383,7 +460,7 @@ void SPIOutput::IndividualLPD8806Control(const DmxBuffer &buffer) { if (!output) return; - const unsigned int length = std::min(m_pixel_count * LPD8806_SLOTS_PER_PIXEL, + const unsigned int length = min(m_pixel_count * LPD8806_SLOTS_PER_PIXEL, buffer.Size() - first_slot); for (unsigned int i = 0; i < length / LPD8806_SLOTS_PER_PIXEL; i++) { @@ -526,7 +603,7 @@ void SPIOutput::IndividualAPA102Control(const DmxBuffer &buffer) { const unsigned int first_slot = m_start_address - 1; // 0 offset // only do something if at least 1 pixel can be updated.. - if (buffer.Size() - first_slot < APA102_SLOTS_PER_PIXEL) { + if ((buffer.Size() - first_slot) < APA102_SLOTS_PER_PIXEL) { OLA_INFO << "Insufficient DMX data, required " << APA102_SLOTS_PER_PIXEL << ", got " << buffer.Size() - first_slot; return; @@ -587,6 +664,81 @@ void SPIOutput::IndividualAPA102Control(const DmxBuffer &buffer) { m_backend->Commit(m_output_number); } + +void SPIOutput::IndividualAPA102ControlPixelBrightness( + const DmxBuffer &buffer) { + // some detailed information on the protocol: + // https://cpldcpu.wordpress.com/2014/11/30/understanding-the-apa102-superled/ + // Data-Struct + // StartFrame: 4 bytes = 32 bits zeros (APA102_START_FRAME_BYTES) + // LEDFrame: + // 1 byte START_MARK + pixel brightness + // 3 bytes color info (Blue, Green, Red) + // EndFrame: (n/2)bits; n = pixel_count + + // calculate DMX-start-address + const unsigned int first_slot = m_start_address - 1; // 0 offset + + // only do something if at least 1 pixel can be updated.. + if ((buffer.Size() - first_slot) < APA102_PB_SLOTS_PER_PIXEL) { + OLA_INFO << "Insufficient DMX data, required " << APA102_PB_SLOTS_PER_PIXEL + << ", got " << buffer.Size() - first_slot; + return; + } + + // We always check out the entire string length, even if we only have data + // for part of it + uint16_t output_length = (m_pixel_count * APA102_SPI_BYTES_PER_PIXEL); + // only add the APA102_START_FRAME_BYTES on the first port!! + if (m_output_number == 0) { + output_length += APA102_START_FRAME_BYTES; + } + uint8_t *output = m_backend->Checkout( + m_output_number, + output_length, + CalculateAPA102LatchBytes(m_pixel_count)); + + // only update SPI data if possible + if (!output) { + return; + } + + // only write to APA102_START_FRAME_BYTES on the first port!! + if (m_output_number == 0) { + // set APA102_START_FRAME_BYTES to zero + memset(output, 0, APA102_START_FRAME_BYTES); + } + + for (uint16_t i = 0; i < m_pixel_count; i++) { + // Convert RGB to APA102 Pixel + uint16_t offset = first_slot + (i * APA102_PB_SLOTS_PER_PIXEL); + + uint16_t spi_offset = (i * APA102_SPI_BYTES_PER_PIXEL); + // only skip APA102_START_FRAME_BYTES on the first port!! + if (m_output_number == 0) { + // We need to avoid the first 4 bytes of the buffer since that acts as a + // start of frame delimiter + spi_offset += APA102_START_FRAME_BYTES; + } + // set pixel data + // only write pixel data if buffer has complete data for this pixel: + if ((buffer.Size() - offset) >= APA102_PB_SLOTS_PER_PIXEL) { + // first Byte: + // 3 bits start mark (111) (APA102_LEDFRAME_START_MARK) + + // 5 bits pixel brightness (datasheet name: global brightness) + output[spi_offset + 0] = (SPIOutput::APA102_LEDFRAME_START_MARK | + CalculateAPA102PixelBrightness(buffer.Get(offset + 0))); + // Convert RGB to APA102 Pixel + output[spi_offset + 1] = buffer.Get(offset + 3); // blue + output[spi_offset + 2] = buffer.Get(offset + 2); // green + output[spi_offset + 3] = buffer.Get(offset + 1); // red + } + } + + // write output back + m_backend->Commit(m_output_number); +} + void SPIOutput::CombinedAPA102Control(const DmxBuffer &buffer) { // for Protocol details see IndividualAPA102Control @@ -594,7 +746,7 @@ void SPIOutput::CombinedAPA102Control(const DmxBuffer &buffer) { const uint16_t first_slot = m_start_address - 1; // 0 offset // check if enough data is there. - if (buffer.Size() - first_slot < APA102_SLOTS_PER_PIXEL) { + if ((buffer.Size() - first_slot) < APA102_SLOTS_PER_PIXEL) { OLA_INFO << "Insufficient DMX data, required " << APA102_SLOTS_PER_PIXEL << ", got " << buffer.Size() - first_slot; return; @@ -644,6 +796,68 @@ void SPIOutput::CombinedAPA102Control(const DmxBuffer &buffer) { m_backend->Commit(m_output_number); } + +void SPIOutput::CombinedAPA102ControlPixelBrightness(const DmxBuffer &buffer) { + // for Protocol details see IndividualAPA102Control + // calculate DMX-start-address + const uint16_t first_slot = m_start_address - 1; // 0 offset + + // check if enough data is there. + if ((buffer.Size() - first_slot) < APA102_PB_SLOTS_PER_PIXEL) { + OLA_INFO << "Insufficient DMX data, required " << APA102_PB_SLOTS_PER_PIXEL + << ", got " << buffer.Size() - first_slot; + return; + } + + // We always check out the entire string length, even if we only have data + // for part of it + uint16_t output_length = (m_pixel_count * APA102_SPI_BYTES_PER_PIXEL); + // only add the APA102_START_FRAME_BYTES on the first port!! + if (m_output_number == 0) { + output_length += APA102_START_FRAME_BYTES; + } + uint8_t *output = m_backend->Checkout( + m_output_number, + output_length, + CalculateAPA102LatchBytes(m_pixel_count)); + + // only update SPI data if possible + if (!output) { + return; + } + + // only write to APA102_START_FRAME_BYTES on the first port!! + if (m_output_number == 0) { + // set APA102_START_FRAME_BYTES to zero + memset(output, 0, APA102_START_FRAME_BYTES); + } + + // create Pixel Data + uint8_t pixel_data[APA102_SPI_BYTES_PER_PIXEL]; + // first Byte: + // 3 bits start mark (111) (APA102_LEDFRAME_START_MARK) + + // 5 bits pixel brightness (datasheet name: global brightness) + pixel_data[0] = (SPIOutput::APA102_LEDFRAME_START_MARK | + CalculateAPA102PixelBrightness(buffer.Get(first_slot + 0))); + // color data + pixel_data[1] = buffer.Get(first_slot + 3); // Get Blue + pixel_data[2] = buffer.Get(first_slot + 2); // Get Green + pixel_data[3] = buffer.Get(first_slot + 1); // Get Red + + // set all pixel to same value + for (uint16_t i = 0; i < m_pixel_count; i++) { + uint16_t spi_offset = (i * APA102_SPI_BYTES_PER_PIXEL); + if (m_output_number == 0) { + spi_offset += APA102_START_FRAME_BYTES; + } + memcpy(&output[spi_offset], pixel_data, + APA102_SPI_BYTES_PER_PIXEL); + } + + // write output back... + m_backend->Commit(m_output_number); +} + /** * Calculate Latch Bytes for APA102: * Use at least half the pixel count bits @@ -661,41 +875,58 @@ uint8_t SPIOutput::CalculateAPA102LatchBytes(uint16_t pixel_count) { return latch_bytes; } +/** + * Calculate Pixel Brightness for APA102: + * Map Input to Output range: + * Input is 8bit value (0..255) + * Output is 5bit value (0..31) + */ +uint8_t SPIOutput::CalculateAPA102PixelBrightness(uint8_t brightness) { + return (brightness >> 3); +} + void SPIOutput::IndividualWS2812bControl(const DmxBuffer &buffer) { const unsigned int first_slot = m_start_address - 1; // 0 offset - if (buffer.Size() - first_slot < WS2812b_SLOTS_PER_PIXEL) { - OLA_INFO << "Insufficient DMX data, required " << WS2812b_SLOTS_PER_PIXEL + if (buffer.Size() - first_slot < WS2812B_SLOTS_PER_PIXEL) { + OLA_INFO << "Insufficient DMX data, required " << WS2812B_SLOTS_PER_PIXEL << ", got " << buffer.Size() - first_slot; return; } // We always check out the entire string length, even if we only have data // for part of it - const unsigned int output_length = m_pixel_count * WS2812b_SLOTS_PER_PIXEL; + const unsigned int output_length = m_pixel_count * WS2812B_SPI_BYTES_PER_PIXEL; uint8_t *output = m_backend->Checkout(m_output_number, output_length); if (!output) { OLA_INFO << "Unable to create output buffer of required length: " << output_length; return; } - const unsigned int length = std::min(m_pixel_count * WS2812b_SLOTS_PER_PIXEL, + const unsigned int length = std::min(m_pixel_count * WS2812B_SLOTS_PER_PIXEL, buffer.Size() - first_slot); - for (unsigned int i = 0; i < length / WS2812b_SLOTS_PER_PIXEL; i++) { + for (unsigned int i = 0; i < length / WS2812B_SLOTS_PER_PIXEL; i++) { // Convert RGB to GRB - unsigned int offset = first_slot + i * WS2812b_SLOTS_PER_PIXEL; + unsigned int offset = first_slot + i * WS2812B_SLOTS_PER_PIXEL; uint8_t r = buffer.Get(offset); uint8_t g = buffer.Get(offset + 1); uint8_t b = buffer.Get(offset + 2); - output[i * WS2812b_SPI_BYTES_PER_PIXEL] = 0x24 | ((g & 0x1) << 1) | ((g & 0x2) << 3) | ((g & 0x4) << 5); - output[i * WS2812b_SPI_BYTES_PER_PIXEL + 1] = 0x49 | ((g & 0x8) >> 1) | ((g & 0x10) << 1); - output[i * WS2812b_SPI_BYTES_PER_PIXEL + 2] = 0x92 | ((g & 0x20) >> 5) | ((g & 0x40) >> 3) | ((g & 0x80) >> 1); - output[i * WS2812b_SPI_BYTES_PER_PIXEL + 3] = 0x24 | ((r & 0x1) << 1) | ((r & 0x2) << 3) | ((r & 0x4) << 5); - output[i * WS2812b_SPI_BYTES_PER_PIXEL + 4] = 0x49 | ((r & 0x8) >> 1) | ((r & 0x10) << 1); - output[i * WS2812b_SPI_BYTES_PER_PIXEL + 5] = 0x92 | ((r & 0x20) >> 5) | ((r & 0x40) >> 3) | ((r & 0x80) >> 1); - output[i * WS2812b_SPI_BYTES_PER_PIXEL + 6] = 0x24 | ((b & 0x1) << 1) | ((b & 0x2) << 3) | ((b & 0x4) << 5); - output[i * WS2812b_SPI_BYTES_PER_PIXEL + 7] = 0x49 | ((b & 0x8) >> 1) | ((b & 0x10) << 1); - output[i * WS2812b_SPI_BYTES_PER_PIXEL + 8] = 0x92 | ((b & 0x20) >> 5) | ((b & 0x40) >> 3) | ((b & 0x80) >> 1); + uint8_t low = 0, mid = 0, high = 0; + + WS2812bByteMapper(g, *low, *mid, *high); + output[i * WS2812B_SPI_BYTES_PER_PIXEL] = low; + output[i * WS2812B_SPI_BYTES_PER_PIXEL + 1] = mid; + output[i * WS2812B_SPI_BYTES_PER_PIXEL + 2] = high; + + WS2812bByteMapper(r, *low, *mid, *high); + output[i * WS2812B_SPI_BYTES_PER_PIXEL + 3] = low; + output[i * WS2812B_SPI_BYTES_PER_PIXEL + 4] = mid; + output[i * WS2812B_SPI_BYTES_PER_PIXEL + 5] = high; + + WS2812bByteMapper(b, *low, *mid, *high); + output[i * WS2812B_SPI_BYTES_PER_PIXEL + 6] = low; + output[i * WS2812B_SPI_BYTES_PER_PIXEL + 7] = mid; + output[i * WS2812B_SPI_BYTES_PER_PIXEL + 8] = high; } // write output back... @@ -704,15 +935,15 @@ void SPIOutput::IndividualWS2812bControl(const DmxBuffer &buffer) { void SPIOutput::CombinedWS2812bControl(const DmxBuffer &buffer) { const unsigned int first_slot = m_start_address - 1; // 0 offset - if (buffer.Size() - first_slot < WS2812b_SLOTS_PER_PIXEL) { - OLA_INFO << "Insufficient DMX data, required " << WS2812b_SLOTS_PER_PIXEL + if (buffer.Size() - first_slot < WS2812B_SLOTS_PER_PIXEL) { + OLA_INFO << "Insufficient DMX data, required " << WS2812B_SLOTS_PER_PIXEL << ", got " << buffer.Size() - first_slot; return; } // We always check out the entire string length, even if we only have data // for part of it - const unsigned int output_length = m_pixel_count * WS2801_SLOTS_PER_PIXEL; + const unsigned int output_length = m_pixel_count * WS2801_SPI_BYTES_PER_PIXEL; uint8_t *output = m_backend->Checkout(m_output_number, output_length); if (!output) { OLA_INFO << "Unable to create output buffer of required length: " << output_length; @@ -723,34 +954,49 @@ void SPIOutput::CombinedWS2812bControl(const DmxBuffer &buffer) { uint8_t r = buffer.Get(first_slot); uint8_t g = buffer.Get(first_slot + 1); uint8_t b = buffer.Get(first_slot + 2); + uint8_t low = 0, mid = 0, high = 0; // create Pixel Data - uint8_t pixel_data[WS2812b_SPI_BYTES_PER_PIXEL]; - pixel_data[0] = 0x24 | ((g & 0x1) << 1) | ((g & 0x2) << 3) | ((g & 0x4) << 5); - pixel_data[1] = 0x49 | ((g & 0x8) >> 1) | ((g & 0x10) << 1); - pixel_data[2] = 0x92 | ((g & 0x20) >> 5) | ((g & 0x40) >> 3) | ((g & 0x80) >> 1); - pixel_data[3] = 0x24 | ((r & 0x1) << 1) | ((r & 0x2) << 3) | ((r & 0x4) << 5); - pixel_data[4] = 0x49 | ((r & 0x8) >> 1) | ((r & 0x10) << 1); - pixel_data[5] = 0x92 | ((r & 0x20) >> 5) | ((r & 0x40) >> 3) | ((r & 0x80) >> 1); - pixel_data[6] = 0x24 | ((b & 0x1) << 1) | ((b & 0x2) << 3) | ((b & 0x4) << 5); - pixel_data[7] = 0x49 | ((b & 0x8) >> 1) | ((b & 0x10) << 1); - pixel_data[8] = 0x92 | ((b & 0x20) >> 5) | ((b & 0x40) >> 3) | ((b & 0x80) >> 1); + uint8_t pixel_data[WS2812B_SPI_BYTES_PER_PIXEL]; + + WS2812bByteMapper(g, *low, *mid, *high); + pixel_data[0] = low; + pixel_data[1] = mid; + pixel_data[2] = high; + + WS2812bByteMapper(r, *low, *mid, *high); + pixel_data[3] = low; + pixel_data[4] = mid; + pixel_data[5] = high; + + WS2812bByteMapper(b, *low, *mid, *high); + pixel_data[6] = low; + pixel_data[7] = mid; + pixel_data[8] = high; // set all pixel to same value for (uint16_t i = 0; i < m_pixel_count; i++) { - uint16_t spi_offset = (i * WS2812b_SPI_BYTES_PER_PIXEL); - memcpy(&output[spi_offset], pixel_data, - WS2812b_SPI_BYTES_PER_PIXEL); + memcpy(&output[i * WS2812B_SPI_BYTES_PER_PIXEL], pixel_data, + WS2812B_SPI_BYTES_PER_PIXEL); } // write output back... m_backend->Commit(m_output_number); } +void SPIOutput::WS2812bByteMapper(uint8_t input, uint8_t &low, uint8_t &mid, uint8_t &high) +{ + low = 0x24 | ((input & 0x1) << 1) | ((input & 0x2) << 3) | ((input & 0x4) << 5); + mid = 0x49 | ((input & 0x8) >> 1) | ((input & 0x10) << 1); + high = 0x92 | ((input & 0x20) >> 5) | ((input & 0x40) >> 3) | ((input & 0x80) >> 1); +} + + RDMResponse *SPIOutput::GetDeviceInfo(const RDMRequest *request) { return ResponderHelper::GetDeviceInfo( request, ola::rdm::OLA_SPI_DEVICE_MODEL, - ola::rdm::PRODUCT_CATEGORY_FIXTURE, 4, + ola::rdm::PRODUCT_CATEGORY_FIXTURE, + 5, // RDM software version (increment on personality changes) m_personality_manager.get(), m_start_address, 0, m_sensors.size()); @@ -799,6 +1045,10 @@ RDMResponse *SPIOutput::GetPersonalityDescription(const RDMRequest *request) { request, m_personality_manager.get()); } +RDMResponse *SPIOutput::GetSlotInfo(const RDMRequest *request) { + return ResponderHelper::GetSlotInfo(request, m_personality_manager.get()); +} + RDMResponse *SPIOutput::GetDmxStartAddress(const RDMRequest *request) { return ResponderHelper::GetDmxAddress(request, m_personality_manager.get(), m_start_address); diff --git a/plugins/spi/SPIOutput.h b/plugins/spi/SPIOutput.h index 7a889b0a19..741ac8922a 100644 --- a/plugins/spi/SPIOutput.h +++ b/plugins/spi/SPIOutput.h @@ -38,6 +38,29 @@ namespace spi { class SPIOutput: public ola::rdm::DiscoverableRDMControllerInterface { public: + // definitions for all SPI Personalities + // keep personality order + // - it is important for backwards compatibility + // the integer representation is used to store the configuration in files + // and RDM Personality IDs should also be stable. + // new ones can be added at end. + // remember to increment RDM-Version in + // SPIOutput.cpp SPIOutput::GetDeviceInfo() + enum SPI_PERSONALITY { + PERS_WS2801_INDIVIDUAL = 1, + PERS_WS2801_COMBINED = 2, + PERS_LDP8806_INDIVIDUAL = 3, + PERS_LDP8806_COMBINED = 4, + PERS_P9813_INDIVIDUAL = 5, + PERS_P9813_COMBINED = 6, + PERS_APA102_INDIVIDUAL = 7, + PERS_APA102_COMBINED = 8, + PERS_APA102_PB_INDIVIDUAL, + PERS_APA102_PB_COMBINED, + PERS_WS2812B_INDIVIDUAL, + PERS_WS2812B_COMBINED, + }; + struct Options { std::string device_label; uint8_t pixel_count; @@ -113,9 +136,11 @@ class SPIOutput: public ola::rdm::DiscoverableRDMControllerInterface { void CombinedP9813Control(const DmxBuffer &buffer); void IndividualAPA102Control(const DmxBuffer &buffer); void CombinedAPA102Control(const DmxBuffer &buffer); + void IndividualAPA102ControlPixelBrightness(const DmxBuffer &buffer); + void CombinedAPA102ControlPixelBrightness(const DmxBuffer &buffer); void IndividualWS2812bControl(const DmxBuffer &buffer); void CombinedWS2812bControl(const DmxBuffer &buffer); - + unsigned int LPD8806BufferSize() const; void WriteSPIData(const uint8_t *data, unsigned int length); @@ -140,6 +165,8 @@ class SPIOutput: public ola::rdm::DiscoverableRDMControllerInterface { const ola::rdm::RDMRequest *request); ola::rdm::RDMResponse *GetPersonalityDescription( const ola::rdm::RDMRequest *request); + ola::rdm::RDMResponse *GetSlotInfo( + const ola::rdm::RDMRequest *request); ola::rdm::RDMResponse *GetDmxStartAddress( const ola::rdm::RDMRequest *request); ola::rdm::RDMResponse *SetDmxStartAddress( @@ -176,6 +203,7 @@ class SPIOutput: public ola::rdm::DiscoverableRDMControllerInterface { // Helpers uint8_t P9813CreateFlag(uint8_t red, uint8_t green, uint8_t blue); static uint8_t CalculateAPA102LatchBytes(uint16_t pixel_count); + static uint8_t CalculateAPA102PixelBrightness(uint8_t brightness); static const uint8_t SPI_MODE; static const uint8_t SPI_BITS_PER_WORD; @@ -186,11 +214,13 @@ class SPIOutput: public ola::rdm::DiscoverableRDMControllerInterface { static const uint16_t P9813_SLOTS_PER_PIXEL; static const uint16_t P9813_SPI_BYTES_PER_PIXEL; static const uint16_t APA102_SLOTS_PER_PIXEL; + static const uint16_t APA102_PB_SLOTS_PER_PIXEL; static const uint16_t APA102_SPI_BYTES_PER_PIXEL; static const uint16_t APA102_START_FRAME_BYTES; - static const uint16_t WS2812b_SLOTS_PER_PIXEL; - static const uint16_t WS2812b_SPI_BYTES_PER_PIXEL; - + static const uint8_t APA102_LEDFRAME_START_MARK; + static const uint16_t WS2812B_SLOTS_PER_PIXEL; + static const uint16_t WS2812B_SPI_BYTES_PER_PIXEL; + static const ola::rdm::ResponderOps::ParamHandler PARAM_HANDLERS[]; }; From 9205b5646c1566458622555a3c4a333ddbad8b40 Mon Sep 17 00:00:00 2001 From: Ph0N37Ic5 Date: Sat, 24 Feb 2018 19:12:05 +0100 Subject: [PATCH 07/17] Minor bugfixes, should compile now. --- plugins/spi/SPIOutput.cpp | 22 +++++++++++----------- plugins/spi/SPIOutput.h | 1 + 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/plugins/spi/SPIOutput.cpp b/plugins/spi/SPIOutput.cpp index 04e655da1a..0d518a9d16 100644 --- a/plugins/spi/SPIOutput.cpp +++ b/plugins/spi/SPIOutput.cpp @@ -263,13 +263,13 @@ SPIOutput::SPIOutput(const UID &uid, SPIBackendInterface *backend, sdc_irgb_combined)); personalities.insert( - personalities.begin() + PERS_WS2812B_PB_INDIVIDUAL - 1, - Personality(m_pixel_count * WS2812B_PB_SLOTS_PER_PIXEL, + personalities.begin() + PERS_WS2812B_INDIVIDUAL - 1, + Personality(m_pixel_count * WS2812B_SLOTS_PER_PIXEL, "WS2812b Individual Control")); personalities.insert( - personalities.begin() + PERS_WS2812B_PB_COMBINED - 1, - Personality(WS2812B_PB_SLOTS_PER_PIXEL, + personalities.begin() + PERS_WS2812B_COMBINED - 1, + Personality(WS2812B_SLOTS_PER_PIXEL, "WS2812b Combined Control", sdc_irgb_combined)); @@ -913,17 +913,17 @@ void SPIOutput::IndividualWS2812bControl(const DmxBuffer &buffer) { uint8_t b = buffer.Get(offset + 2); uint8_t low = 0, mid = 0, high = 0; - WS2812bByteMapper(g, *low, *mid, *high); + WS2812bByteMapper(g, low, mid, high); output[i * WS2812B_SPI_BYTES_PER_PIXEL] = low; output[i * WS2812B_SPI_BYTES_PER_PIXEL + 1] = mid; output[i * WS2812B_SPI_BYTES_PER_PIXEL + 2] = high; - WS2812bByteMapper(r, *low, *mid, *high); + WS2812bByteMapper(r, low, mid, high); output[i * WS2812B_SPI_BYTES_PER_PIXEL + 3] = low; output[i * WS2812B_SPI_BYTES_PER_PIXEL + 4] = mid; output[i * WS2812B_SPI_BYTES_PER_PIXEL + 5] = high; - WS2812bByteMapper(b, *low, *mid, *high); + WS2812bByteMapper(b, low, mid, high); output[i * WS2812B_SPI_BYTES_PER_PIXEL + 6] = low; output[i * WS2812B_SPI_BYTES_PER_PIXEL + 7] = mid; output[i * WS2812B_SPI_BYTES_PER_PIXEL + 8] = high; @@ -943,7 +943,7 @@ void SPIOutput::CombinedWS2812bControl(const DmxBuffer &buffer) { // We always check out the entire string length, even if we only have data // for part of it - const unsigned int output_length = m_pixel_count * WS2801_SPI_BYTES_PER_PIXEL; + const unsigned int output_length = m_pixel_count * WS2812B_SPI_BYTES_PER_PIXEL; uint8_t *output = m_backend->Checkout(m_output_number, output_length); if (!output) { OLA_INFO << "Unable to create output buffer of required length: " << output_length; @@ -959,17 +959,17 @@ void SPIOutput::CombinedWS2812bControl(const DmxBuffer &buffer) { // create Pixel Data uint8_t pixel_data[WS2812B_SPI_BYTES_PER_PIXEL]; - WS2812bByteMapper(g, *low, *mid, *high); + WS2812bByteMapper(g, low, mid, high); pixel_data[0] = low; pixel_data[1] = mid; pixel_data[2] = high; - WS2812bByteMapper(r, *low, *mid, *high); + WS2812bByteMapper(r, low, mid, high); pixel_data[3] = low; pixel_data[4] = mid; pixel_data[5] = high; - WS2812bByteMapper(b, *low, *mid, *high); + WS2812bByteMapper(b, low, mid, high); pixel_data[6] = low; pixel_data[7] = mid; pixel_data[8] = high; diff --git a/plugins/spi/SPIOutput.h b/plugins/spi/SPIOutput.h index 741ac8922a..88c47bb445 100644 --- a/plugins/spi/SPIOutput.h +++ b/plugins/spi/SPIOutput.h @@ -204,6 +204,7 @@ class SPIOutput: public ola::rdm::DiscoverableRDMControllerInterface { uint8_t P9813CreateFlag(uint8_t red, uint8_t green, uint8_t blue); static uint8_t CalculateAPA102LatchBytes(uint16_t pixel_count); static uint8_t CalculateAPA102PixelBrightness(uint8_t brightness); + void WS2812bByteMapper(uint8_t input, uint8_t &low, uint8_t &mid, uint8_t &high); static const uint8_t SPI_MODE; static const uint8_t SPI_BITS_PER_WORD; From 9c223867285f310a1059c9ecdff10c2fd0f75114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20Gr=C3=B8nvold?= Date: Sat, 24 Feb 2018 21:40:13 +0100 Subject: [PATCH 08/17] Added some tests of WS2812b. --- plugins/spi/SPIOutputTest.cpp | 107 ++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/plugins/spi/SPIOutputTest.cpp b/plugins/spi/SPIOutputTest.cpp index c39038864c..e3c76c477d 100644 --- a/plugins/spi/SPIOutputTest.cpp +++ b/plugins/spi/SPIOutputTest.cpp @@ -1139,3 +1139,110 @@ void SPIOutputTest::testCombinedAPA102ControlPixelBrightness() { OLA_ASSERT_DATA_EQUALS(EXPECTED8, arraysize(EXPECTED8), data, length); OLA_ASSERT_EQ(5u, backend.Writes(0)); } + +/* + * WS2812b unit tests +*/ +void SPIOutputTest::testIndividualWS2812bControl() { + const uint16_t this_test_personality = SPIOutput::PERS_WS2812B_INDIVIDUAL; + // setup Backend + FakeSPIBackend backend(2); + SPIOutput::Options options(0, "Test SPI Device"); + // setup pixel_count to 2 (enough to test all cases) + options.pixel_count = 2; + // setup SPIOutput + SPIOutput output(m_uid, &backend, options); + // set personality to Individual WS2812b + output.SetPersonality(this_test_personality); + + // simulate incoming dmx data with this buffer + DmxBuffer buffer; + // setup an pointer to the returned data (the fake SPI data stream) + unsigned int length = 0; + const uint8_t *data = NULL; + + // test1 + // setup some 'DMX' data + buffer.SetFromString("1, 10, 100"); + // simulate incoming data + output.WriteDMX(buffer); + // get fake SPI data stream + data = backend.GetData(0, &length); + // this is the expected spi data stream: + const uint8_t EXPECTED1[] = { 0x92, 0x4D, 0x34, //Pixel 1 Green + 0x92, 0x49, 0x26, //Pixel 1 Red + 0x9B, 0x49, 0xA4, //Pixel 1 Blue + 0x92, 0x49, 0x24, //Pixel 2 Green + 0x92, 0x49, 0x24, //Pixel 2 Red + 0x92, 0x49, 0x24 //Pixel 2 Blue + }; + // check for Equality + OLA_ASSERT_DATA_EQUALS(EXPECTED1, arraysize(EXPECTED1), data, length); + // check if the output writes are 1 + OLA_ASSERT_EQ(1u, backend.Writes(0)); + + // test2 + buffer.SetFromString("255,128,0,10,20,30"); + output.WriteDMX(buffer); + data = backend.GetData(0, &length); + const uint8_t EXPECTED2[] = { 0xD2, 0x49, 0x24, //Pixel 1 Green (128) + 0xDB, 0x6D, 0xB6, //Pixel 1 Red (255) + 0x92, 0x49, 0x24, //Pixel 1 Blue (0) + 0x92, 0x69, 0xA4, //Pixel 2 Green (20) + 0x92, 0x4D, 0x34, //Pixel 2 Red (10) + 0x92, 0x6D, 0xB4 //Pixel 2 Blue (30) + }; + OLA_ASSERT_DATA_EQUALS(EXPECTED2, arraysize(EXPECTED2), data, length); + OLA_ASSERT_EQ(2u, backend.Writes(0)); + + // test3 + // test what happens when only new data for the first leds is available. + // later data should be not modified so for pixel2 data set in test2 is valid + buffer.SetFromString("34,56,78"); + output.WriteDMX(buffer); + data = backend.GetData(0, &length); + const uint8_t EXPECTED3[] = { 0x93, 0x6D, 0x24, //Pixel 1 Green (56) + 0x93, 0x49, 0x34, //Pixel 1 Red (34) + 0x9A, 0x4D, 0xB4, //Pixel 1 Blue (78) + 0x92, 0x69, 0xA4, //Pixel 2 Green (20) + 0x92, 0x4D, 0x34, //Pixel 2 Red (10) + 0x92, 0x6D, 0xB4 //Pixel 2 Blue (30) + }; + OLA_ASSERT_DATA_EQUALS(EXPECTED3, arraysize(EXPECTED3), data, length); + OLA_ASSERT_EQ(3u, backend.Writes(0)); + + // test4 + // tests what happens if fewer then needed color information are received + buffer.SetFromString("7, 9"); + output.WriteDMX(buffer); + data = backend.GetData(0, &length); + // check that the returns are the same as test3 (nothing changed) + OLA_ASSERT_DATA_EQUALS(EXPECTED3, arraysize(EXPECTED3), data, length); + OLA_ASSERT_EQ(3u, backend.Writes(0)); + + // test5 + // test with changed StartAddress + // set StartAddress + output.SetStartAddress(3); + // values 1 & 2 should not be visible in SPI data stream + buffer.SetFromString("1,2,3,4,5,6,7,8"); + output.WriteDMX(buffer); + data = backend.GetData(0, &length); + const uint8_t EXPECTED5[] = { 0x92, 0x49, 0xA4, //Pixel 1 Green (4) + 0x92, 0x49, 0x36, //Pixel 1 Red (3) + 0x92, 0x49, 0xA6, //Pixel 1 Blue (5) + 0x92, 0x49, 0xB6, //Pixel 2 Green (7) + 0x92, 0x49, 0xB4, //Pixel 2 Red (6) + 0x92, 0x4D, 0x24 //Pixel 2 Blue (8) + }; + OLA_ASSERT_DATA_EQUALS(EXPECTED5, arraysize(EXPECTED5), data, length); + OLA_ASSERT_EQ(4u, backend.Writes(0)); + // change StartAddress back to default + output.SetStartAddress(1); + + // test6 + // Check nothing changed on the other output. + OLA_ASSERT_EQ(reinterpret_cast(NULL), + backend.GetData(1, &length)); + OLA_ASSERT_EQ(0u, backend.Writes(1)); +} From 585d41f5710f6dc03a53c5658d345cc2fa78fb56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20Gr=C3=B8nvold?= Date: Sat, 24 Feb 2018 22:16:52 +0100 Subject: [PATCH 09/17] Lint --- plugins/spi/SPIOutput.cpp | 40 +++++++++++++++++++++++---------------- plugins/spi/SPIOutput.h | 3 ++- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/plugins/spi/SPIOutput.cpp b/plugins/spi/SPIOutput.cpp index 531f37cc77..493b2b66bf 100644 --- a/plugins/spi/SPIOutput.cpp +++ b/plugins/spi/SPIOutput.cpp @@ -273,6 +273,7 @@ SPIOutput::SPIOutput(const UID &uid, SPIBackendInterface *backend, "WS2812b Combined Control", sdc_irgb_combined)); + m_personality_collection.reset(new PersonalityCollection(personalities)); m_personality_manager.reset(new PersonalityManager( m_personality_collection.get())); @@ -894,10 +895,12 @@ void SPIOutput::IndividualWS2812bControl(const DmxBuffer &buffer) { // We always check out the entire string length, even if we only have data // for part of it - const unsigned int output_length = m_pixel_count * WS2812B_SPI_BYTES_PER_PIXEL; + const unsigned int output_length = m_pixel_count + * WS2812B_SPI_BYTES_PER_PIXEL; uint8_t *output = m_backend->Checkout(m_output_number, output_length); if (!output) { - OLA_INFO << "Unable to create output buffer of required length: " << output_length; + OLA_INFO << "Unable to create output buffer of required length: " + << output_length; return; } @@ -912,24 +915,24 @@ void SPIOutput::IndividualWS2812bControl(const DmxBuffer &buffer) { uint8_t b = buffer.Get(offset + 2); uint8_t low = 0, mid = 0, high = 0; - WS2812bByteMapper(g, low, mid, high); + WS2812bByteMapper(g, &low, &mid, &high); output[i * WS2812B_SPI_BYTES_PER_PIXEL] = low; output[i * WS2812B_SPI_BYTES_PER_PIXEL + 1] = mid; output[i * WS2812B_SPI_BYTES_PER_PIXEL + 2] = high; - WS2812bByteMapper(r, low, mid, high); + WS2812bByteMapper(r, &low, &mid, &high); output[i * WS2812B_SPI_BYTES_PER_PIXEL + 3] = low; output[i * WS2812B_SPI_BYTES_PER_PIXEL + 4] = mid; output[i * WS2812B_SPI_BYTES_PER_PIXEL + 5] = high; - WS2812bByteMapper(b, low, mid, high); + WS2812bByteMapper(b, &low, &mid, &high); output[i * WS2812B_SPI_BYTES_PER_PIXEL + 6] = low; output[i * WS2812B_SPI_BYTES_PER_PIXEL + 7] = mid; output[i * WS2812B_SPI_BYTES_PER_PIXEL + 8] = high; } // write output back... - m_backend->Commit(m_output_number); + m_backend->Commit(m_output_number); } void SPIOutput::CombinedWS2812bControl(const DmxBuffer &buffer) { @@ -942,10 +945,12 @@ void SPIOutput::CombinedWS2812bControl(const DmxBuffer &buffer) { // We always check out the entire string length, even if we only have data // for part of it - const unsigned int output_length = m_pixel_count * WS2812B_SPI_BYTES_PER_PIXEL; + const unsigned int output_length = m_pixel_count + * WS2812B_SPI_BYTES_PER_PIXEL; uint8_t *output = m_backend->Checkout(m_output_number, output_length); if (!output) { - OLA_INFO << "Unable to create output buffer of required length: " << output_length; + OLA_INFO << "Unable to create output buffer of required length: " + << output_length; return; } @@ -958,17 +963,17 @@ void SPIOutput::CombinedWS2812bControl(const DmxBuffer &buffer) { // create Pixel Data uint8_t pixel_data[WS2812B_SPI_BYTES_PER_PIXEL]; - WS2812bByteMapper(g, low, mid, high); + WS2812bByteMapper(g, &low, &mid, &high); pixel_data[0] = low; pixel_data[1] = mid; pixel_data[2] = high; - WS2812bByteMapper(r, low, mid, high); + WS2812bByteMapper(r, &low, &mid, &high); pixel_data[3] = low; pixel_data[4] = mid; pixel_data[5] = high; - WS2812bByteMapper(b, low, mid, high); + WS2812bByteMapper(b, &low, &mid, &high); pixel_data[6] = low; pixel_data[7] = mid; pixel_data[8] = high; @@ -983,13 +988,16 @@ void SPIOutput::CombinedWS2812bControl(const DmxBuffer &buffer) { m_backend->Commit(m_output_number); } -void SPIOutput::WS2812bByteMapper(uint8_t input, uint8_t &low, uint8_t &mid, uint8_t &high) -{ - low = 0x24 | ((input & 0x1) << 1) | ((input & 0x2) << 3) | ((input & 0x4) << 5); - mid = 0x49 | ((input & 0x8) >> 1) | ((input & 0x10) << 1); - high = 0x92 | ((input & 0x20) >> 5) | ((input & 0x40) >> 3) | ((input & 0x80) >> 1); +void SPIOutput::WS2812bByteMapper(uint8_t input, + uint8_t *low, uint8_t *mid, uint8_t *high) { + *low = 0x24 | ((input & 0x1) << 1) | ((input & 0x2) << 3) + | ((input & 0x4) << 5); + *mid = 0x49 | ((input & 0x8) >> 1) | ((input & 0x10) << 1); + *high = 0x92 | ((input & 0x20) >> 5) | ((input & 0x40) >> 3) + | ((input & 0x80) >> 1); } + RDMResponse *SPIOutput::GetDeviceInfo(const RDMRequest *request) { return ResponderHelper::GetDeviceInfo( request, ola::rdm::OLA_SPI_DEVICE_MODEL, diff --git a/plugins/spi/SPIOutput.h b/plugins/spi/SPIOutput.h index 88c47bb445..b6e7f95a4d 100644 --- a/plugins/spi/SPIOutput.h +++ b/plugins/spi/SPIOutput.h @@ -204,7 +204,8 @@ class SPIOutput: public ola::rdm::DiscoverableRDMControllerInterface { uint8_t P9813CreateFlag(uint8_t red, uint8_t green, uint8_t blue); static uint8_t CalculateAPA102LatchBytes(uint16_t pixel_count); static uint8_t CalculateAPA102PixelBrightness(uint8_t brightness); - void WS2812bByteMapper(uint8_t input, uint8_t &low, uint8_t &mid, uint8_t &high); + void WS2812bByteMapper(uint8_t input, + uint8_t *low, uint8_t *mid, uint8_t *high); static const uint8_t SPI_MODE; static const uint8_t SPI_BITS_PER_WORD; From 7e3f8aff533726524402eb4f77e204ce57056186 Mon Sep 17 00:00:00 2001 From: Ph0N37Ic5 Date: Sat, 24 Feb 2018 22:46:23 +0100 Subject: [PATCH 10/17] Fished out a small copy bug, and added a comment. --- plugins/spi/SPIOutput.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/spi/SPIOutput.cpp b/plugins/spi/SPIOutput.cpp index 493b2b66bf..4357e80bdd 100644 --- a/plugins/spi/SPIOutput.cpp +++ b/plugins/spi/SPIOutput.cpp @@ -271,7 +271,7 @@ SPIOutput::SPIOutput(const UID &uid, SPIBackendInterface *backend, personalities.begin() + PERS_WS2812B_COMBINED - 1, Personality(WS2812B_SLOTS_PER_PIXEL, "WS2812b Combined Control", - sdc_irgb_combined)); + sdc_rgb_combined)); m_personality_collection.reset(new PersonalityCollection(personalities)); @@ -988,6 +988,14 @@ void SPIOutput::CombinedWS2812bControl(const DmxBuffer &buffer) { m_backend->Commit(m_output_number); } +/* + * Converting to WS2811/12b format. + * + * The format sends each bit with a leading 1 and a trailing 0. + * This function spaces out the bits of a byte and inserts them into a + * hexadecimal version (0x924924) of the octal 44444444, ending up with + * three bytes of information per byte input. + */ void SPIOutput::WS2812bByteMapper(uint8_t input, uint8_t *low, uint8_t *mid, uint8_t *high) { *low = 0x24 | ((input & 0x1) << 1) | ((input & 0x2) << 3) From 9f0f5be4616e71e6c21e9ab785f30602c3c6dbc7 Mon Sep 17 00:00:00 2001 From: Ph0N37Ic5 Date: Sat, 24 Feb 2018 23:45:03 +0100 Subject: [PATCH 11/17] Scriptkiddied in some tests, hopefully they work. --- plugins/spi/SPIOutputTest.cpp | 181 ++++++++++++++++++++++++++++++++-- 1 file changed, 175 insertions(+), 6 deletions(-) diff --git a/plugins/spi/SPIOutputTest.cpp b/plugins/spi/SPIOutputTest.cpp index e3c76c477d..1bc8e0385c 100644 --- a/plugins/spi/SPIOutputTest.cpp +++ b/plugins/spi/SPIOutputTest.cpp @@ -49,6 +49,8 @@ class SPIOutputTest: public CppUnit::TestFixture { CPPUNIT_TEST(testCombinedAPA102Control); CPPUNIT_TEST(testIndividualAPA102ControlPixelBrightness); CPPUNIT_TEST(testCombinedAPA102ControlPixelBrightness); + CPPUNIT_TEST(testIndividualWS2812bControl); + CPPUNIT_TEST(testCombinedWS2812bControl); CPPUNIT_TEST_SUITE_END(); public: @@ -69,6 +71,8 @@ class SPIOutputTest: public CppUnit::TestFixture { void testCombinedAPA102Control(); void testIndividualAPA102ControlPixelBrightness(); void testCombinedAPA102ControlPixelBrightness(); + void testIndividualWS2812bControl(); + void testCombinedWS2812bControl(); private: UID m_uid; @@ -1169,12 +1173,12 @@ void SPIOutputTest::testIndividualWS2812bControl() { // get fake SPI data stream data = backend.GetData(0, &length); // this is the expected spi data stream: - const uint8_t EXPECTED1[] = { 0x92, 0x4D, 0x34, //Pixel 1 Green - 0x92, 0x49, 0x26, //Pixel 1 Red - 0x9B, 0x49, 0xA4, //Pixel 1 Blue - 0x92, 0x49, 0x24, //Pixel 2 Green - 0x92, 0x49, 0x24, //Pixel 2 Red - 0x92, 0x49, 0x24 //Pixel 2 Blue + const uint8_t EXPECTED1[] = { 0x92, 0x4D, 0x34, //Pixel 1 Green (10) + 0x92, 0x49, 0x26, //Pixel 1 Red (1) + 0x9B, 0x49, 0xA4, //Pixel 1 Blue (100) + 0x92, 0x49, 0x24, //Pixel 2 Green (0) + 0x92, 0x49, 0x24, //Pixel 2 Red (0) + 0x92, 0x49, 0x24 //Pixel 2 Blue (0) }; // check for Equality OLA_ASSERT_DATA_EQUALS(EXPECTED1, arraysize(EXPECTED1), data, length); @@ -1245,4 +1249,169 @@ void SPIOutputTest::testIndividualWS2812bControl() { OLA_ASSERT_EQ(reinterpret_cast(NULL), backend.GetData(1, &length)); OLA_ASSERT_EQ(0u, backend.Writes(1)); + + // test7 + // test for multiple ports + // StartFrame is only allowed on first port. + SPIOutput::Options option1(1, "second SPI Device"); + // setup pixel_count to 2 (enough to test all cases) + option1.pixel_count = 2; + // setup SPIOutput + SPIOutput output1(m_uid, &backend, option1); + // set personality + output1.SetPersonality(this_test_personality); + // setup some 'DMX' data + buffer.SetFromString("1, 10, 100"); + // simulate incoming data + output1.WriteDMX(buffer); + // get fake SPI data stream + data = backend.GetData(1, &length); + // this is the expected spi data stream: + // StartFrame is missing --> port is >0 ! + const uint8_t EXPECTED7[] = { 0x92, 0x4D, 0x34, //Pixel 1 Green (10) + 0x92, 0x49, 0x26, //Pixel 1 Red (1) + 0x9B, 0x49, 0xA4, //Pixel 1 Blue (100) + 0x92, 0x49, 0x24, //Pixel 2 Green (0) + 0x92, 0x49, 0x24, //Pixel 2 Red (0) + 0x92, 0x49, 0x24 //Pixel 2 Blue (0) + }; + // check for Equality + OLA_ASSERT_DATA_EQUALS(EXPECTED7, arraysize(EXPECTED7), data, length); + // check if the output writes are 1 + OLA_ASSERT_EQ(1u, backend.Writes(1)); } + +void SPIOutputTest::testCombinedWS2812bControl() { + const uint16_t this_test_personality = SPIOutput::PERS_WS2812B_COMBINED; + // setup Backend + FakeSPIBackend backend(2); + SPIOutput::Options options(0, "Test SPI Device"); + // setup pixel_count to 2 (enough to test all cases) + options.pixel_count = 2; + // setup SPIOutput + SPIOutput output(m_uid, &backend, options); + // set personality to Combined WS2812b + output.SetPersonality(this_test_personality); + + // simulate incoming dmx data with this buffer + DmxBuffer buffer; + // setup an pointer to the returned data (the fake SPI data stream) + unsigned int length = 0; + const uint8_t *data = NULL; + + // test1 + // setup some 'DMX' data + buffer.SetFromString("1, 10, 100"); + // simulate incoming data + output.WriteDMX(buffer); + // get fake SPI data stream + data = backend.GetData(0, &length); + // this is the expected spi data stream: + const uint8_t EXPECTED1[] = { 0x92, 0x4D, 0x34, //Pixel 1 Green (10) + 0x92, 0x49, 0x26, //Pixel 1 Red (1) + 0x9B, 0x49, 0xA4, //Pixel 1 Blue (100) + 0x92, 0x4D, 0x34, //Pixel 2 Green (10) + 0x92, 0x49, 0x26, //Pixel 2 Red (1) + 0x9B, 0x49, 0xA4 //Pixel 2 Blue (100) + }; + // check for Equality + OLA_ASSERT_DATA_EQUALS(EXPECTED1, arraysize(EXPECTED1), data, length); + // check if the output writes are 1 + OLA_ASSERT_EQ(1u, backend.Writes(0)); + + // test2 + buffer.SetFromString("255,128,0,10,20,30"); + output.WriteDMX(buffer); + data = backend.GetData(0, &length); + const uint8_t EXPECTED2[] = { 0xD2, 0x49, 0x24, //Pixel 1 Green (128) + 0xDB, 0x6D, 0xB6, //Pixel 1 Red (255) + 0x92, 0x49, 0x24, //Pixel 1 Blue (0) + 0xD2, 0x49, 0x24, //Pixel 2 Green (128) + 0xDB, 0x6D, 0xB6, //Pixel 2 Red (255) + 0x92, 0x49, 0x24 //Pixel 2 Blue (0) + }; + OLA_ASSERT_DATA_EQUALS(EXPECTED2, arraysize(EXPECTED2), data, length); + OLA_ASSERT_EQ(2u, backend.Writes(0)); + + // test3 + // test what happens when only new data for the first leds is available. + // later data should be not modified so for pixel2 data set in test2 is valid + buffer.SetFromString("34,56,78"); + output.WriteDMX(buffer); + data = backend.GetData(0, &length); + const uint8_t EXPECTED3[] = { 0x93, 0x6D, 0x24, //Pixel 1 Green (56) + 0x93, 0x49, 0x34, //Pixel 1 Red (34) + 0x9A, 0x4D, 0xB4, //Pixel 1 Blue (78) + 0x93, 0x6D, 0x24, //Pixel 2 Green (56) + 0x93, 0x49, 0x34, //Pixel 2 Red (34) + 0x9A, 0x4D, 0xB4 //Pixel 2 Blue (78) + }; + OLA_ASSERT_DATA_EQUALS(EXPECTED3, arraysize(EXPECTED3), data, length); + OLA_ASSERT_EQ(3u, backend.Writes(0)); + + // test4 + // tests what happens if fewer then needed color information are received + buffer.SetFromString("7, 9"); + output.WriteDMX(buffer); + data = backend.GetData(0, &length); + // check that the returns are the same as test3 (nothing changed) + OLA_ASSERT_DATA_EQUALS(EXPECTED3, arraysize(EXPECTED3), data, length); + OLA_ASSERT_EQ(3u, backend.Writes(0)); + + // test5 + // test with changed StartAddress + // set StartAddress + output.SetStartAddress(3); + // values 1 & 2 should not be visible in SPI data stream + buffer.SetFromString("1,2,3,4,5,6,7,8"); + output.WriteDMX(buffer); + data = backend.GetData(0, &length); + const uint8_t EXPECTED5[] = { 0x92, 0x49, 0xA4, //Pixel 1 Green (4) + 0x92, 0x49, 0x36, //Pixel 1 Red (3) + 0x92, 0x49, 0xA6, //Pixel 1 Blue (5) + 0x92, 0x49, 0xA4, //Pixel 2 Green (4) + 0x92, 0x49, 0x36, //Pixel 2 Red (3) + 0x92, 0x49, 0xA6 //Pixel 2 Blue (5) + }; + OLA_ASSERT_DATA_EQUALS(EXPECTED5, arraysize(EXPECTED5), data, length); + OLA_ASSERT_EQ(4u, backend.Writes(0)); + // change StartAddress back to default + output.SetStartAddress(1); + + // test6 + // Check nothing changed on the other output. + OLA_ASSERT_EQ(reinterpret_cast(NULL), + backend.GetData(1, &length)); + OLA_ASSERT_EQ(0u, backend.Writes(1)); + + // test7 + // test for multiple ports + // StartFrame is only allowed on first port. + SPIOutput::Options option1(1, "second SPI Device"); + // setup pixel_count to 2 (enough to test all cases) + option1.pixel_count = 2; + // setup SPIOutput + SPIOutput output1(m_uid, &backend, option1); + // set personality + output1.SetPersonality(this_test_personality); + // setup some 'DMX' data + buffer.SetFromString("1, 10, 100"); + // simulate incoming data + output1.WriteDMX(buffer); + // get fake SPI data stream + data = backend.GetData(1, &length); + // this is the expected spi data stream: + // StartFrame is missing --> port is >0 ! + const uint8_t EXPECTED7[] = { 0x92, 0x4D, 0x34, //Pixel 1 Green (10) + 0x92, 0x49, 0x26, //Pixel 1 Red (1) + 0x9B, 0x49, 0xA4, //Pixel 1 Blue (100) + 0x92, 0x4D, 0x34, //Pixel 2 Green (10) + 0x92, 0x49, 0x26, //Pixel 2 Red (1) + 0x9B, 0x49, 0xA4 //Pixel 2 Blue (100) + }; + // check for Equality + OLA_ASSERT_DATA_EQUALS(EXPECTED7, arraysize(EXPECTED7), data, length); + // check if the output writes are 1 + OLA_ASSERT_EQ(1u, backend.Writes(1)); +} + From c5116981649bb2aff5605a29da1c32ab270151cf Mon Sep 17 00:00:00 2001 From: Ph0N37Ic5 Date: Sun, 25 Feb 2018 00:15:28 +0100 Subject: [PATCH 12/17] Beautification, and adding 2 calls to InternalWriteDMX --- plugins/spi/SPIOutput.cpp | 11 ++++++++--- plugins/spi/SPIOutput.h | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/plugins/spi/SPIOutput.cpp b/plugins/spi/SPIOutput.cpp index 4357e80bdd..83f818bec6 100644 --- a/plugins/spi/SPIOutput.cpp +++ b/plugins/spi/SPIOutput.cpp @@ -273,7 +273,6 @@ SPIOutput::SPIOutput(const UID &uid, SPIBackendInterface *backend, "WS2812b Combined Control", sdc_rgb_combined)); - m_personality_collection.reset(new PersonalityCollection(personalities)); m_personality_manager.reset(new PersonalityManager( m_personality_collection.get())); @@ -400,6 +399,12 @@ bool SPIOutput::InternalWriteDMX(const DmxBuffer &buffer) { case PERS_APA102_PB_COMBINED: CombinedAPA102ControlPixelBrightness(buffer); break; + case PERS_WS2812B_INDIVIDUAL: + IndividualWS2812bControl(buffer); + break; + case PERS_WS2812B_COMBINED: + CombinedWS2812bControl(buffer); + break; default: break; } @@ -900,7 +905,7 @@ void SPIOutput::IndividualWS2812bControl(const DmxBuffer &buffer) { uint8_t *output = m_backend->Checkout(m_output_number, output_length); if (!output) { OLA_INFO << "Unable to create output buffer of required length: " - << output_length; + << output_length; return; } @@ -950,7 +955,7 @@ void SPIOutput::CombinedWS2812bControl(const DmxBuffer &buffer) { uint8_t *output = m_backend->Checkout(m_output_number, output_length); if (!output) { OLA_INFO << "Unable to create output buffer of required length: " - << output_length; + << output_length; return; } diff --git a/plugins/spi/SPIOutput.h b/plugins/spi/SPIOutput.h index b6e7f95a4d..38ff4812d0 100644 --- a/plugins/spi/SPIOutput.h +++ b/plugins/spi/SPIOutput.h @@ -205,7 +205,7 @@ class SPIOutput: public ola::rdm::DiscoverableRDMControllerInterface { static uint8_t CalculateAPA102LatchBytes(uint16_t pixel_count); static uint8_t CalculateAPA102PixelBrightness(uint8_t brightness); void WS2812bByteMapper(uint8_t input, - uint8_t *low, uint8_t *mid, uint8_t *high); + uint8_t *low, uint8_t *mid, uint8_t *high); static const uint8_t SPI_MODE; static const uint8_t SPI_BITS_PER_WORD; From 0c994920397013ea16428a269c2cc2811323d271 Mon Sep 17 00:00:00 2001 From: Ph0N37Ic5 Date: Sun, 25 Feb 2018 00:47:20 +0100 Subject: [PATCH 13/17] The output and the test now agrees. --- plugins/spi/SPIOutput.cpp | 44 +++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/plugins/spi/SPIOutput.cpp b/plugins/spi/SPIOutput.cpp index 83f818bec6..1a26173b09 100644 --- a/plugins/spi/SPIOutput.cpp +++ b/plugins/spi/SPIOutput.cpp @@ -909,31 +909,39 @@ void SPIOutput::IndividualWS2812bControl(const DmxBuffer &buffer) { return; } - const unsigned int length = std::min(m_pixel_count * WS2812B_SLOTS_PER_PIXEL, - buffer.Size() - first_slot); + const unsigned int length = m_pixel_count; - for (unsigned int i = 0; i < length / WS2812B_SLOTS_PER_PIXEL; i++) { + for (unsigned int i = 0; i < length; i++) { // Convert RGB to GRB unsigned int offset = first_slot + i * WS2812B_SLOTS_PER_PIXEL; - uint8_t r = buffer.Get(offset); - uint8_t g = buffer.Get(offset + 1); - uint8_t b = buffer.Get(offset + 2); + + //Get DMX data + uint8_t r = 0; + uint8_t g = 0; + uint8_t b = 0; + if(offset < buffer.Size() - 2) { + r = buffer.Get(offset); + g = buffer.Get(offset + 1); + b = buffer.Get(offset + 2); + } // fill further pixel data only if the pixel data is empty + else if(output[i * WS2812B_SPI_BYTES_PER_PIXEL] != 0) + break; uint8_t low = 0, mid = 0, high = 0; WS2812bByteMapper(g, &low, &mid, &high); - output[i * WS2812B_SPI_BYTES_PER_PIXEL] = low; + output[i * WS2812B_SPI_BYTES_PER_PIXEL] = high; output[i * WS2812B_SPI_BYTES_PER_PIXEL + 1] = mid; - output[i * WS2812B_SPI_BYTES_PER_PIXEL + 2] = high; + output[i * WS2812B_SPI_BYTES_PER_PIXEL + 2] = low; WS2812bByteMapper(r, &low, &mid, &high); - output[i * WS2812B_SPI_BYTES_PER_PIXEL + 3] = low; + output[i * WS2812B_SPI_BYTES_PER_PIXEL + 3] = high; output[i * WS2812B_SPI_BYTES_PER_PIXEL + 4] = mid; - output[i * WS2812B_SPI_BYTES_PER_PIXEL + 5] = high; + output[i * WS2812B_SPI_BYTES_PER_PIXEL + 5] = low; WS2812bByteMapper(b, &low, &mid, &high); - output[i * WS2812B_SPI_BYTES_PER_PIXEL + 6] = low; + output[i * WS2812B_SPI_BYTES_PER_PIXEL + 6] = high; output[i * WS2812B_SPI_BYTES_PER_PIXEL + 7] = mid; - output[i * WS2812B_SPI_BYTES_PER_PIXEL + 8] = high; + output[i * WS2812B_SPI_BYTES_PER_PIXEL + 8] = low; } // write output back... @@ -969,19 +977,19 @@ void SPIOutput::CombinedWS2812bControl(const DmxBuffer &buffer) { uint8_t pixel_data[WS2812B_SPI_BYTES_PER_PIXEL]; WS2812bByteMapper(g, &low, &mid, &high); - pixel_data[0] = low; + pixel_data[0] = high; pixel_data[1] = mid; - pixel_data[2] = high; + pixel_data[2] = low; WS2812bByteMapper(r, &low, &mid, &high); - pixel_data[3] = low; + pixel_data[3] = high; pixel_data[4] = mid; - pixel_data[5] = high; + pixel_data[5] = low; WS2812bByteMapper(b, &low, &mid, &high); - pixel_data[6] = low; + pixel_data[6] = high; pixel_data[7] = mid; - pixel_data[8] = high; + pixel_data[8] = low; // set all pixel to same value for (uint16_t i = 0; i < m_pixel_count; i++) { From c17ef87b7e7b68094034a67894928c9c85c173dc Mon Sep 17 00:00:00 2001 From: Ph0N37Ic5 Date: Sun, 25 Feb 2018 08:00:11 +0100 Subject: [PATCH 14/17] Lint --- plugins/spi/SPIOutput.cpp | 10 +-- plugins/spi/SPIOutputTest.cpp | 128 +++++++++++++++++----------------- 2 files changed, 70 insertions(+), 68 deletions(-) diff --git a/plugins/spi/SPIOutput.cpp b/plugins/spi/SPIOutput.cpp index 1a26173b09..a2d73c1f76 100644 --- a/plugins/spi/SPIOutput.cpp +++ b/plugins/spi/SPIOutput.cpp @@ -915,17 +915,19 @@ void SPIOutput::IndividualWS2812bControl(const DmxBuffer &buffer) { // Convert RGB to GRB unsigned int offset = first_slot + i * WS2812B_SLOTS_PER_PIXEL; - //Get DMX data + // Get DMX data uint8_t r = 0; uint8_t g = 0; uint8_t b = 0; - if(offset < buffer.Size() - 2) { + if ( offset < buffer.Size() - 2 ) { r = buffer.Get(offset); g = buffer.Get(offset + 1); b = buffer.Get(offset + 2); - } // fill further pixel data only if the pixel data is empty - else if(output[i * WS2812B_SPI_BYTES_PER_PIXEL] != 0) + + // fill further pixel data only if the pixel data is empty + } else if ( output[i * WS2812B_SPI_BYTES_PER_PIXEL] != 0 ) break; + uint8_t low = 0, mid = 0, high = 0; WS2812bByteMapper(g, &low, &mid, &high); diff --git a/plugins/spi/SPIOutputTest.cpp b/plugins/spi/SPIOutputTest.cpp index 1bc8e0385c..38edda84a1 100644 --- a/plugins/spi/SPIOutputTest.cpp +++ b/plugins/spi/SPIOutputTest.cpp @@ -1173,13 +1173,13 @@ void SPIOutputTest::testIndividualWS2812bControl() { // get fake SPI data stream data = backend.GetData(0, &length); // this is the expected spi data stream: - const uint8_t EXPECTED1[] = { 0x92, 0x4D, 0x34, //Pixel 1 Green (10) - 0x92, 0x49, 0x26, //Pixel 1 Red (1) - 0x9B, 0x49, 0xA4, //Pixel 1 Blue (100) - 0x92, 0x49, 0x24, //Pixel 2 Green (0) - 0x92, 0x49, 0x24, //Pixel 2 Red (0) - 0x92, 0x49, 0x24 //Pixel 2 Blue (0) - }; + const uint8_t EXPECTED1[] = { 0x92, 0x4D, 0x34, // Pixel 1 Green (10) + 0x92, 0x49, 0x26, // Pixel 1 Red (1) + 0x9B, 0x49, 0xA4, // Pixel 1 Blue (100) + 0x92, 0x49, 0x24, // Pixel 2 Green (0) + 0x92, 0x49, 0x24, // Pixel 2 Red (0) + 0x92, 0x49, 0x24 // Pixel 2 Blue (0) + }; // check for Equality OLA_ASSERT_DATA_EQUALS(EXPECTED1, arraysize(EXPECTED1), data, length); // check if the output writes are 1 @@ -1189,12 +1189,12 @@ void SPIOutputTest::testIndividualWS2812bControl() { buffer.SetFromString("255,128,0,10,20,30"); output.WriteDMX(buffer); data = backend.GetData(0, &length); - const uint8_t EXPECTED2[] = { 0xD2, 0x49, 0x24, //Pixel 1 Green (128) - 0xDB, 0x6D, 0xB6, //Pixel 1 Red (255) - 0x92, 0x49, 0x24, //Pixel 1 Blue (0) - 0x92, 0x69, 0xA4, //Pixel 2 Green (20) - 0x92, 0x4D, 0x34, //Pixel 2 Red (10) - 0x92, 0x6D, 0xB4 //Pixel 2 Blue (30) + const uint8_t EXPECTED2[] = { 0xD2, 0x49, 0x24, // Pixel 1 Green (128) + 0xDB, 0x6D, 0xB6, // Pixel 1 Red (255) + 0x92, 0x49, 0x24, // Pixel 1 Blue (0) + 0x92, 0x69, 0xA4, // Pixel 2 Green (20) + 0x92, 0x4D, 0x34, // Pixel 2 Red (10) + 0x92, 0x6D, 0xB4 // Pixel 2 Blue (30) }; OLA_ASSERT_DATA_EQUALS(EXPECTED2, arraysize(EXPECTED2), data, length); OLA_ASSERT_EQ(2u, backend.Writes(0)); @@ -1205,12 +1205,12 @@ void SPIOutputTest::testIndividualWS2812bControl() { buffer.SetFromString("34,56,78"); output.WriteDMX(buffer); data = backend.GetData(0, &length); - const uint8_t EXPECTED3[] = { 0x93, 0x6D, 0x24, //Pixel 1 Green (56) - 0x93, 0x49, 0x34, //Pixel 1 Red (34) - 0x9A, 0x4D, 0xB4, //Pixel 1 Blue (78) - 0x92, 0x69, 0xA4, //Pixel 2 Green (20) - 0x92, 0x4D, 0x34, //Pixel 2 Red (10) - 0x92, 0x6D, 0xB4 //Pixel 2 Blue (30) + const uint8_t EXPECTED3[] = { 0x93, 0x6D, 0x24, // Pixel 1 Green (56) + 0x93, 0x49, 0x34, // Pixel 1 Red (34) + 0x9A, 0x4D, 0xB4, // Pixel 1 Blue (78) + 0x92, 0x69, 0xA4, // Pixel 2 Green (20) + 0x92, 0x4D, 0x34, // Pixel 2 Red (10) + 0x92, 0x6D, 0xB4 // Pixel 2 Blue (30) }; OLA_ASSERT_DATA_EQUALS(EXPECTED3, arraysize(EXPECTED3), data, length); OLA_ASSERT_EQ(3u, backend.Writes(0)); @@ -1232,12 +1232,12 @@ void SPIOutputTest::testIndividualWS2812bControl() { buffer.SetFromString("1,2,3,4,5,6,7,8"); output.WriteDMX(buffer); data = backend.GetData(0, &length); - const uint8_t EXPECTED5[] = { 0x92, 0x49, 0xA4, //Pixel 1 Green (4) - 0x92, 0x49, 0x36, //Pixel 1 Red (3) - 0x92, 0x49, 0xA6, //Pixel 1 Blue (5) - 0x92, 0x49, 0xB6, //Pixel 2 Green (7) - 0x92, 0x49, 0xB4, //Pixel 2 Red (6) - 0x92, 0x4D, 0x24 //Pixel 2 Blue (8) + const uint8_t EXPECTED5[] = { 0x92, 0x49, 0xA4, // Pixel 1 Green (4) + 0x92, 0x49, 0x36, // Pixel 1 Red (3) + 0x92, 0x49, 0xA6, // Pixel 1 Blue (5) + 0x92, 0x49, 0xB6, // Pixel 2 Green (7) + 0x92, 0x49, 0xB4, // Pixel 2 Red (6) + 0x92, 0x4D, 0x24 // Pixel 2 Blue (8) }; OLA_ASSERT_DATA_EQUALS(EXPECTED5, arraysize(EXPECTED5), data, length); OLA_ASSERT_EQ(4u, backend.Writes(0)); @@ -1268,13 +1268,13 @@ void SPIOutputTest::testIndividualWS2812bControl() { data = backend.GetData(1, &length); // this is the expected spi data stream: // StartFrame is missing --> port is >0 ! - const uint8_t EXPECTED7[] = { 0x92, 0x4D, 0x34, //Pixel 1 Green (10) - 0x92, 0x49, 0x26, //Pixel 1 Red (1) - 0x9B, 0x49, 0xA4, //Pixel 1 Blue (100) - 0x92, 0x49, 0x24, //Pixel 2 Green (0) - 0x92, 0x49, 0x24, //Pixel 2 Red (0) - 0x92, 0x49, 0x24 //Pixel 2 Blue (0) - }; + const uint8_t EXPECTED7[] = { 0x92, 0x4D, 0x34, // Pixel 1 Green (10) + 0x92, 0x49, 0x26, // Pixel 1 Red (1) + 0x9B, 0x49, 0xA4, // Pixel 1 Blue (100) + 0x92, 0x49, 0x24, // Pixel 2 Green (0) + 0x92, 0x49, 0x24, // Pixel 2 Red (0) + 0x92, 0x49, 0x24 // Pixel 2 Blue (0) + }; // check for Equality OLA_ASSERT_DATA_EQUALS(EXPECTED7, arraysize(EXPECTED7), data, length); // check if the output writes are 1 @@ -1307,13 +1307,13 @@ void SPIOutputTest::testCombinedWS2812bControl() { // get fake SPI data stream data = backend.GetData(0, &length); // this is the expected spi data stream: - const uint8_t EXPECTED1[] = { 0x92, 0x4D, 0x34, //Pixel 1 Green (10) - 0x92, 0x49, 0x26, //Pixel 1 Red (1) - 0x9B, 0x49, 0xA4, //Pixel 1 Blue (100) - 0x92, 0x4D, 0x34, //Pixel 2 Green (10) - 0x92, 0x49, 0x26, //Pixel 2 Red (1) - 0x9B, 0x49, 0xA4 //Pixel 2 Blue (100) - }; + const uint8_t EXPECTED1[] = { 0x92, 0x4D, 0x34, // Pixel 1 Green (10) + 0x92, 0x49, 0x26, // Pixel 1 Red (1) + 0x9B, 0x49, 0xA4, // Pixel 1 Blue (100) + 0x92, 0x4D, 0x34, // Pixel 2 Green (10) + 0x92, 0x49, 0x26, // Pixel 2 Red (1) + 0x9B, 0x49, 0xA4 // Pixel 2 Blue (100) + }; // check for Equality OLA_ASSERT_DATA_EQUALS(EXPECTED1, arraysize(EXPECTED1), data, length); // check if the output writes are 1 @@ -1323,12 +1323,12 @@ void SPIOutputTest::testCombinedWS2812bControl() { buffer.SetFromString("255,128,0,10,20,30"); output.WriteDMX(buffer); data = backend.GetData(0, &length); - const uint8_t EXPECTED2[] = { 0xD2, 0x49, 0x24, //Pixel 1 Green (128) - 0xDB, 0x6D, 0xB6, //Pixel 1 Red (255) - 0x92, 0x49, 0x24, //Pixel 1 Blue (0) - 0xD2, 0x49, 0x24, //Pixel 2 Green (128) - 0xDB, 0x6D, 0xB6, //Pixel 2 Red (255) - 0x92, 0x49, 0x24 //Pixel 2 Blue (0) + const uint8_t EXPECTED2[] = { 0xD2, 0x49, 0x24, // Pixel 1 Green (128) + 0xDB, 0x6D, 0xB6, // Pixel 1 Red (255) + 0x92, 0x49, 0x24, // Pixel 1 Blue (0) + 0xD2, 0x49, 0x24, // Pixel 2 Green (128) + 0xDB, 0x6D, 0xB6, // Pixel 2 Red (255) + 0x92, 0x49, 0x24 // Pixel 2 Blue (0) }; OLA_ASSERT_DATA_EQUALS(EXPECTED2, arraysize(EXPECTED2), data, length); OLA_ASSERT_EQ(2u, backend.Writes(0)); @@ -1339,12 +1339,12 @@ void SPIOutputTest::testCombinedWS2812bControl() { buffer.SetFromString("34,56,78"); output.WriteDMX(buffer); data = backend.GetData(0, &length); - const uint8_t EXPECTED3[] = { 0x93, 0x6D, 0x24, //Pixel 1 Green (56) - 0x93, 0x49, 0x34, //Pixel 1 Red (34) - 0x9A, 0x4D, 0xB4, //Pixel 1 Blue (78) - 0x93, 0x6D, 0x24, //Pixel 2 Green (56) - 0x93, 0x49, 0x34, //Pixel 2 Red (34) - 0x9A, 0x4D, 0xB4 //Pixel 2 Blue (78) + const uint8_t EXPECTED3[] = { 0x93, 0x6D, 0x24, // Pixel 1 Green (56) + 0x93, 0x49, 0x34, // Pixel 1 Red (34) + 0x9A, 0x4D, 0xB4, // Pixel 1 Blue (78) + 0x93, 0x6D, 0x24, // Pixel 2 Green (56) + 0x93, 0x49, 0x34, // Pixel 2 Red (34) + 0x9A, 0x4D, 0xB4 // Pixel 2 Blue (78) }; OLA_ASSERT_DATA_EQUALS(EXPECTED3, arraysize(EXPECTED3), data, length); OLA_ASSERT_EQ(3u, backend.Writes(0)); @@ -1366,12 +1366,12 @@ void SPIOutputTest::testCombinedWS2812bControl() { buffer.SetFromString("1,2,3,4,5,6,7,8"); output.WriteDMX(buffer); data = backend.GetData(0, &length); - const uint8_t EXPECTED5[] = { 0x92, 0x49, 0xA4, //Pixel 1 Green (4) - 0x92, 0x49, 0x36, //Pixel 1 Red (3) - 0x92, 0x49, 0xA6, //Pixel 1 Blue (5) - 0x92, 0x49, 0xA4, //Pixel 2 Green (4) - 0x92, 0x49, 0x36, //Pixel 2 Red (3) - 0x92, 0x49, 0xA6 //Pixel 2 Blue (5) + const uint8_t EXPECTED5[] = { 0x92, 0x49, 0xA4, // Pixel 1 Green (4) + 0x92, 0x49, 0x36, // Pixel 1 Red (3) + 0x92, 0x49, 0xA6, // Pixel 1 Blue (5) + 0x92, 0x49, 0xA4, // Pixel 2 Green (4) + 0x92, 0x49, 0x36, // Pixel 2 Red (3) + 0x92, 0x49, 0xA6 // Pixel 2 Blue (5) }; OLA_ASSERT_DATA_EQUALS(EXPECTED5, arraysize(EXPECTED5), data, length); OLA_ASSERT_EQ(4u, backend.Writes(0)); @@ -1402,13 +1402,13 @@ void SPIOutputTest::testCombinedWS2812bControl() { data = backend.GetData(1, &length); // this is the expected spi data stream: // StartFrame is missing --> port is >0 ! - const uint8_t EXPECTED7[] = { 0x92, 0x4D, 0x34, //Pixel 1 Green (10) - 0x92, 0x49, 0x26, //Pixel 1 Red (1) - 0x9B, 0x49, 0xA4, //Pixel 1 Blue (100) - 0x92, 0x4D, 0x34, //Pixel 2 Green (10) - 0x92, 0x49, 0x26, //Pixel 2 Red (1) - 0x9B, 0x49, 0xA4 //Pixel 2 Blue (100) - }; + const uint8_t EXPECTED7[] = { 0x92, 0x4D, 0x34, // Pixel 1 Green (10) + 0x92, 0x49, 0x26, // Pixel 1 Red (1) + 0x9B, 0x49, 0xA4, // Pixel 1 Blue (100) + 0x92, 0x4D, 0x34, // Pixel 2 Green (10) + 0x92, 0x49, 0x26, // Pixel 2 Red (1) + 0x9B, 0x49, 0xA4 // Pixel 2 Blue (100) + }; // check for Equality OLA_ASSERT_DATA_EQUALS(EXPECTED7, arraysize(EXPECTED7), data, length); // check if the output writes are 1 From 262909096eacce53e73df27b6695082a2b76884f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20Gr=C3=B8nvold?= Date: Sun, 25 Feb 2018 08:43:31 +0100 Subject: [PATCH 15/17] Lint lint. --- plugins/spi/SPIOutput.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/spi/SPIOutput.cpp b/plugins/spi/SPIOutput.cpp index a2d73c1f76..971af4ebf0 100644 --- a/plugins/spi/SPIOutput.cpp +++ b/plugins/spi/SPIOutput.cpp @@ -923,10 +923,10 @@ void SPIOutput::IndividualWS2812bControl(const DmxBuffer &buffer) { r = buffer.Get(offset); g = buffer.Get(offset + 1); b = buffer.Get(offset + 2); - - // fill further pixel data only if the pixel data is empty - } else if ( output[i * WS2812B_SPI_BYTES_PER_PIXEL] != 0 ) + } else if ( output[i * WS2812B_SPI_BYTES_PER_PIXEL] != 0 ) { + // fill further pixel data only if the pixel data is empty break; + } uint8_t low = 0, mid = 0, high = 0; From d510fece18d85ef82bac08313b24e8c988584b75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20Gr=C3=B8nvold?= Date: Mon, 26 Feb 2018 00:10:38 +0100 Subject: [PATCH 16/17] The lint that lint didn't find. --- plugins/spi/SPIOutput.cpp | 4 ++-- plugins/spi/SPIOutputTest.cpp | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/spi/SPIOutput.cpp b/plugins/spi/SPIOutput.cpp index 971af4ebf0..08c57e7d42 100644 --- a/plugins/spi/SPIOutput.cpp +++ b/plugins/spi/SPIOutput.cpp @@ -919,11 +919,11 @@ void SPIOutput::IndividualWS2812bControl(const DmxBuffer &buffer) { uint8_t r = 0; uint8_t g = 0; uint8_t b = 0; - if ( offset < buffer.Size() - 2 ) { + if (offset < buffer.Size() - 2) { r = buffer.Get(offset); g = buffer.Get(offset + 1); b = buffer.Get(offset + 2); - } else if ( output[i * WS2812B_SPI_BYTES_PER_PIXEL] != 0 ) { + } else if (output[i * WS2812B_SPI_BYTES_PER_PIXEL] != 0) { // fill further pixel data only if the pixel data is empty break; } diff --git a/plugins/spi/SPIOutputTest.cpp b/plugins/spi/SPIOutputTest.cpp index 38edda84a1..a88cf598fc 100644 --- a/plugins/spi/SPIOutputTest.cpp +++ b/plugins/spi/SPIOutputTest.cpp @@ -1179,7 +1179,7 @@ void SPIOutputTest::testIndividualWS2812bControl() { 0x92, 0x49, 0x24, // Pixel 2 Green (0) 0x92, 0x49, 0x24, // Pixel 2 Red (0) 0x92, 0x49, 0x24 // Pixel 2 Blue (0) - }; + }; // check for Equality OLA_ASSERT_DATA_EQUALS(EXPECTED1, arraysize(EXPECTED1), data, length); // check if the output writes are 1 @@ -1274,7 +1274,7 @@ void SPIOutputTest::testIndividualWS2812bControl() { 0x92, 0x49, 0x24, // Pixel 2 Green (0) 0x92, 0x49, 0x24, // Pixel 2 Red (0) 0x92, 0x49, 0x24 // Pixel 2 Blue (0) - }; + }; // check for Equality OLA_ASSERT_DATA_EQUALS(EXPECTED7, arraysize(EXPECTED7), data, length); // check if the output writes are 1 @@ -1313,7 +1313,7 @@ void SPIOutputTest::testCombinedWS2812bControl() { 0x92, 0x4D, 0x34, // Pixel 2 Green (10) 0x92, 0x49, 0x26, // Pixel 2 Red (1) 0x9B, 0x49, 0xA4 // Pixel 2 Blue (100) - }; + }; // check for Equality OLA_ASSERT_DATA_EQUALS(EXPECTED1, arraysize(EXPECTED1), data, length); // check if the output writes are 1 @@ -1408,7 +1408,7 @@ void SPIOutputTest::testCombinedWS2812bControl() { 0x92, 0x4D, 0x34, // Pixel 2 Green (10) 0x92, 0x49, 0x26, // Pixel 2 Red (1) 0x9B, 0x49, 0xA4 // Pixel 2 Blue (100) - }; + }; // check for Equality OLA_ASSERT_DATA_EQUALS(EXPECTED7, arraysize(EXPECTED7), data, length); // check if the output writes are 1 From d90feca935fc5c03f58fdc95033e5c6833b35c81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20Gr=C3=B8nvold?= Date: Tue, 27 Feb 2018 18:30:02 +0100 Subject: [PATCH 17/17] Added a bit of debugging to the SPIOutput.cpp constructor. Incremented RDM version value. --- plugins/spi/SPIOutput.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/spi/SPIOutput.cpp b/plugins/spi/SPIOutput.cpp index e6fb5e7981..d503450b27 100644 --- a/plugins/spi/SPIOutput.cpp +++ b/plugins/spi/SPIOutput.cpp @@ -245,7 +245,6 @@ SPIOutput::SPIOutput(const UID &uid, SPIBackendInterface *backend, personalities.begin() + PERS_APA102_INDIVIDUAL - 1, Personality(m_pixel_count * APA102_SLOTS_PER_PIXEL, "APA102 Individual Control")); - personalities.insert( personalities.begin() + PERS_APA102_COMBINED - 1, Personality(APA102_SLOTS_PER_PIXEL, @@ -256,7 +255,6 @@ SPIOutput::SPIOutput(const UID &uid, SPIBackendInterface *backend, personalities.begin() + PERS_APA102_PB_INDIVIDUAL - 1, Personality(m_pixel_count * APA102_PB_SLOTS_PER_PIXEL, "APA102 Pixel Brightness Individ.")); - personalities.insert( personalities.begin() + PERS_APA102_PB_COMBINED - 1, Personality(APA102_PB_SLOTS_PER_PIXEL, @@ -267,13 +265,17 @@ SPIOutput::SPIOutput(const UID &uid, SPIBackendInterface *backend, personalities.begin() + PERS_WS2812B_INDIVIDUAL - 1, Personality(m_pixel_count * WS2812B_SLOTS_PER_PIXEL, "WS2812b Individual Control")); - personalities.insert( personalities.begin() + PERS_WS2812B_COMBINED - 1, Personality(WS2812B_SLOTS_PER_PIXEL, "WS2812b Combined Control", sdc_rgb_combined)); + for (uint8_t iter = 0; iter < personalities.size(); iter++) + OLA_DEBUG << "Personality '" << personalities[iter].Description() + << "' added to list of SPI personalities with id: " + << (iter + 1); + m_personality_collection.reset(new PersonalityCollection(personalities)); m_personality_manager.reset(new PersonalityManager( m_personality_collection.get()));