From 6616653a7fea86f1a78f8da950bcee0301c800bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Micha=C3=ABl=20Celerier?= Date: Thu, 22 Feb 2024 00:46:19 -0500 Subject: [PATCH] [dmx] Input for sACN and Artnet --- src/ossia/network/sockets/udp_socket.hpp | 7 ++ .../protocols/artnet/artnet_protocol.cpp | 93 +++++++++++++- .../protocols/artnet/artnet_protocol.hpp | 19 ++- src/ossia/protocols/artnet/dmx_parameter.cpp | 113 +++++++++++++++++- src/ossia/protocols/artnet/dmx_parameter.hpp | 8 +- .../protocols/artnet/dmx_protocol_base.cpp | 40 ++++++- .../protocols/artnet/dmx_protocol_base.hpp | 25 +++- .../protocols/artnet/dmxusbpro_protocol.cpp | 2 +- .../protocols/artnet/dmxusbpro_protocol.hpp | 2 +- src/ossia/protocols/artnet/e131_protocol.cpp | 41 ++----- src/ossia/protocols/artnet/e131_protocol.hpp | 6 +- 11 files changed, 304 insertions(+), 52 deletions(-) diff --git a/src/ossia/network/sockets/udp_socket.hpp b/src/ossia/network/sockets/udp_socket.hpp index d0f134b6e5b..6cce9bae444 100644 --- a/src/ossia/network/sockets/udp_socket.hpp +++ b/src/ossia/network/sockets/udp_socket.hpp @@ -18,6 +18,12 @@ class udp_receive_socket using proto = boost::asio::ip::udp; public: + udp_receive_socket(boost::asio::io_context& ctx) + : m_context{ctx} + , m_socket{ctx} + { + } + udp_receive_socket(const socket_configuration& conf, boost::asio::io_context& ctx) : m_context{ctx} , m_endpoint{boost::asio::ip::make_address(conf.host), conf.port} @@ -27,6 +33,7 @@ class udp_receive_socket ~udp_receive_socket() = default; + void assign(int sock) { m_socket.assign(boost::asio::ip::udp::v4(), sock); } void open() { m_socket.open(boost::asio::ip::udp::v4()); diff --git a/src/ossia/protocols/artnet/artnet_protocol.cpp b/src/ossia/protocols/artnet/artnet_protocol.cpp index df711d418e8..6134fbf1865 100644 --- a/src/ossia/protocols/artnet/artnet_protocol.cpp +++ b/src/ossia/protocols/artnet/artnet_protocol.cpp @@ -5,9 +5,12 @@ #include #include +#include + #include #include +#include namespace ossia::net { @@ -21,7 +24,7 @@ dmx_buffer::~dmx_buffer() = default; artnet_protocol::artnet_protocol( ossia::net::network_context_ptr ctx, const dmx_config& conf, std::string_view host) - : dmx_protocol_base{ctx, conf} + : dmx_output_protocol_base{ctx, conf} { if(conf.frequency < 1 || conf.frequency > 44) throw std::runtime_error("DMX 512 update frequency must be in the range [1, 44] Hz"); @@ -45,8 +48,10 @@ artnet_protocol::artnet_protocol( throw std::runtime_error("Artnet new failed"); static constexpr int artnet_port_id = 0; - artnet_set_port_type(m_node, artnet_port_id, ARTNET_ENABLE_OUTPUT, ARTNET_PORT_DMX); - artnet_set_port_addr(m_node, artnet_port_id, ARTNET_OUTPUT_PORT, m_conf.universe); + static constexpr int artnet_subnet_id = 0; + artnet_set_port_type(m_node, artnet_port_id, ARTNET_ENABLE_INPUT, ARTNET_PORT_DMX); + artnet_set_port_addr(m_node, artnet_port_id, ARTNET_INPUT_PORT, m_conf.universe); + artnet_set_subnet_addr(m_node, artnet_subnet_id); artnet_set_short_name(m_node, "libossia"); artnet_set_long_name(m_node, "libossia artnet protocol"); @@ -80,6 +85,88 @@ void artnet_protocol::update_function() m_buffer.dirty = false; } } + +artnet_input_protocol::artnet_input_protocol( + ossia::net::network_context_ptr ctx, const dmx_config& conf, std::string_view host) + : dmx_input_protocol_base{ctx, conf} +{ + if(conf.frequency < 1 || conf.frequency > 44) + throw std::runtime_error("DMX 512 update frequency must be in the range [1, 44] Hz"); + +#if defined(_NDEBUG) + bool verbose = 0; +#else + bool verbose = 1; +#endif + m_node = artnet_new("192.168.0.55", verbose); + + if(m_node == NULL) + throw std::runtime_error("Artnet new failed"); + + static constexpr int artnet_port_id = 0; + static constexpr int artnet_subnet_id = 0; + artnet_set_port_type(m_node, artnet_port_id, ARTNET_ENABLE_OUTPUT, ARTNET_PORT_DMX); + artnet_set_port_addr(m_node, artnet_port_id, ARTNET_OUTPUT_PORT, 0); + artnet_set_subnet_addr(m_node, artnet_subnet_id); + + artnet_set_short_name(m_node, "libossia"); + artnet_set_long_name(m_node, "libossia artnet protocol"); + artnet_set_node_type(m_node, ARTNET_NODE); + artnet_set_dmx_handler( + m_node, + [](artnet_node n, int port, void* d) -> int { + auto& self = *(artnet_input_protocol*)d; + self.on_packet(n, port); + return 0; + }, + this); + + artnet_dump_config(m_node); + std::fflush(stdout); + std::fflush(stderr); + if(artnet_start(m_node) != ARTNET_EOK) + throw std::runtime_error("Artnet Start failed"); +} + +artnet_input_protocol::~artnet_input_protocol() +{ + m_socket->close(); + std::future wait + = boost::asio::post(m_context->context, boost::asio::use_future); + wait.get(); + artnet_destroy(m_node); +} + +void artnet_input_protocol::set_device(ossia::net::device_base& dev) +{ + dmx_protocol_base::set_device(dev); + + m_socket = std::make_unique(m_context->context); + m_socket->assign(artnet_get_sd(m_node)); + auto& sock = m_socket->m_socket; + sock.non_blocking(true); + + do_read(); +} + +void artnet_input_protocol::do_read() +{ + auto& sock = m_socket->m_socket; + sock.async_wait( + boost::asio::ip::udp::socket::wait_read, [this](boost::system::error_code ec) { + if(ec == boost::asio::error::operation_aborted) + return; + artnet_read(m_node, 1); + do_read(); + }); +} + +void artnet_input_protocol::on_packet(artnet_node n, int port) +{ + int length = 0; + auto data = artnet_read_dmx(n, port, &length); + on_dmx(data + 1, std::min(length - 1, 512)); +} } #endif diff --git a/src/ossia/protocols/artnet/artnet_protocol.hpp b/src/ossia/protocols/artnet/artnet_protocol.hpp index e38bfff3223..4e031fc6747 100644 --- a/src/ossia/protocols/artnet/artnet_protocol.hpp +++ b/src/ossia/protocols/artnet/artnet_protocol.hpp @@ -1,6 +1,7 @@ #pragma once #include #if defined(OSSIA_PROTOCOL_ARTNET) +#include #include using artnet_node = void*; @@ -8,7 +9,7 @@ using artnet_node = void*; namespace ossia::net { struct dmx_config; -class OSSIA_EXPORT artnet_protocol final : public dmx_protocol_base +class OSSIA_EXPORT artnet_protocol final : public dmx_output_protocol_base { public: artnet_protocol( @@ -23,6 +24,22 @@ class OSSIA_EXPORT artnet_protocol final : public dmx_protocol_base artnet_node m_node; }; +class OSSIA_EXPORT artnet_input_protocol final : public dmx_input_protocol_base +{ +public: + artnet_input_protocol( + ossia::net::network_context_ptr, const dmx_config& conf, std::string_view host); + ~artnet_input_protocol(); + + void set_device(ossia::net::device_base& dev) override; + +private: + void on_packet(artnet_node n, int port); + void do_read(); + + artnet_node m_node; + std::unique_ptr m_socket; +}; } #endif diff --git a/src/ossia/protocols/artnet/dmx_parameter.cpp b/src/ossia/protocols/artnet/dmx_parameter.cpp index 75c3494d556..d3a8327bf3c 100644 --- a/src/ossia/protocols/artnet/dmx_parameter.cpp +++ b/src/ossia/protocols/artnet/dmx_parameter.cpp @@ -9,7 +9,77 @@ namespace ossia::net { template -struct artnet_visitor +struct artnet_in_visitor +{ + static_assert(I >= 1); + static_assert(I <= 4); + + const uint8_t* start{}; + void apply(uint32_t& res) const noexcept + { + if constexpr(I == 1) + { + res += *start; + } + else if constexpr(I == 2) + { + res += *(start + 1); + res += *(start + 0) << 8; + } + else if constexpr(I == 3) + { + res += *(start + 2); + res += *(start + 1) << 8; + res += *(start + 0) << 16; + } + else if constexpr(I == 4) + { + res += *(start + 3); + res += *(start + 2) << 8; + res += *(start + 1) << 16; + res += *(start + 0) << 24; + } + } + + void operator()(int& v) const noexcept + { + uint32_t vv = 0; + apply(vv); + v = vv; + } + void operator()(float& v) const noexcept + { + uint32_t vv = 0; + apply(vv); + v = vv; + } + void operator()(bool& v) const noexcept + { + uint32_t vv = 0; + apply(vv); + v = vv; + } + template + void operator()(Args&&...) const noexcept + { + } +}; + +template <> +struct artnet_in_visitor<1> +{ + const uint8_t* value{}; + void operator()(int& v) const noexcept { v = *value; } + void operator()(float& v) const noexcept { v = *value; } + void operator()(bool& v) const noexcept { v = *value > 0; } + template + void operator()(const Args&...) const noexcept + { + } +}; + +template +struct artnet_out_visitor { static_assert(I >= 1); static_assert(I <= 4); @@ -41,6 +111,7 @@ struct artnet_visitor } void operator()(int v) const noexcept { return apply(v); } void operator()(float v) const noexcept { return apply(v); } + void operator()(bool v) const noexcept { return apply(v ? 0xFFFFFFFF : 0x0); } template void operator()(Args&&...) const noexcept { @@ -48,7 +119,7 @@ struct artnet_visitor }; template <> -struct artnet_visitor<1> +struct artnet_out_visitor<1> { dmx_parameter& self; void operator()(int v) const noexcept @@ -59,11 +130,16 @@ struct artnet_visitor<1> { self.m_buffer.data[self.m_channel] = std::clamp(v, 0.f, 255.f); } + void operator()(bool v) const noexcept + { + self.m_buffer.data[self.m_channel] = v ? 255 : 0; + } template void operator()(const Args&...) const noexcept { } }; + dmx_parameter::dmx_parameter( net::node_base& node, dmx_buffer& buffer, const unsigned int channel, int min, int max, int8_t bytes) @@ -78,21 +154,46 @@ dmx_parameter::dmx_parameter( dmx_parameter::~dmx_parameter() = default; +void dmx_parameter::set_dmx_value(const uint8_t* start, const uint8_t* buffer_end) +{ + // dmx in -> ossia + if(buffer_end - start < m_bytes) + return; + + switch(m_bytes) + { + case 1: + m_current_value.apply(artnet_in_visitor<1>{start}); + break; + case 2: + m_current_value.apply(artnet_in_visitor<2>{start}); + break; + case 3: + m_current_value.apply(artnet_in_visitor<3>{start}); + break; + case 4: + m_current_value.apply(artnet_in_visitor<4>{start}); + break; + } + push_value(m_current_value); +} + void dmx_parameter::device_update_value() { + // ossia -> dmx out switch(m_bytes) { case 1: - m_current_value.apply(artnet_visitor<1>{*this}); + m_current_value.apply(artnet_out_visitor<1>{*this}); break; case 2: - m_current_value.apply(artnet_visitor<2>{*this}); + m_current_value.apply(artnet_out_visitor<2>{*this}); break; case 3: - m_current_value.apply(artnet_visitor<3>{*this}); + m_current_value.apply(artnet_out_visitor<3>{*this}); break; case 4: - m_current_value.apply(artnet_visitor<4>{*this}); + m_current_value.apply(artnet_out_visitor<4>{*this}); break; } diff --git a/src/ossia/protocols/artnet/dmx_parameter.hpp b/src/ossia/protocols/artnet/dmx_parameter.hpp index 866e11c58fd..ec21556084c 100644 --- a/src/ossia/protocols/artnet/dmx_parameter.hpp +++ b/src/ossia/protocols/artnet/dmx_parameter.hpp @@ -22,6 +22,10 @@ class OSSIA_EXPORT dmx_parameter : public device_parameter ~dmx_parameter(); int8_t m_bytes{}; + uint32_t channel() const noexcept { return m_channel; } + + void set_dmx_value(const uint8_t* start, const uint8_t* buffer_end); + private: void device_update_value() override; @@ -29,7 +33,9 @@ class OSSIA_EXPORT dmx_parameter : public device_parameter const uint32_t m_channel{}; template - friend struct artnet_visitor; + friend struct artnet_in_visitor; + template + friend struct artnet_out_visitor; }; class OSSIA_EXPORT dmx_enum_parameter : public device_parameter diff --git a/src/ossia/protocols/artnet/dmx_protocol_base.cpp b/src/ossia/protocols/artnet/dmx_protocol_base.cpp index e27d3d157b4..3744af2f9bc 100644 --- a/src/ossia/protocols/artnet/dmx_protocol_base.cpp +++ b/src/ossia/protocols/artnet/dmx_protocol_base.cpp @@ -1,6 +1,7 @@ #include "dmx_protocol_base.hpp" #include +#include #include namespace ossia::net @@ -10,7 +11,6 @@ dmx_protocol_base::dmx_protocol_base( ossia::net::network_context_ptr ctx, const dmx_config& conf) : protocol_base{flags{}} , m_context{ctx} - , m_timer{ctx->context} , m_conf{conf} { } @@ -41,11 +41,6 @@ bool dmx_protocol_base::update(ossia::net::node_base&) return true; } -void dmx_protocol_base::stop_processing() -{ - m_timer.stop(); -} - void dmx_protocol_base::set_device(ossia::net::device_base& dev) { m_device = &dev; @@ -64,4 +59,37 @@ void dmx_protocol_base::set_device(ossia::net::device_base& dev) } } } + +dmx_output_protocol_base::dmx_output_protocol_base( + ossia::net::network_context_ptr ctx, const dmx_config& conf) + : dmx_protocol_base{ctx, conf} + , m_timer{ctx->context} +{ +} + +void dmx_output_protocol_base::stop_processing() +{ + m_timer.stop(); +} + +void dmx_input_protocol_base::create_channel_map() +{ + m_cache.fill(nullptr); + + ossia::net::visit_parameters( + m_device->get_root_node(), + [this](ossia::net::node_base&, ossia::net::parameter_base& param) { + auto p = static_cast(¶m); + if(auto c = p->channel(); c >= 0 && c < m_cache.size()) + m_cache[p->channel()] = p; + }); +} + +void dmx_input_protocol_base::on_dmx(const uint8_t* dmx, int count) +{ + for(int i = 0; i < count; i++) + { + this->m_cache[i]->set_dmx_value(dmx + i, dmx + count); + } +} } diff --git a/src/ossia/protocols/artnet/dmx_protocol_base.hpp b/src/ossia/protocols/artnet/dmx_protocol_base.hpp index c08a95e82c4..0fad1f7221f 100644 --- a/src/ossia/protocols/artnet/dmx_protocol_base.hpp +++ b/src/ossia/protocols/artnet/dmx_protocol_base.hpp @@ -14,6 +14,7 @@ namespace ossia::net { struct dmx_config; +class dmx_parameter; class OSSIA_EXPORT dmx_protocol_base : public ossia::net::protocol_base { public: @@ -30,16 +31,36 @@ class OSSIA_EXPORT dmx_protocol_base : public ossia::net::protocol_base bool update(ossia::net::node_base&) override; dmx_buffer& buffer() noexcept { return m_buffer; } - void stop_processing(); protected: ossia::net::network_context_ptr m_context; - ossia::timer m_timer; dmx_buffer m_buffer; ossia::net::device_base* m_device{}; dmx_config m_conf{}; }; +class OSSIA_EXPORT dmx_output_protocol_base : public dmx_protocol_base +{ +public: + dmx_output_protocol_base(ossia::net::network_context_ptr, const dmx_config& conf); + void stop_processing(); + +protected: + ossia::timer m_timer; +}; + +class OSSIA_EXPORT dmx_input_protocol_base : public dmx_protocol_base +{ +public: + using dmx_protocol_base::dmx_protocol_base; + // Caches the DMX address -> ossia parameter mapping for + // fast operation during input + void create_channel_map(); + void on_dmx(const uint8_t* dmx, int count); + +protected: + std::array m_cache; +}; } diff --git a/src/ossia/protocols/artnet/dmxusbpro_protocol.cpp b/src/ossia/protocols/artnet/dmxusbpro_protocol.cpp index 28feed31eb7..e2488812508 100644 --- a/src/ossia/protocols/artnet/dmxusbpro_protocol.cpp +++ b/src/ossia/protocols/artnet/dmxusbpro_protocol.cpp @@ -7,7 +7,7 @@ namespace ossia::net dmxusbpro_protocol::dmxusbpro_protocol( ossia::net::network_context_ptr ctx, const dmx_config& conf, const ossia::net::serial_configuration& socket, int version) - : dmx_protocol_base{ctx, conf} + : dmx_output_protocol_base{ctx, conf} , m_port{ctx->context} , m_version{version} { diff --git a/src/ossia/protocols/artnet/dmxusbpro_protocol.hpp b/src/ossia/protocols/artnet/dmxusbpro_protocol.hpp index c56085191a9..d3d42610503 100644 --- a/src/ossia/protocols/artnet/dmxusbpro_protocol.hpp +++ b/src/ossia/protocols/artnet/dmxusbpro_protocol.hpp @@ -9,7 +9,7 @@ namespace ossia::net { -class OSSIA_EXPORT dmxusbpro_protocol final : public dmx_protocol_base +class OSSIA_EXPORT dmxusbpro_protocol final : public dmx_output_protocol_base { public: dmxusbpro_protocol( diff --git a/src/ossia/protocols/artnet/e131_protocol.cpp b/src/ossia/protocols/artnet/e131_protocol.cpp index 1ec2779355a..36d94361fb8 100644 --- a/src/ossia/protocols/artnet/e131_protocol.cpp +++ b/src/ossia/protocols/artnet/e131_protocol.cpp @@ -149,7 +149,7 @@ e131_host(const dmx_config& conf, const ossia::net::socket_configuration& socket e131_protocol::e131_protocol( ossia::net::network_context_ptr ctx, const dmx_config& conf, const ossia::net::socket_configuration& socket) - : dmx_protocol_base{ctx, conf} + : dmx_output_protocol_base{ctx, conf} , m_socket{e131_host(conf, socket), socket.port, ctx->context} { if(conf.frequency < 1 || conf.frequency > 44) @@ -207,7 +207,7 @@ void e131_protocol::update_function() e131_input_protocol::e131_input_protocol( ossia::net::network_context_ptr ctx, const dmx_config& conf, const ossia::net::socket_configuration& socket) - : dmx_protocol_base{ctx, conf} + : dmx_input_protocol_base{ctx, conf} , m_socket{socket, ctx->context} { if(conf.frequency < 1 || conf.frequency > 44) @@ -216,42 +216,27 @@ e131_input_protocol::e131_input_protocol( m_socket.open(); } -e131_input_protocol::~e131_input_protocol() -{ - stop_processing(); -} +e131_input_protocol::~e131_input_protocol() { } void e131_input_protocol::set_device(ossia::net::device_base& dev) { dmx_protocol_base::set_device(dev); - m_socket.receive( - [](const char* bytes, int sz) { std::cerr << (int)bytes[0] << "\n"; }); + // FIXME we must make sure that this is actually called after the fixtures have been assigned + m_socket.receive([this](const char* bytes, int sz) { on_packet(bytes, sz); }); } -void e131_input_protocol::update_function() +void e131_input_protocol::on_packet(const char* bytes, int sz) { - static std::atomic_int seq = 0; - try - { - e131_packet pkt; - e131_pkt_init(&pkt, this->m_conf.universe, 512); + if(sz != sizeof(e131_packet)) + return; - for(size_t pos = 0; pos < 512; pos++) - pkt.dmp.prop_val[pos + 1] = m_buffer.data[pos]; - pkt.frame.seq_number = seq.fetch_add(1, std::memory_order_relaxed); + auto& pkt = *reinterpret_cast(bytes); - // m_socket.write(reinterpret_cast(&pkt), sizeof(pkt)); + int universe = ntohs(pkt.frame.universe); + if(universe != this->m_conf.universe) + return; - m_buffer.dirty = false; - } - catch(std::exception& e) - { - ossia::logger().error("write failure: {}", e.what()); - } - catch(...) - { - ossia::logger().error("write failure"); - } + on_dmx(pkt.dmp.prop_val + 1, std::min(pkt.dmp.prop_val_cnt - 1, 512)); } } diff --git a/src/ossia/protocols/artnet/e131_protocol.hpp b/src/ossia/protocols/artnet/e131_protocol.hpp index 91615bb1576..a22177d9e17 100644 --- a/src/ossia/protocols/artnet/e131_protocol.hpp +++ b/src/ossia/protocols/artnet/e131_protocol.hpp @@ -9,7 +9,7 @@ namespace ossia::net { // Implementation mostly based on https://github.com/hhromic/libe131 -class OSSIA_EXPORT e131_protocol final : public ossia::net::dmx_protocol_base +class OSSIA_EXPORT e131_protocol final : public ossia::net::dmx_output_protocol_base { public: static constexpr uint16_t default_port = 5568; @@ -28,7 +28,7 @@ class OSSIA_EXPORT e131_protocol final : public ossia::net::dmx_protocol_base ossia::net::udp_send_socket m_socket; }; -class OSSIA_EXPORT e131_input_protocol final : public ossia::net::dmx_protocol_base +class OSSIA_EXPORT e131_input_protocol final : public ossia::net::dmx_input_protocol_base { public: static constexpr uint16_t default_port = 5568; @@ -43,7 +43,7 @@ class OSSIA_EXPORT e131_input_protocol final : public ossia::net::dmx_protocol_b void set_device(ossia::net::device_base& dev) override; private: - void update_function(); + void on_packet(const char* bytes, int sz); ossia::net::udp_receive_socket m_socket; };