diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f06e6dc8..f48195081 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### ✨ Added +- Support of RGBW in **e1.31** LED devices. - HTTPS support for homeassistant LED devices (#1886) - Hue Bridge - Use https and certificates for all API calls, support Bridge Pro (V3) - Hue Bridge - Alternate certificate support @@ -23,8 +24,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add standalone DRM grabber - Avoid queuing on image processing for output ---- - ### 🔧 Changed - Hue Bridge - Wizard updates to support bridge-ids, overall code refactoring diff --git a/assets/webconfig/i18n/en.json b/assets/webconfig/i18n/en.json index 3c14ccbb6..f46e9b5af 100644 --- a/assets/webconfig/i18n/en.json +++ b/assets/webconfig/i18n/en.json @@ -689,6 +689,7 @@ "edt_dev_spec_devices_discovery_inprogress": "Discovery in progress", "edt_dev_spec_dithering_title": "Dithering", "edt_dev_spec_dmaNumber_title": "DMA channel", + "edt_dev_spec_dmx_max_title": "DMX maximum channels supported", "edt_dev_spec_fullBrightnessAtStart_title": "Full brightness at start", "edt_dev_spec_gamma_title": "Gamma", "edt_dev_spec_globalBrightnessControlMaxLevel_title": "Max Current Level", diff --git a/libsrc/leddevice/dev_net/LedDeviceUdpE131.cpp b/libsrc/leddevice/dev_net/LedDeviceUdpE131.cpp index 12b417d99..0ab5b38fc 100644 --- a/libsrc/leddevice/dev_net/LedDeviceUdpE131.cpp +++ b/libsrc/leddevice/dev_net/LedDeviceUdpE131.cpp @@ -39,6 +39,9 @@ const int DMX_MAX = 512; // 512 usable slots LedDeviceUdpE131::LedDeviceUdpE131(const QJsonObject &deviceConfig) : ProviderUdp(deviceConfig) + , _e131_dmx_max(DMX_MAX) + , _whiteAlgorithm(RGBW::WhiteAlgorithm::INVALID) + , _dmxChannelCount(0) { } @@ -59,15 +62,35 @@ bool LedDeviceUdpE131::init(const QJsonObject &deviceConfig) _port = deviceConfig[CONFIG_PORT].toInt(E131_DEFAULT_PORT); _e131_universe = static_cast(deviceConfig["universe"].toInt(1)); + _e131_dmx_max = static_cast(deviceConfig["dmx-max"].toInt(DMX_MAX)); + if (_e131_dmx_max > DMX_MAX) + { + _e131_dmx_max = DMX_MAX; + Warning(_log, "Maximum channels configured [%d] cannot exceed maximum channels defined by the E1.31 protocol. Corrected to %d channels.", _e131_dmx_max, DMX_MAX); + } _e131_source_name = deviceConfig["source-name"].toString("hyperion on "+QHostInfo::localHostName()); QString _json_cid = deviceConfig["cid"].toString(""); + // Initialize white algorithm + QString whiteAlgorithmStr = deviceConfig["whiteAlgorithm"].toString("white_off"); + _whiteAlgorithm = RGBW::stringToWhiteAlgorithm(whiteAlgorithmStr); + if (_whiteAlgorithm == RGBW::WhiteAlgorithm::INVALID) + { + QString errortext = QString("unknown whiteAlgorithm: %1").arg(whiteAlgorithmStr); + this->setInError(errortext); + return false; + } + Debug(_log, "whiteAlgorithm : %s", QSTRING_CSTR(whiteAlgorithmStr)); + _dmxChannelCount = (_whiteAlgorithm == RGBW::WhiteAlgorithm::WHITE_OFF) ? _ledRGBCount : _ledRGBWCount; + _ledBuffer.resize(_dmxChannelCount); + if (_json_cid.isEmpty()) { _e131_cid = QUuid::createUuid(); Debug( _log, "e131 no CID found, generated %s", QSTRING_CSTR(_e131_cid.toString())); return true; } + _e131_cid = QUuid(_json_cid); if ( _e131_cid.isNull() ) { @@ -75,7 +98,7 @@ bool LedDeviceUdpE131::init(const QJsonObject &deviceConfig) return false; } Debug( _log, "e131 CID found, using %s", QSTRING_CSTR(_e131_cid.toString())); - + return true; } @@ -83,7 +106,7 @@ int LedDeviceUdpE131::open() { _isDeviceReady = false; this->setIsRecoverable(true); - + NetUtils::convertMdnsToIp(_log, _hostName); if (ProviderUdp::open() == 0) { @@ -95,75 +118,90 @@ int LedDeviceUdpE131::open() } // populates the headers -void LedDeviceUdpE131::prepare(unsigned this_universe, unsigned this_dmxChannelCount) +void LedDeviceUdpE131::prepare(uint16_t this_universe, uint16_t this_dmxChannelCount) { - memset(e131_packet.raw, 0, sizeof(e131_packet.raw)); + memset(_e131_packet.raw, 0, sizeof(_e131_packet.raw)); /* Root Layer */ - e131_packet.preamble_size = htons(16); - e131_packet.postamble_size = 0; - memcpy (e131_packet.acn_id, _acn_id, 12); - e131_packet.root_flength = htons(0x7000 | (110+this_dmxChannelCount) ); - e131_packet.root_vector = htonl(VECTOR_ROOT_E131_DATA); - memcpy (e131_packet.cid, _e131_cid.toRfc4122().constData() , sizeof(e131_packet.cid) ); - + _e131_packet.frame.preamble_size = htons(16); + _e131_packet.frame.postamble_size = 0; + memcpy (_e131_packet.frame.acn_id, _acn_id, 12); + _e131_packet.frame.root_flength = htons(0x7000 | (110+this_dmxChannelCount) ); + _e131_packet.frame.root_vector = htonl(VECTOR_ROOT_E131_DATA); + memcpy (_e131_packet.frame.cid, _e131_cid.toRfc4122().constData() , sizeof(_e131_packet.frame.cid) ); /* Frame Layer */ - e131_packet.frame_flength = htons(0x7000 | (88+this_dmxChannelCount)); - e131_packet.frame_vector = htonl(VECTOR_E131_DATA_PACKET); - snprintf (e131_packet.source_name, sizeof(e131_packet.source_name), "%s", QSTRING_CSTR(_e131_source_name) ); - e131_packet.priority = 100; - e131_packet.reserved = htons(0); - e131_packet.options = 0; // Bit 7 = Preview_Data + _e131_packet.frame.frame_flength = htons(0x7000 | (88+this_dmxChannelCount)); + _e131_packet.frame.frame_vector = htonl(VECTOR_E131_DATA_PACKET); + snprintf (_e131_packet.frame.source_name, sizeof(_e131_packet.frame.source_name), "%s", QSTRING_CSTR(_e131_source_name) ); + _e131_packet.frame.priority = 100; + _e131_packet.frame.reserved = htons(0); + _e131_packet.frame.sequence_number = 0; + _e131_packet.frame.options = 0; // Bit 7 = Preview_Data // Bit 6 = Stream_Terminated // Bit 5 = Force_Synchronization - e131_packet.universe = htons(this_universe); + _e131_packet.frame.universe = htons(this_universe); /* DMX Layer */ - e131_packet.dmp_flength = htons(0x7000 | (11+this_dmxChannelCount)); - e131_packet.dmp_vector = VECTOR_DMP_SET_PROPERTY; - e131_packet.type = 0xa1; - e131_packet.first_address = htons(0); - e131_packet.address_increment = htons(1); - e131_packet.property_value_count = htons(1+this_dmxChannelCount); - - e131_packet.property_values[0] = 0; // start code + _e131_packet.frame.dmp_flength = htons(0x7000 | (11+this_dmxChannelCount)); + _e131_packet.frame.dmp_vector = VECTOR_DMP_SET_PROPERTY; + _e131_packet.frame.type = 0xa1; + _e131_packet.frame.first_address = htons(0); + _e131_packet.frame.address_increment = htons(1); + _e131_packet.frame.property_value_count = htons(1+this_dmxChannelCount); + + _e131_packet.frame.property_values[0] = 0; // start code } int LedDeviceUdpE131::write(const QVector &ledValues) { - int retVal = 0; - int thisChannelCount = 0; - int dmxChannelCount = _ledRGBCount; - auto rawdata = reinterpret_cast(ledValues.data()); + int retVal = 0; + uint16_t thisChannelCount = 0; + + uint8_t* rawDataPtr = _ledBuffer.data(); + + int currentChannel = 0; + for (const ColorRgb& color : ledValues) + { + if (_whiteAlgorithm == RGBW::WhiteAlgorithm::WHITE_OFF) + { + rawDataPtr[currentChannel++] = color.red; + rawDataPtr[currentChannel++] = color.green; + rawDataPtr[currentChannel++] = color.blue; + } + else + { + RGBW::Rgb_to_Rgbw(color, &_temp_rgbw, _whiteAlgorithm); + rawDataPtr[currentChannel++] = _temp_rgbw.red; + rawDataPtr[currentChannel++] = _temp_rgbw.green; + rawDataPtr[currentChannel++] = _temp_rgbw.blue; + rawDataPtr[currentChannel++] = _temp_rgbw.white; + } + } _e131_seq++; - for (int rawIdx = 0; rawIdx < dmxChannelCount; rawIdx++) + for (auto rawIdx = 0; rawIdx < _dmxChannelCount; rawIdx++) { - if (rawIdx % DMX_MAX == 0) // start of new packet + if (rawIdx % _e131_dmx_max == 0) // start of new packet { - thisChannelCount = (dmxChannelCount - rawIdx < DMX_MAX) ? dmxChannelCount % DMX_MAX : DMX_MAX; -// is this the last packet? ? ^^ last packet : ^^ earlier packets + thisChannelCount = static_cast((_dmxChannelCount - rawIdx < _e131_dmx_max) ? _dmxChannelCount - rawIdx : _e131_dmx_max); + // is this the last packet? ? ^^ last packet : ^^ earlier packets - prepare(_e131_universe + rawIdx / DMX_MAX, thisChannelCount); - e131_packet.sequence_number = _e131_seq; + prepare(static_cast(_e131_universe + rawIdx / _e131_dmx_max), thisChannelCount); + _e131_packet.frame.sequence_number = _e131_seq; } - e131_packet.property_values[1 + rawIdx%DMX_MAX] = rawdata[rawIdx]; + _e131_packet.frame.property_values[1 + rawIdx % _e131_dmx_max] = rawDataPtr[rawIdx]; -// is this the last byte of last packet || last byte of other packets - if ( (rawIdx == dmxChannelCount-1) || (rawIdx %DMX_MAX == DMX_MAX-1) ) + // is this the last byte of last packet || last byte of other packets + if ((rawIdx == _dmxChannelCount - 1) || (rawIdx % _e131_dmx_max == _e131_dmx_max - 1)) { -#undef e131debug -#if e131debug - Debug (_log, "send packet: rawidx %d dmxchannelcount %d universe: %d, packetsz %d" - , rawIdx - , dmxChannelCount - , _e131_universe + rawIdx / DMX_MAX - , E131_DMP_DATA + 1 + thisChannelCount - ); -#endif - retVal &= writeBytes(E131_DMP_DATA + 1 + thisChannelCount, e131_packet.raw); + qCDebug(leddevice_write) << QString("send packet: rawidx %1 dmxchannelcount %2 universe: %3, packetsz %4") + .arg(rawIdx) + .arg(_dmxChannelCount) + .arg(_e131_universe + rawIdx / _e131_dmx_max) + .arg(thisChannelCount); + retVal &= writeBytes(E131_DMP_DATA + 1 + thisChannelCount, _e131_packet.raw); } } diff --git a/libsrc/leddevice/dev_net/LedDeviceUdpE131.h b/libsrc/leddevice/dev_net/LedDeviceUdpE131.h index a9a5150ff..e59f7760d 100644 --- a/libsrc/leddevice/dev_net/LedDeviceUdpE131.h +++ b/libsrc/leddevice/dev_net/LedDeviceUdpE131.h @@ -3,6 +3,8 @@ // hyperion includes #include "ProviderUdp.h" +#include "utils/ColorRgbw.h" +#include "utils/RgbToRgbw.h" #include #include @@ -49,48 +51,52 @@ const unsigned int E131_DMP_DATA=125; /* E1.31 Packet Structure */ -typedef union +struct e131_packet_t { -#pragma pack(push, 1) - struct + union { - /* Root Layer */ - uint16_t preamble_size; - uint16_t postamble_size; - uint8_t acn_id[12]; - uint16_t root_flength; - uint32_t root_vector; - char cid[16]; - - /* Frame Layer */ - uint16_t frame_flength; - uint32_t frame_vector; - char source_name[64]; - uint8_t priority; - uint16_t reserved; - uint8_t sequence_number; - uint8_t options; - uint16_t universe; - - /* DMP Layer */ - uint16_t dmp_flength; - uint8_t dmp_vector; - uint8_t type; - uint16_t first_address; - uint16_t address_increment; - uint16_t property_value_count; - uint8_t property_values[513]; - }; +#pragma pack(push, 1) + struct + { + /* Root Layer */ + uint16_t preamble_size; + uint16_t postamble_size; + uint8_t acn_id[12]; + uint16_t root_flength; + uint32_t root_vector; + char cid[16]; + + /* Frame Layer */ + uint16_t frame_flength; + uint32_t frame_vector; + char source_name[64]; + uint8_t priority; + uint16_t reserved; + uint8_t sequence_number; + uint8_t options; + uint16_t universe; + + /* DMP Layer */ + uint16_t dmp_flength; + uint8_t dmp_vector; + uint8_t type; + uint16_t first_address; + uint16_t address_increment; + uint16_t property_value_count; + uint8_t property_values[513]; + } frame; #pragma pack(pop) - uint8_t raw[638]; -} e131_packet_t; + uint8_t raw[638]; + }; +}; /// /// Implementation of the LedDevice interface for sending led colors via udp/E1.31 packets /// class LedDeviceUdpE131 : public ProviderUdp { + Q_OBJECT public: /// @@ -131,19 +137,30 @@ class LedDeviceUdpE131 : public ProviderUdp /// @param[in] ledValues The RGB-color per LED /// @return Zero on success, else negative /// - int write(const QVector & ledValues) override; + int write(const QVector & ledValues) override; + /// + /// @brief Writes the RGB-Color values to the LEDs. + /// + /// @param[in] ledValues The RGB-color per LED + /// @return Zero on success, else negative /// /// @brief Generate E1.31 communication header /// - void prepare(unsigned this_universe, unsigned this_dmxChannelCount); + void prepare(uint16_t this_universe, uint16_t this_dmxChannelCount); - e131_packet_t e131_packet; + e131_packet_t _e131_packet; uint8_t _e131_seq = 0; - uint8_t _e131_universe = 1; + uint16_t _e131_universe = 1; + uint16_t _e131_dmx_max; uint8_t _acn_id[12] = {0x41, 0x53, 0x43, 0x2d, 0x45, 0x31, 0x2e, 0x31, 0x37, 0x00, 0x00, 0x00 }; QString _e131_source_name; QUuid _e131_cid; + + // RGBW specific members + RGBW::WhiteAlgorithm _whiteAlgorithm; + ColorRgbw _temp_rgbw; + uint16_t _dmxChannelCount; }; #endif // LEDEVICEUDPE131_H diff --git a/libsrc/leddevice/schemas/schema-e131.json b/libsrc/leddevice/schemas/schema-e131.json index 027407272..0d68fd0c3 100644 --- a/libsrc/leddevice/schemas/schema-e131.json +++ b/libsrc/leddevice/schemas/schema-e131.json @@ -22,6 +22,15 @@ "default": 1, "propertyOrder": 3 }, + "dmx-max": { + "type": "integer", + "title": "edt_dev_spec_dmx_max_title", + "minimum": 1, + "maximum": 512, + "default": 512, + "access": "expert", + "propertyOrder": 4 + }, "latchTime": { "type": "integer", "title": "edt_dev_spec_latchtime_title", @@ -30,13 +39,43 @@ "minimum": 0, "maximum": 1000, "access": "expert", - "propertyOrder": 4 + "propertyOrder": 5 }, "cid": { "type": "string", "title": "edt_dev_spec_cid_title", - "propertyOrder": 5 + "propertyOrder": 6 + }, + "whiteAlgorithm": { + "type": "string", + "title": "edt_dev_spec_whiteLedAlgor_title", + "enum": [ + "subtract_minimum", + "sub_min_cool_adjust", + "sub_min_warm_adjust", + "cold_white", + "neutral_white", + "auto", + "auto_max", + "auto_accurate", + "white_off" + ], + "default": "white_off", + "options": { + "enum_titles": [ + "edt_dev_enum_subtract_minimum", + "edt_dev_enum_sub_min_cool_adjust", + "edt_dev_enum_sub_min_warm_adjust", + "edt_dev_enum_cold_white", + "edt_dev_enum_neutral_white", + "edt_dev_enum_auto", + "edt_dev_enum_auto_max", + "edt_dev_enum_auto_accurate", + "edt_dev_enum_white_off" + ] + }, + "propertyOrder": 7 } }, "additionalProperties": true -} +} \ No newline at end of file