diff --git a/plugins/spi/SPIOutput.cpp b/plugins/spi/SPIOutput.cpp index 988254198d..d503450b27 100644 --- a/plugins/spi/SPIOutput.cpp +++ b/plugins/spi/SPIOutput.cpp @@ -87,11 +87,13 @@ 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::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::APA102_START_FRAME_BYTES = 4; const uint8_t SPIOutput::APA102_LEDFRAME_START_MARK = 0xE0; @@ -243,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, @@ -254,13 +255,27 @@ 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, "APA102 Pixel Brightness Combined", sdc_irgb_combined)); + personalities.insert( + 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())); @@ -387,6 +402,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; } @@ -872,6 +893,135 @@ 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 + << ", 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_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 = m_pixel_count; + + for (unsigned int i = 0; i < length; i++) { + // Convert RGB to GRB + unsigned int offset = first_slot + i * WS2812B_SLOTS_PER_PIXEL; + + // 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); + } 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; + + WS2812bByteMapper(g, &low, &mid, &high); + 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] = low; + + WS2812bByteMapper(r, &low, &mid, &high); + 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] = low; + + WS2812bByteMapper(b, &low, &mid, &high); + 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] = low; + } + + // 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 + * 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; + } + + // 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); + uint8_t low = 0, mid = 0, high = 0; + + // create Pixel Data + uint8_t pixel_data[WS2812B_SPI_BYTES_PER_PIXEL]; + + WS2812bByteMapper(g, &low, &mid, &high); + pixel_data[0] = high; + pixel_data[1] = mid; + pixel_data[2] = low; + + WS2812bByteMapper(r, &low, &mid, &high); + pixel_data[3] = high; + pixel_data[4] = mid; + pixel_data[5] = low; + + WS2812bByteMapper(b, &low, &mid, &high); + pixel_data[6] = high; + pixel_data[7] = mid; + pixel_data[8] = low; + + // set all pixel to same value + for (uint16_t i = 0; i < m_pixel_count; i++) { + memcpy(&output[i * WS2812B_SPI_BYTES_PER_PIXEL], pixel_data, + WS2812B_SPI_BYTES_PER_PIXEL); + } + + // write output back... + 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) + | ((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) { diff --git a/plugins/spi/SPIOutput.h b/plugins/spi/SPIOutput.h index 745e6c7c07..38ff4812d0 100644 --- a/plugins/spi/SPIOutput.h +++ b/plugins/spi/SPIOutput.h @@ -57,6 +57,8 @@ class SPIOutput: public ola::rdm::DiscoverableRDMControllerInterface { PERS_APA102_COMBINED = 8, PERS_APA102_PB_INDIVIDUAL, PERS_APA102_PB_COMBINED, + PERS_WS2812B_INDIVIDUAL, + PERS_WS2812B_COMBINED, }; struct Options { @@ -136,6 +138,8 @@ class SPIOutput: public ola::rdm::DiscoverableRDMControllerInterface { 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); @@ -200,6 +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); static const uint8_t SPI_MODE; static const uint8_t SPI_BITS_PER_WORD; @@ -214,6 +220,8 @@ class SPIOutput: public ola::rdm::DiscoverableRDMControllerInterface { static const uint16_t APA102_SPI_BYTES_PER_PIXEL; static const uint16_t APA102_START_FRAME_BYTES; 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 c39038864c..a88cf598fc 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; @@ -1139,3 +1143,275 @@ 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 (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 + 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)); + + // 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)); +} +