From 5c0cc41af575aa49f446f45c0c69498f52e0f599 Mon Sep 17 00:00:00 2001 From: Kevin Matz Date: Wed, 1 Sep 2021 12:53:53 -0400 Subject: [PATCH] worker thread for streaming sACN --- README.md | 14 ++--- protocols/sacn/universe.cpp | 105 ++++++++++++++++++++++++++++++++++++ protocols/sacn/universe.h | 8 +++ 3 files changed, 120 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 6da40c9..7d32645 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ may not be inherited from sibling `PDU` in the data. | :- | :-: | :-: | | 0 to 10 V Analog Control | E1.3 | | | USITT DMX512-A | E1.11 | Data Abstraction | -| ACN Root Layer Protocol (RLP) | E1.17 | Rx | +| ACN Root Layer Protocol (RLP) | E1.17 | ✓ | | ACN Session Data Transport Protocol (SDT) | E1.17 | | | ACN Device Management Protocol (DMP) | E1.17 | Limited | | ACN Device Description Language (DDL) | E1.17 | | @@ -53,9 +53,9 @@ may not be inherited from sibling `PDU` in the data. | EPI 13 - Allocation of Internet Protocol Version 4 Addresses to ACN Hosts | E1.17 | | | EPI 15 - ACN Allocation of Multicast Addresses on IPv4 Networks | E1.17 | | | EPI 16 - ESTA Registered Names and Identifiers | E1.17 | | -| EPI 17 - ACN Root Layer Protocol Operation on UDP | E1.17 | Rx | +| EPI 17 - ACN Root Layer Protocol Operation on UDP | E1.17 | ✓ | | EPI 18 - Operation of SDT on UDP Networks | E1.17 | | -| EPI 19 - ACN Discovery on IP Networks | E1.17 | | +| EPI 19 - ACN Discovery on IP Networks | E1.17 | Names | | EPI 20 - MTU Size for ACN on IPv4 Networks | E1.17 | | | EPI 22 - DDL Core Modules for ACN Devices | E1.17 | | | Remote Device Management (RDM) | E1.20 | Responder | @@ -68,12 +68,12 @@ may not be inherited from sibling `PDU` in the data. | EPI 25 - Time Reference in ACN Systems Using SNTP and NTP | E1.30-3 | | | EPI 26 - DDL Extensions for DMX and RDM Devices | E1.30-4 | | | EPI 32 - Identification of Draft DDL Modules | E1.30-10 | | -| EPI 33 - ACN RLP Operation on TCP | E1.30-11 | | -| Streaming ACN (sACN) | E1.31 | Partial | +| EPI 33 - ACN RLP Operation on TCP | E1.30-11 | ✓ | +| Streaming ACN (sACN) | E1.31 | Data/Discovery | | sACN Receiver | E1.31 | ✓ | -| sACN Source | E1.31 | Limited | +| sACN Source | E1.31 | ✓ | | sACN Data | E1.31 | ✓ | -| sACN Sync | E1.31 | ✓ | +| sACN Sync | E1.31 | Rx | | sACN Discovery | E1.31 | ✓ | | sACN Preview | E1.31 | - | | RDMNet | E1.33 | | diff --git a/protocols/sacn/universe.cpp b/protocols/sacn/universe.cpp index 5bd556d..06b30c4 100644 --- a/protocols/sacn/universe.cpp +++ b/protocols/sacn/universe.cpp @@ -36,6 +36,7 @@ Universe::Universe(Source* src) , active_data_slots(0) , provenance_(std::make_shared()) , source_(src) + , tx_worker_(&Universe::tx_loop_, this) { destination.type = ACN::SDT::SDT_ADDR_NULL; } @@ -46,6 +47,17 @@ Universe::Universe(Source* src) */ Universe::~Universe() { + // shut down tx worker thread + if (tx_worker_.joinable()) + { + tx_control_mutex_.lock(); + tx_enable_ = false; + tx_control_mutex_.unlock(); + tx_request_.notify_all(); + tx_worker_.join(); + } + + // delete sync data pointer delete sync_data_; } @@ -169,6 +181,9 @@ void Universe::setValue (const uint16_t address, const uint8_t value) // set the value DMX::Universe::setValue(address, value); + + // request sACN message to be sent + tx_request_.notify_all(); } @@ -196,6 +211,9 @@ void Universe::setValue (const uint16_t start, const uint16_t footprint, // set the values DMX::Universe::setValue(start, footprint, data); + + // request sACN message to be sent + tx_request_.notify_all(); } @@ -304,4 +322,91 @@ void Universe::sACNsend() const } } + +/** + * @brief Universe::tx_loop_ + */ +void Universe::tx_loop_() +{ + // rx universes, by definition, won't have a source set + if (!source_) + return; + + // run at least 1 loop + bool enable = true; + + /// > \cite sACN 6.6.1 Transmission Rate + /// > + /// > E1.31 sources shall not transmit packets for a given universe number + /// > at a rate which exceeds the maximum refresh rate specified in + /// > E1.11 \cite DMX + /// + /// > \cite DMX Table 6 - Timing Diagram Values - output of transmitting UART + /// > + /// > Minimum Update Time for 513 slots : 22.7ms + std::chrono::milliseconds minimum_update_time(23); + + /// > \cite sACN 6.6.2 Null START Code Transmission Requirements in E1.31 + /// > Data Packets + /// > + /// > 1. Three packets containing the non-changing Property Values + /// > (corresponding to DMX512-A slot data) shall be sent before the + /// > initiation of transmission suppression. + DMX::DimmerData last_null_data({0}); + uint retransmission_count = 0; + + /// > 2. Thereafter, a single keep-alive packet shall be transmitted at + /// > intervals of between 800mS and 1000mS. + std::chrono::milliseconds keep_alive_interval(800); + + std::chrono::system_clock::time_point last_sent; + + // I don't fully understand the semantics of std::conditional_variable, + // but it requires this mutex in the sleeping thread. + std::mutex mtx; + + while (enable) + { + // enforce strict minimum update times + auto elapsed = std::chrono::duration_cast + (std::chrono::system_clock::now() - last_sent); + if (elapsed < minimum_update_time) + std::this_thread::sleep_for(minimum_update_time - elapsed); + + // check for control permission to continue looping + tx_control_mutex_.lock(); + enable = tx_enable_; + tx_control_mutex_.unlock(); + + // if shutting down, send stream_terminated in this last update + if (!enable) + provenance_->options.stream_terminated = true; + + // see if this is new data or re-transmitting + auto sleep = std::chrono::milliseconds(minimum_update_time); + null_start_mutex.lock(); + if (null_start_data != last_null_data) + { + retransmission_count = 0; + last_null_data = null_start_data; + } + else + if (++retransmission_count >= 2) + sleep = std::chrono::milliseconds(keep_alive_interval); + null_start_mutex.unlock(); + + // send the sACN message + sACNsend(); + last_sent = std::chrono::system_clock::now(); + + // sleep before the next cycle + if (enable) + { + mtx.lock(); + tx_request_.wait_for(mtx, sleep); + mtx.unlock(); + } + } +} + }; // namespace SACN diff --git a/protocols/sacn/universe.h b/protocols/sacn/universe.h index 5d4c5f0..9ac5b96 100644 --- a/protocols/sacn/universe.h +++ b/protocols/sacn/universe.h @@ -28,7 +28,9 @@ #include "dmx/universe.h" #include +#include #include +#include #include namespace sACN { @@ -97,6 +99,12 @@ private: /// > between the sequence number of an E1.31 Synchronization Packet and /// > the sequence number of an E1.31 Data Packet on that same universe. uint8_t sync_sequence_ = 0; + + std::condition_variable_any tx_request_; + std::mutex tx_control_mutex_; + bool tx_enable_ = true; + std::thread tx_worker_; + void tx_loop_(); }; } // SACN namespace