diff --git a/.gitignore b/.gitignore index d74286b4f..35092aadf 100644 --- a/.gitignore +++ b/.gitignore @@ -224,6 +224,10 @@ tools/e133/e133_monitor tools/e133/e133_monitor.exe tools/e133/e133_receiver tools/e133/e133_receiver.exe +tools/e133/llrp_manager +tools/e133/llrp_manager.exe +tools/e133/llrp_target +tools/e133/llrp_target.exe tools/e133/slp_locate tools/e133/slp_locate.exe tools/e133/slp_register diff --git a/NEWS b/NEWS index 6a58b4a0d..86c3ee55b 100644 --- a/NEWS +++ b/NEWS @@ -662,7 +662,7 @@ Documentation: * Fix the build on OS X Lion 10.7 - #231 * Fix the build with clang 4.2 * Added a simple E1.31 load test tool - * Added the first E1.33 (RDMNet) tools + * Added the first E1.33 (RDMnet) tools * Switched to using the new command line flags module in some programs. * Man page for dmxmonitor - #219 * Rename ola_universe_stats to ola_uni_stats, standardising names diff --git a/common/protocol/Ola.proto b/common/protocol/Ola.proto index 0e0bb1459..c9ede9a6c 100644 --- a/common/protocol/Ola.proto +++ b/common/protocol/Ola.proto @@ -68,6 +68,7 @@ enum PluginIds { OLA_PLUGIN_GPIO = 22; OLA_PLUGIN_SPIDMX = 23; OLA_PLUGIN_NANOLEAF = 24; + OLA_PLUGIN_E133 = 25; /* * To obtain a new plugin ID, open a ticket at diff --git a/common/rdm/UIDTest.cpp b/common/rdm/UIDTest.cpp index 3a0a3acb3..9191119a1 100644 --- a/common/rdm/UIDTest.cpp +++ b/common/rdm/UIDTest.cpp @@ -166,13 +166,13 @@ void UIDTest::testRPTUID() { static_cast(0x0001ffff)); // TODO(Peter): Handle the more complicated RPT vendorcast tests OLA_ASSERT_TRUE(rpt_all_controllers.IsBroadcast()); - // OLA_ASSERT_FALSE(rpt_all_controllers.IsVendorcast()); + OLA_ASSERT_FALSE(rpt_all_controllers.IsVendorcast()); OLA_ASSERT_TRUE(rpt_all_devices.IsBroadcast()); - // OLA_ASSERT_FALSE(rpt_all_devices.IsVendorcast()); - // OLA_ASSERT_TRUE(rpt_manufacturer_devices.IsBroadcast()); - // OLA_ASSERT_TRUE(rpt_manufacturer_devices.IsVendorcast()); - // OLA_ASSERT_TRUE(rpt_manufacturer_devices2.IsBroadcast()); - // OLA_ASSERT_TRUE(rpt_manufacturer_devices2.IsVendorcast()); + OLA_ASSERT_FALSE(rpt_all_devices.IsVendorcast()); + OLA_ASSERT_TRUE(rpt_manufacturer_devices.IsBroadcast()); + OLA_ASSERT_TRUE(rpt_manufacturer_devices.IsVendorcast()); + OLA_ASSERT_TRUE(rpt_manufacturer_devices2.IsBroadcast()); + OLA_ASSERT_TRUE(rpt_manufacturer_devices2.IsVendorcast()); } diff --git a/configure.ac b/configure.ac index 0d2aa0f01..0d880390f 100644 --- a/configure.ac +++ b/configure.ac @@ -895,6 +895,7 @@ PLUGIN_SUPPORT(artnet, USE_ARTNET) PLUGIN_SUPPORT(dmx4linux, USE_DMX4LINUX, [$have_dmx4linux]) PLUGIN_SUPPORT(dummy, USE_DUMMY) PLUGIN_SUPPORT(e131, USE_E131) +PLUGIN_SUPPORT(e133, USE_E133) PLUGIN_SUPPORT(espnet, USE_ESPNET) PLUGIN_SUPPORT(ftdidmx, USE_FTDI, [$have_libftdi]) PLUGIN_SUPPORT(gpio, USE_GPIO) diff --git a/doxygen/namespaces.dox b/doxygen/namespaces.dox index 78e52d8b0..bd1cabda7 100644 --- a/doxygen/namespaces.dox +++ b/doxygen/namespaces.dox @@ -15,7 +15,7 @@ * @brief DMX512 related code. * * @namespace ola::e133 - * @brief E1.33 (RDMNet) + * @brief E1.33 (RDMnet) * * @namespace ola::file * @brief File helper functions. diff --git a/include/ola/acn/ACNPort.h b/include/ola/acn/ACNPort.h index 97f98a1ab..50771e184 100644 --- a/include/ola/acn/ACNPort.h +++ b/include/ola/acn/ACNPort.h @@ -27,7 +27,7 @@ * [ACN on * wikipedia](http://en.wikipedia.org/wiki/Architecture_for_Control_Networks). * - * This covers code for E1.31 (Streaming ACN) and E1.33 (RDMNet). + * This covers code for E1.31 (Streaming ACN) and E1.33 (RDMnet). */ /** diff --git a/include/ola/acn/ACNVectors.h b/include/ola/acn/ACNVectors.h index b058beb81..e8d88e87d 100644 --- a/include/ola/acn/ACNVectors.h +++ b/include/ola/acn/ACNVectors.h @@ -74,7 +74,7 @@ enum E131Vector { * @brief Vectors used at the E1.33 layer. */ enum E133Vector { - VECTOR_FRAMING_RDMNET = 1, /**< RDMNet data */ + VECTOR_FRAMING_RDMNET = 1, /**< RDMnet data */ VECTOR_FRAMING_STATUS = 2, /**< Status message */ VECTOR_FRAMING_CONTROLLER = 3, /**< Controller message */ VECTOR_FRAMING_CHANGE_NOTIFICATION = 4, /**< Controller change message */ diff --git a/include/ola/e133/DeviceManager.h b/include/ola/e133/DeviceManager.h index 023e8d9f1..213511cff 100644 --- a/include/ola/e133/DeviceManager.h +++ b/include/ola/e133/DeviceManager.h @@ -49,7 +49,7 @@ using std::vector; class DeviceManager { public: /* - * The callback used to receive RDMNet layer messages from the devices. + * The callback used to receive RDMnet layer messages from the devices. * @returns true if the data should be acknowledged, false otherwise. */ typedef ola::Callback3 #include #include +#include #include namespace ola { @@ -48,7 +49,8 @@ class MessageBuilder { void PrependRDMHeader(IOStack *packet); void BuildTCPRDMCommandPDU(IOStack *packet, - ola::rdm::RDMRequest *request, + const ola::rdm::RDMRequest *request, + const ola::rdm::UID *rpt_destination_uid, uint16_t source_endpoint_id, uint16_t destination_endpoint_id, uint32_t sequence_number); @@ -65,7 +67,7 @@ class MessageBuilder { const string &description); void BuildUDPE133StatusPDU(IOStack *packet, uint32_t sequence_number, uint16_t endpoint_id, - ola::e133::E133StatusCode status_code, + const ola::e133::E133StatusCode status_code, const string &description); void BuildTCPRootE133(IOStack *packet, uint32_t vector, diff --git a/libs/acn/BrokerNullInflatorTest.cpp b/libs/acn/BrokerNullInflatorTest.cpp index bf2d98da3..8f3ada358 100644 --- a/libs/acn/BrokerNullInflatorTest.cpp +++ b/libs/acn/BrokerNullInflatorTest.cpp @@ -36,6 +36,7 @@ using ola::network::HostToNetwork; class BrokerNullInflatorTest: public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(BrokerNullInflatorTest); +// CPPUNIT_TEST(testDecodeHeader); CPPUNIT_TEST(testInflatePDU); CPPUNIT_TEST_SUITE_END(); diff --git a/libs/acn/LLRPProbeRequestPDU.cpp b/libs/acn/LLRPProbeRequestPDU.cpp index 341bf11f6..ecb9c9b21 100644 --- a/libs/acn/LLRPProbeRequestPDU.cpp +++ b/libs/acn/LLRPProbeRequestPDU.cpp @@ -30,7 +30,6 @@ namespace acn { using ola::io::OutputStream; using ola::network::HostToNetwork; using ola::rdm::UID; -using std::vector; unsigned int LLRPProbeRequestPDU::DataSize() const { llrp_probe_request_pdu_data data; @@ -103,6 +102,7 @@ void LLRPProbeRequestPDU::PrependPDU(ola::io::IOStack *stack, filter |= FILTER_BROKERS_ONLY; } data.filter = HostToNetwork(filter); + // TODO(Peter): We need to check we've got <= 200 UIDs here known_uids.Pack(data.known_uids, sizeof(data.known_uids)); stack->Write(reinterpret_cast(&data), static_cast(sizeof(llrp_probe_request_pdu_data) - diff --git a/libs/acn/Makefile.mk b/libs/acn/Makefile.mk index c7acf94b0..de2432be1 100644 --- a/libs/acn/Makefile.mk +++ b/libs/acn/Makefile.mk @@ -59,28 +59,11 @@ libs_acn_libolae131core_la_SOURCES = \ libs/acn/E131Sender.cpp \ libs/acn/E131Sender.h \ libs/acn/HeaderSet.h \ - libs/acn/LLRPHeader.h \ - libs/acn/LLRPInflator.cpp \ - libs/acn/LLRPInflator.h \ - libs/acn/LLRPProbeReplyInflator.cpp \ - libs/acn/LLRPProbeReplyInflator.h \ - libs/acn/LLRPProbeReplyPDU.cpp \ - libs/acn/LLRPProbeReplyPDU.h \ - libs/acn/LLRPProbeRequestInflator.cpp \ - libs/acn/LLRPProbeRequestInflator.h \ - libs/acn/LLRPProbeRequestPDU.cpp \ - libs/acn/LLRPProbeRequestPDU.h \ - libs/acn/LLRPPDU.cpp \ - libs/acn/LLRPPDU.h \ libs/acn/PDU.cpp \ libs/acn/PDU.h \ libs/acn/PDUTestCommon.h \ libs/acn/PreamblePacker.cpp \ libs/acn/PreamblePacker.h \ - libs/acn/RDMInflator.cpp \ - libs/acn/RDMInflator.h \ - libs/acn/RDMPDU.cpp \ - libs/acn/RDMPDU.h \ libs/acn/RootHeader.h \ libs/acn/RootInflator.cpp \ libs/acn/RootInflator.h \ @@ -88,15 +71,6 @@ libs_acn_libolae131core_la_SOURCES = \ libs/acn/RootPDU.h \ libs/acn/RootSender.cpp \ libs/acn/RootSender.h \ - libs/acn/RPTHeader.h \ - libs/acn/RPTInflator.cpp \ - libs/acn/RPTInflator.h \ - libs/acn/RPTNotificationInflator.h \ - libs/acn/RPTPDU.cpp \ - libs/acn/RPTPDU.h \ - libs/acn/RPTRequestInflator.h \ - libs/acn/RPTRequestPDU.cpp \ - libs/acn/RPTRequestPDU.h \ libs/acn/TCPTransport.cpp \ libs/acn/TCPTransport.h \ libs/acn/Transport.h \ @@ -113,32 +87,31 @@ libs_acn_libolae131core_la_LIBADD = $(uuid_LIBS) \ # libolae133core.la # This needs to be after libolaacn.la and libolae131core.la since it depends on # them. Otherwise it breaks the freeBSD build -# TODO(Peter): Re-add these classes -# libs/acn/BrokerConnectedClientListInflator.cpp -# libs/acn/BrokerConnectedClientListInflator.h -# libs/acn/BrokerHeader.h -# libs/acn/BrokerManager.cpp -# libs/acn/BrokerManagerImpl.cpp -# libs/acn/BrokerManagerImpl.h libs_acn_libolae133core_la_SOURCES = \ libs/acn/BrokerClientAddInflator.h \ libs/acn/BrokerClientEntryChangeInflator.h \ libs/acn/BrokerClientEntryHeader.h \ libs/acn/BrokerClientEntryPDU.cpp \ libs/acn/BrokerClientEntryPDU.h \ - libs/acn/BrokerClientEntryRPTPDU.cpp \ - libs/acn/BrokerClientEntryRPTPDU.h \ libs/acn/BrokerClientEntryRPTInflator.cpp \ libs/acn/BrokerClientEntryRPTInflator.h \ + libs/acn/BrokerClientEntryRPTPDU.cpp \ + libs/acn/BrokerClientEntryRPTPDU.h \ libs/acn/BrokerClientEntryUpdateInflator.h \ libs/acn/BrokerClientRemoveInflator.h \ + libs/acn/BrokerConnectedClientListInflator.cpp \ + libs/acn/BrokerConnectedClientListInflator.h \ libs/acn/BrokerConnectPDU.cpp \ libs/acn/BrokerConnectPDU.h \ libs/acn/BrokerConnectReplyInflator.cpp \ libs/acn/BrokerConnectReplyInflator.h \ libs/acn/BrokerFetchClientListPDU.cpp \ libs/acn/BrokerFetchClientListPDU.h \ + libs/acn/BrokerHeader.h \ libs/acn/BrokerInflator.h \ + libs/acn/BrokerManager.cpp \ + libs/acn/BrokerManagerImpl.cpp \ + libs/acn/BrokerManagerImpl.h \ libs/acn/BrokerNullInflator.h \ libs/acn/BrokerNullPDU.cpp \ libs/acn/BrokerNullPDU.h \ @@ -157,7 +130,35 @@ libs_acn_libolae133core_la_SOURCES = \ libs/acn/E133StatusInflator.h \ libs/acn/E133StatusPDU.cpp \ libs/acn/E133StatusPDU.h \ - libs/acn/MessageBuilder.cpp + libs/acn/LLRPHeader.h \ + libs/acn/LLRPInflator.cpp \ + libs/acn/LLRPInflator.h \ + libs/acn/LLRPProbeReplyInflator.cpp \ + libs/acn/LLRPProbeReplyInflator.h \ + libs/acn/LLRPProbeReplyPDU.cpp \ + libs/acn/LLRPProbeReplyPDU.h \ + libs/acn/LLRPProbeRequestInflator.cpp \ + libs/acn/LLRPProbeRequestInflator.h \ + libs/acn/LLRPProbeRequestPDU.cpp \ + libs/acn/LLRPProbeRequestPDU.h \ + libs/acn/LLRPPDU.cpp \ + libs/acn/LLRPPDU.h \ + libs/acn/MessageBuilder.cpp \ + libs/acn/RDMInflator.cpp \ + libs/acn/RDMInflator.h \ + libs/acn/RDMPDU.cpp \ + libs/acn/RDMPDU.h \ + libs/acn/RPTHeader.h \ + libs/acn/RPTInflator.cpp \ + libs/acn/RPTInflator.h \ + libs/acn/RPTNotificationInflator.h \ + libs/acn/RPTPDU.cpp \ + libs/acn/RPTPDU.h \ + libs/acn/RPTRequestInflator.h \ + libs/acn/RPTRequestPDU.cpp \ + libs/acn/RPTRequestPDU.h \ + libs/acn/RPTStatusInflator.cpp \ + libs/acn/RPTStatusInflator.h libs_acn_libolae133core_la_CXXFLAGS = \ $(COMMON_E133_CXXFLAGS) $(uuid_CFLAGS) @@ -208,6 +209,7 @@ libs_acn_E131Tester_LDADD = \ libs/acn/libolae131core.la \ $(COMMON_TESTING_LIBS) +# libs/acn/BrokerClientEntryRPTInflatorTest.cpp # libs/acn/BrokerInflatorTest.cpp libs_acn_E133Tester_SOURCES = \ libs/acn/BrokerClientEntryPDUTest.cpp \ @@ -238,6 +240,7 @@ libs_acn_LLRPTester_SOURCES = \ libs_acn_LLRPTester_CPPFLAGS = $(COMMON_TESTING_FLAGS) libs_acn_LLRPTester_LDADD = \ libs/acn/libolae131core.la \ + libs/acn/libolae133core.la \ $(COMMON_TESTING_LIBS) libs_acn_TransportTester_SOURCES = \ diff --git a/libs/acn/MessageBuilder.cpp b/libs/acn/MessageBuilder.cpp index 0216cfed6..688965439 100644 --- a/libs/acn/MessageBuilder.cpp +++ b/libs/acn/MessageBuilder.cpp @@ -46,6 +46,7 @@ using ola::acn::E133PDU; using ola::acn::PreamblePacker; using ola::acn::RootPDU; using ola::acn::RPTPDU; +using ola::rdm::UID; MessageBuilder::MessageBuilder(const CID &cid, const string &source_name) @@ -69,31 +70,17 @@ void MessageBuilder::PrependRDMHeader(IOStack *packet) { * Build a TCP E1.33 RDM Command PDU response. */ void MessageBuilder::BuildTCPRDMCommandPDU(IOStack *packet, - ola::rdm::RDMRequest *request, + const ola::rdm::RDMRequest *request, + const UID *rpt_destination_uid, uint16_t source_endpoint_id, uint16_t destination_endpoint_id, uint32_t sequence_number) { - // TODO(Peter): Potentially need some future way to handle controller - // messages here - ola::rdm::UID rpt_destination_uid = request->DestinationUID(); - if (rpt_destination_uid.IsBroadcast()) { - if (rpt_destination_uid.IsVendorcast()) { - rpt_destination_uid = ola::rdm::UID::RPTVendorcastAddressDevices( - rpt_destination_uid); - } else { - rpt_destination_uid = ola::rdm::UID::RPTAllDevices(); - } - if (destination_endpoint_id != NULL_ENDPOINT) { - // TODO(Peter): Should we handle the reserved endpoints now? - destination_endpoint_id = BROADCAST_ENDPOINT; - } - } ola::rdm::RDMCommandSerializer::Write(*request, packet); ola::acn::RDMPDU::PrependPDU(packet); ola::acn::RPTRequestPDU::PrependPDU(packet); RPTPDU::PrependPDU(packet, ola::acn::VECTOR_RPT_REQUEST, request->SourceUID(), source_endpoint_id, - rpt_destination_uid, destination_endpoint_id, + *rpt_destination_uid, destination_endpoint_id, sequence_number); RootPDU::PrependPDU(packet, ola::acn::VECTOR_ROOT_RPT, m_cid, true); PreamblePacker::AddTCPPreamble(packet); diff --git a/libs/acn/RDMInflator.h b/libs/acn/RDMInflator.h index aa2cb73ad..4d4e5517d 100644 --- a/libs/acn/RDMInflator.h +++ b/libs/acn/RDMInflator.h @@ -46,8 +46,8 @@ class RDMInflator: public BaseInflator { const std::string& // rdm data > GenericRDMMessageHandler; - // TODO(Peter): Set a better default vector for RDM use (possibly the RPT - // one) + // TODO(Peter): Set a better default vector for RDM use (possibly the RPT + // one) explicit RDMInflator(unsigned int vector = ola::acn::VECTOR_FRAMING_RDMNET); ~RDMInflator() {} diff --git a/olad/DynamicPluginLoader.cpp b/olad/DynamicPluginLoader.cpp index d5b11e60e..233df1eb6 100644 --- a/olad/DynamicPluginLoader.cpp +++ b/olad/DynamicPluginLoader.cpp @@ -39,6 +39,10 @@ #include "plugins/e131/E131Plugin.h" #endif // USE_E131 +#ifdef USE_E133 +#include "plugins/e133/E133Plugin.h" +#endif // USE_E133 + #ifdef USE_ESPNET #include "plugins/espnet/EspNetPlugin.h" #endif // USE_ESPNET @@ -159,6 +163,10 @@ void DynamicPluginLoader::PopulatePlugins() { m_plugins.push_back(new ola::plugin::e131::E131Plugin(m_plugin_adaptor)); #endif // USE_E131 +#ifdef USE_E133 + m_plugins.push_back(new ola::plugin::e133::E133Plugin(m_plugin_adaptor)); +#endif // USE_E133 + #ifdef USE_ESPNET m_plugins.push_back(new ola::plugin::espnet::EspNetPlugin(m_plugin_adaptor)); #endif // USE_ESPNET diff --git a/plugins/Makefile.mk b/plugins/Makefile.mk index b298128a1..67e3e56ff 100644 --- a/plugins/Makefile.mk +++ b/plugins/Makefile.mk @@ -23,6 +23,7 @@ if !USING_WIN32 include plugins/usbpro/Makefile.mk include plugins/dmx4linux/Makefile.mk include plugins/e131/Makefile.mk +include plugins/e133/Makefile.mk include plugins/uartdmx/Makefile.mk endif diff --git a/plugins/e133/E133Plugin.cpp b/plugins/e133/E133Plugin.cpp new file mode 100644 index 000000000..3bf7fe29f --- /dev/null +++ b/plugins/e133/E133Plugin.cpp @@ -0,0 +1,185 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * E133Plugin.cpp + * The E1.33 plugin for ola + * Copyright (C) 2024 Peter Newman + */ + +#include +#include + +#include "ola/Logging.h" +#include "ola/network/NetworkUtils.h" +#include "ola/network/SocketAddress.h" +#include "ola/StringUtils.h" +#include "ola/acn/CID.h" +#include "olad/PluginAdaptor.h" +#include "olad/Preferences.h" +#include "plugins/e133/E133Device.h" +#include "plugins/e133/E133Plugin.h" +#include "plugins/e133/E133PluginDescription.h" + +namespace ola { +namespace plugin { +namespace e133 { + +using ola::acn::CID; +using ola::network::IPV4SocketAddress; +using std::string; + +const char E133Plugin::CID_KEY[] = "cid"; +const unsigned int E133Plugin::DEFAULT_DSCP_VALUE = 0; +const char E133Plugin::DSCP_KEY[] = "dscp"; +const char E133Plugin::INPUT_PORT_COUNT_KEY[] = "input_ports"; +const char E133Plugin::IP_KEY[] = "ip"; +const char E133Plugin::OUTPUT_PORT_COUNT_KEY[] = "output_ports"; +const char E133Plugin::PLUGIN_NAME[] = "E1.33 (RDMnet)"; +const char E133Plugin::PLUGIN_PREFIX[] = "e133"; +const char E133Plugin::PREPEND_HOSTNAME_KEY[] = "prepend_hostname"; +const char E133Plugin::TARGET_SOCKET_KEY[] = "target_socket"; +const unsigned int E133Plugin::DEFAULT_PORT_COUNT = 5; + + +/* + * Start the plugin + */ +bool E133Plugin::StartHook() { + CID cid = CID::FromString(m_preferences->GetValue(CID_KEY)); + string ip_addr = m_preferences->GetValue(IP_KEY); + + E133Device::E133DeviceOptions options; + if (m_preferences->GetValueAsBool(PREPEND_HOSTNAME_KEY)) { + std::ostringstream str; + str << ola::network::Hostname() << "-" << m_plugin_adaptor->InstanceName(); +// options.source_name = str.str(); + } else { +// options.source_name = m_plugin_adaptor->InstanceName(); + } + + unsigned int dscp; + if (!StringToInt(m_preferences->GetValue(DSCP_KEY), &dscp)) { + OLA_WARN << "Can't convert dscp value " << + m_preferences->GetValue(DSCP_KEY) << " to int"; +// options.dscp = 0; + } else { + // shift 2 bits left +// options.dscp = dscp << 2; + } + + if (!StringToInt(m_preferences->GetValue(INPUT_PORT_COUNT_KEY), + &options.input_ports)) { + OLA_WARN << "Invalid value for input_ports"; + } + + if (!StringToInt(m_preferences->GetValue(OUTPUT_PORT_COUNT_KEY), + &options.output_ports)) { + OLA_WARN << "Invalid value for output_ports"; + } + + IPV4SocketAddress socket_address; + if (!IPV4SocketAddress::FromString(m_preferences->GetValue(TARGET_SOCKET_KEY), &socket_address)) { + OLA_WARN << "Invalid value for " << TARGET_SOCKET_KEY; + } + + m_device = new E133Device(this, cid, ip_addr, socket_address, m_plugin_adaptor, options); + + if (!m_device->Start()) { + delete m_device; + return false; + } + + m_plugin_adaptor->RegisterDevice(m_device); + return true; +} + + +/* + * Stop the plugin + * @return true on success, false on failure + */ +bool E133Plugin::StopHook() { + if (m_device) { + m_plugin_adaptor->UnregisterDevice(m_device); + bool ret = m_device->Stop(); + delete m_device; + return ret; + } + return true; +} + + +/* + * Return the description for this plugin + */ +string E133Plugin::Description() const { + return plugin_description; +} + + +/* + * Load the plugin prefs and default to sensible values + * + */ +bool E133Plugin::SetDefaultPreferences() { + if (!m_preferences) + return false; + + bool save = false; + + CID cid = CID::FromString(m_preferences->GetValue(CID_KEY)); + if (cid.IsNil()) { + cid = CID::Generate(); + m_preferences->SetValue(CID_KEY, cid.ToString()); + save = true; + } + + save |= m_preferences->SetDefaultValue( + DSCP_KEY, + UIntValidator(0, 63), + DEFAULT_DSCP_VALUE); + + save |= m_preferences->SetDefaultValue( + INPUT_PORT_COUNT_KEY, + UIntValidator(0, 512), + DEFAULT_PORT_COUNT); + + save |= m_preferences->SetDefaultValue( + OUTPUT_PORT_COUNT_KEY, + UIntValidator(0, 512), + DEFAULT_PORT_COUNT); + + save |= m_preferences->SetDefaultValue(IP_KEY, StringValidator(true), ""); + + save |= m_preferences->SetDefaultValue( + PREPEND_HOSTNAME_KEY, + BoolValidator(), + true); + + IPV4SocketAddress socket_address; + if (!IPV4SocketAddress::FromString(m_preferences->GetValue(TARGET_SOCKET_KEY), &socket_address)) { + m_preferences->SetValue(TARGET_SOCKET_KEY, ""); + save = true; + } + + if (save) { + m_preferences->Save(); + } + + return true; +} +} // namespace e133 +} // namespace plugin +} // namespace ola diff --git a/plugins/e133/E133Plugin.h b/plugins/e133/E133Plugin.h new file mode 100644 index 000000000..68059f302 --- /dev/null +++ b/plugins/e133/E133Plugin.h @@ -0,0 +1,67 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * E133Plugin.h + * Interface for the E1.33 plugin class + * Copyright (C) 2024 Peter Newman + */ + +#ifndef PLUGINS_E133_E133PLUGIN_H_ +#define PLUGINS_E133_E133PLUGIN_H_ + +#include +#include "olad/Plugin.h" +#include "ola/plugin_id.h" + +namespace ola { +namespace plugin { +namespace e133 { + +class E133Device; + +class E133Plugin: public ola::Plugin { + public: + explicit E133Plugin(ola::PluginAdaptor *plugin_adaptor): + ola::Plugin(plugin_adaptor), + m_device(NULL) {} + ~E133Plugin() {} + + std::string Name() const { return PLUGIN_NAME; } + ola_plugin_id Id() const { return OLA_PLUGIN_E133; } + std::string Description() const; + std::string PluginPrefix() const { return PLUGIN_PREFIX; } + + private: + bool StartHook(); + bool StopHook(); + bool SetDefaultPreferences(); + + E133Device *m_device; + static const char CID_KEY[]; + static const unsigned int DEFAULT_DSCP_VALUE; + static const unsigned int DEFAULT_PORT_COUNT; + static const char DSCP_KEY[]; + static const char INPUT_PORT_COUNT_KEY[]; + static const char IP_KEY[]; + static const char OUTPUT_PORT_COUNT_KEY[]; + static const char PLUGIN_NAME[]; + static const char PLUGIN_PREFIX[]; + static const char PREPEND_HOSTNAME_KEY[]; + static const char TARGET_SOCKET_KEY[]; +}; +} // namespace e133 +} // namespace plugin +} // namespace ola +#endif // PLUGINS_E133_E133PLUGIN_H_ diff --git a/plugins/e133/Makefile.mk b/plugins/e133/Makefile.mk new file mode 100644 index 000000000..d64378a62 --- /dev/null +++ b/plugins/e133/Makefile.mk @@ -0,0 +1,35 @@ +# LIBRARIES +################################################## + +if USE_E133 +lib_LTLIBRARIES += plugins/e133/libolae133.la + +# Plugin description is generated from README.md +built_sources += plugins/e133/E133PluginDescription.h +nodist_plugins_e133_libolae133_la_SOURCES = \ + plugins/e133/E133PluginDescription.h +plugins/e133/E133PluginDescription.h: plugins/e133/README.md plugins/e133/Makefile.mk plugins/convert_README_to_header.sh + sh $(top_srcdir)/plugins/convert_README_to_header.sh $(top_srcdir)/plugins/e133 $(top_builddir)/plugins/e133/E133PluginDescription.h + +plugins_e133_libolae133_la_SOURCES = \ + plugins/e133/E133Device.cpp \ + plugins/e133/E133Device.h \ + plugins/e133/E133Plugin.cpp \ + plugins/e133/E133Plugin.h \ + plugins/e133/E133Port.cpp \ + plugins/e133/E133Port.h \ + plugins/e133/E133PortImpl.cpp \ + plugins/e133/E133PortImpl.h +plugins_e133_libolae133_la_CXXFLAGS = $(COMMON_PROTOBUF_CXXFLAGS) $(uuid_CFLAGS) + +# plugins/e133/messages/libolae133conf.la +plugins_e133_libolae133_la_LIBADD = \ + $(uuid_LIBS) \ + common/libolacommon.la \ + olad/plugin_api/libolaserverplugininterface.la \ + libs/acn/libolaacn.la \ + libs/acn/libolae131core.la \ + libs/acn/libolae133core.la +endif + +EXTRA_DIST += plugins/e133/README.md diff --git a/plugins/e133/README.md b/plugins/e133/README.md new file mode 100644 index 000000000..2821e9ff1 --- /dev/null +++ b/plugins/e133/README.md @@ -0,0 +1,26 @@ +E1.33 (RDMnet) Plugin +===================================== + +This plugin creates a single device with a configurable number of input and +output ports. + +Each port can be assigned to a different E1.33 Universe. + + +## Config file: `ola-e133.conf` + +`cid = 00010203-0405-0607-0809-0A0B0C0D0E0F` +The CID to use for this device. + +#`dscp = [int]` +#The DSCP value to tag the packets with, range is 0 to 63. + +`input_ports = [int]` +The number of input ports to create up to an arbitrary max of 512. + +`ip = [a.b.c.d|]` +The IP address or interface name to bind to. If not specified it will use +the first non-loopback interface. + +`output_ports = [int]` +The number of output ports to create up to an arbitrary max of 512. diff --git a/python/ola/PidStore.py b/python/ola/PidStore.py index d1dcd6aa4..a8acf20ed 100644 --- a/python/ola/PidStore.py +++ b/python/ola/PidStore.py @@ -1126,8 +1126,12 @@ def LoadFile(self, pid_file_name, validate, override=False): if ((pid_pb.value < RDMConstants.RDM_MANUFACTURER_PID_MIN) or (pid_pb.value > RDMConstants.RDM_MANUFACTURER_PID_MAX)): raise InvalidPidFormat( - 'Manufacturer pid 0x%04hx not between 0x%04hx and 0x%04hx' % - (pid_pb.value, + 'Manufacturer %s (0x%04hx) manufacturer pid 0x%04hx (%s) not ' + 'between 0x%04hx and 0x%04hx' % + (manufacturer.manufacturer_name, + manufacturer.manufacturer_id, + pid_pb.value, + pid_pb.name, RDMConstants.RDM_MANUFACTURER_PID_MIN, RDMConstants.RDM_MANUFACTURER_PID_MAX)) if pid_pb.value in pid_dict: diff --git a/python/ola/RDMConstants.py b/python/ola/RDMConstants.py index 736f75051..e5ec395b7 100644 --- a/python/ola/RDMConstants.py +++ b/python/ola/RDMConstants.py @@ -20,6 +20,8 @@ __author__ = 'nomis52@gmail.com (Simon Newton)' +RDM_MAX_PARAM_DATA_LENGTH = 231 + RDM_ZERO_FOOTPRINT_DMX_ADDRESS = 0xFFFF RDM_MANUFACTURER_PID_MIN = 0x8000 @@ -39,6 +41,8 @@ RDM_DNS_NAME_SERVER_MAX_INDEX = 2 +RDM_MAX_SEARCH_DOMAIN_LENGTH = 231 + RDM_MAX_SERIAL_NUMBER_LENGTH = 231 RDM_MAX_TEST_DATA_PATTERN_LENGTH = 4096 diff --git a/tools/e133/DeviceManager.cpp b/tools/e133/DeviceManager.cpp index 3fe1f3887..a737e9feb 100644 --- a/tools/e133/DeviceManager.cpp +++ b/tools/e133/DeviceManager.cpp @@ -53,7 +53,7 @@ DeviceManager::~DeviceManager() {} /** - * Set the callback to be run when RDMNet data is received from a device. + * Set the callback to be run when RDMnet data is received from a device. * @param callback the RDMMessageCallback to run when data is received. */ void DeviceManager::SetRDMMessageCallback(RDMMessageCallback *callback) { diff --git a/tools/e133/DeviceManagerImpl.cpp b/tools/e133/DeviceManagerImpl.cpp index f063ae441..2f820ce5f 100644 --- a/tools/e133/DeviceManagerImpl.cpp +++ b/tools/e133/DeviceManagerImpl.cpp @@ -131,7 +131,7 @@ DeviceManagerImpl::~DeviceManagerImpl() { /** - * Set the callback to be run when RDMNet data is received from a device. + * Set the callback to be run when RDMnet data is received from a device. * @param callback the RDMMessageCallback to run when data is received. */ void DeviceManagerImpl::SetRDMMessageCallback(RDMMessageCallback *callback) { diff --git a/tools/e133/DeviceManagerImpl.h b/tools/e133/DeviceManagerImpl.h index 5f0c7ad1c..ab817d162 100644 --- a/tools/e133/DeviceManagerImpl.h +++ b/tools/e133/DeviceManagerImpl.h @@ -66,7 +66,7 @@ using std::vector; class DeviceManagerImpl { public: /* - * The callback used to receive RDMNet layer messages from the devices. + * The callback used to receive RDMnet layer messages from the devices. * @returns true if the data should be acknowledged, false otherwise. */ typedef ola::Callback3 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "libs/acn/HeaderSet.h" +#include "libs/acn/LLRPHeader.h" +#include "libs/acn/LLRPInflator.h" +#include "libs/acn/LLRPPDU.h" +#include "libs/acn/LLRPProbeReplyInflator.h" +#include "libs/acn/LLRPProbeReplyPDU.h" +#include "libs/acn/LLRPProbeRequestPDU.h" +#include "libs/acn/PreamblePacker.h" +#include "libs/acn/RDMInflator.h" +#include "libs/acn/RDMPDU.h" +#include "libs/acn/RootHeader.h" +#include "libs/acn/RootInflator.h" +#include "libs/acn/RootSender.h" +#include "libs/acn/Transport.h" +#include "libs/acn/UDPTransport.h" + +using std::string; +using std::vector; +using std::auto_ptr; + +using ola::acn::CID; +using ola::acn::IncomingUDPTransport; +using ola::acn::LLRPHeader; +using ola::acn::LLRPProbeReplyInflator; +using ola::acn::LLRPProbeReplyPDU; +using ola::acn::LLRPProbeRequestPDU; +using ola::acn::OutgoingUDPTransport; +using ola::acn::OutgoingUDPTransportImpl; +using ola::acn::RootHeader; +using ola::network::Interface; +using ola::network::IPV4Address; +using ola::network::IPV4SocketAddress; +using ola::network::MACAddress; +using ola::network::NetworkToHost; +using ola::rdm::PidStoreHelper; +using ola::rdm::RDMGetRequest; +using ola::rdm::RDMReply; +using ola::rdm::RDMRequest; +using ola::rdm::RDMResponse; +using ola::rdm::RDMSetRequest; +using ola::rdm::UID; +using ola::rdm::UIDSet; + +DEFINE_string(manager_uid, "7a70:00000002", "The UID of the manager."); +DEFINE_default_bool(set, false, "Send a set rather than a get."); +DEFINE_default_bool(allow_loopback, false, "Include the loopback interface."); +DEFINE_s_string(interface, i, "", + "The interface name (e.g. eth0) or IP address of the network " + "interface to use for LLRP messages."); + +auto_ptr picker( + ola::network::InterfacePicker::NewPicker()); +ola::network::Interface m_interface; +ola::network::UDPSocket m_socket; +uint8_t *m_recv_buffer; +std::auto_ptr manager_uid; +std::auto_ptr m_pid_helper; +ola::SequenceNumber m_llrp_transaction_number_sequence; +ola::SequenceNumber m_rdm_transaction_number_sequence; + +std::string pid_name; +vector rdm_inputs; + +ola::acn::PreamblePacker m_packer; +CID cid = CID::Generate(); +ola::acn::RootSender m_root_sender(cid, true); + +bool CheckCIDAddressedToUs(const CID destination_cid) { + return (destination_cid == CID::LLRPBroadcastCID() || + destination_cid == cid); +} + +void SendLLRPProbeRequest() { + LLRPHeader llrp_header = LLRPHeader(CID::LLRPBroadcastCID(), + m_llrp_transaction_number_sequence.Next()); + + IPV4Address *target_address = IPV4Address::FromString("239.255.250.133"); + + OutgoingUDPTransportImpl transport_impl = OutgoingUDPTransportImpl(&m_socket, &m_packer); + OutgoingUDPTransport transport(&transport_impl, + *target_address, + ola::acn::LLRP_PORT); + + LLRPProbeRequestPDU probe_request( + LLRPProbeRequestPDU::VECTOR_PROBE_REQUEST_DATA, + *UID::FromString("0000:00000000"), + *UID::FromString("ffff:ffffffff"), + false, + false, + UIDSet()); + + ola::acn::LLRPPDU pdu(ola::acn::VECTOR_LLRP_PROBE_REQUEST, llrp_header, &probe_request); + + m_root_sender.SendPDU(ola::acn::VECTOR_ROOT_LLRP, pdu, &transport); + OLA_DEBUG << "Sent PDU"; +} + +void HandleLLRPProbeReply( + const ola::acn::HeaderSet *headers, + const LLRPProbeReplyInflator::LLRPProbeReply &reply) { + OLA_DEBUG << "Potentially handling probe reply from " << reply.uid; + + const LLRPHeader llrp_header = headers->GetLLRPHeader(); + if (!CheckCIDAddressedToUs(llrp_header.DestinationCid())) { + OLA_INFO << "Ignoring probe request as it's not addressed to us or the LLRP broadcast CID"; + return; + } + + const RootHeader root_header = headers->GetRootHeader(); + + OLA_DEBUG << "Source CID: " << root_header.GetCid(); + OLA_DEBUG << "TN: " << llrp_header.TransactionNumber(); + + LLRPHeader rdm_llrp_header = LLRPHeader(root_header.GetCid(), + m_llrp_transaction_number_sequence.Next()); + + IPV4Address *target_address = IPV4Address::FromString("239.255.250.133"); + + OutgoingUDPTransportImpl transport_impl = OutgoingUDPTransportImpl(&m_socket, &m_packer); + OutgoingUDPTransport transport(&transport_impl, + *target_address, + ola::acn::LLRP_PORT); + + bool is_set = FLAGS_set; + + // get the pid descriptor + const ola::rdm::PidDescriptor *pid_descriptor = m_pid_helper->GetDescriptor( + pid_name, + reply.uid.ManufacturerId()); + + uint16_t pid_value; + if (!pid_descriptor && + (ola::PrefixedHexStringToInt(pid_name, &pid_value) || + ola::StringToInt(pid_name, &pid_value))) { + pid_descriptor = m_pid_helper->GetDescriptor( + pid_value, + reply.uid.ManufacturerId()); + } + + if (!pid_descriptor) { + std::cout << "Unknown PID: " << pid_name << std::endl; + std::cout << "Use --list-pids to list the available PIDs." << std::endl; + return; + } + + const ola::messaging::Descriptor *descriptor = NULL; + if (is_set) { + descriptor = pid_descriptor->SetRequest(); + } else { + descriptor = pid_descriptor->GetRequest(); + } + + if (!descriptor) { + std::cout << (is_set ? "SET" : "GET") << " command not supported for " + << pid_name << std::endl; + return; + } + + // attempt to build the message + auto_ptr message(m_pid_helper->BuildMessage( + descriptor, + rdm_inputs)); + + if (!message.get()) { + std::cout << m_pid_helper->SchemaAsString(descriptor); + return; + } + + unsigned int param_data_length; + const uint8_t *param_data = m_pid_helper->SerializeMessage( + message.get(), + ¶m_data_length); + + RDMRequest *request; + if (is_set) { + request = new RDMSetRequest( + *manager_uid, + reply.uid, + m_rdm_transaction_number_sequence.Next(), // transaction # + 1, // port id + 0, // sub device + pid_descriptor->Value(), // param id + param_data, // data + param_data_length); // data length + } else { + request = new RDMGetRequest( + *manager_uid, + reply.uid, + m_rdm_transaction_number_sequence.Next(), // transaction # + 1, // port id + 0, // sub device + pid_descriptor->Value(), // param id + param_data, // data + param_data_length); // data length + } + + ola::io::ByteString raw_reply; + ola::rdm::RDMCommandSerializer::Pack(*request, &raw_reply); + + ola::acn::RDMPDU rdm_reply(raw_reply); + + ola::acn::LLRPPDU pdu(ola::acn::VECTOR_LLRP_RDM_CMD, rdm_llrp_header, &rdm_reply); + + m_root_sender.SendPDU(ola::acn::VECTOR_ROOT_LLRP, pdu, &transport); + OLA_DEBUG << "Sent PDU"; +} + +/** + * Handle an ACK response + */ +void HandleAckResponse(uint16_t manufacturer_id, + bool is_set, + uint16_t pid, + const uint8_t *data, + unsigned int length) { + const ola::rdm::PidDescriptor *pid_descriptor = m_pid_helper->GetDescriptor( + pid, + manufacturer_id); + + if (!pid_descriptor) { + OLA_WARN << "Unknown PID: " << pid << "."; + return; + } + + const ola::messaging::Descriptor *descriptor = NULL; + if (is_set) { + descriptor = pid_descriptor->SetResponse(); + } else { + descriptor = pid_descriptor->GetResponse(); + } + + if (!descriptor) { + OLA_WARN << "Unknown response message: " << (is_set ? "SET" : "GET") << + " " << pid_descriptor->Name(); + return; + } + + auto_ptr message( + m_pid_helper->DeserializeMessage(descriptor, data, length)); + + if (!message.get()) { + OLA_WARN << "Unable to inflate RDM response"; + return; + } + + std::cout << m_pid_helper->PrettyPrintMessage(manufacturer_id, + is_set, + pid, + message.get()); +} + +void HandleRDM( + const ola::acn::HeaderSet *headers, + const string &raw_response) { + IPV4SocketAddress target = headers->GetTransportHeader().Source(); + OLA_INFO << "Got RDM response from " << target; + + if (!CheckCIDAddressedToUs(headers->GetLLRPHeader().DestinationCid())) { + OLA_INFO << "Ignoring RDM response as it's not addressed to us or the LLRP broadcast CID"; + return; + } + + ola::rdm::RDMStatusCode status_code; + // attempt to unpack as a request + ola::rdm::RDMResponse *response = ola::rdm::RDMResponse::InflateFromData( + reinterpret_cast(raw_response.data()), + raw_response.size(), + &status_code); + + OLA_DEBUG << "Got status code " << ola::rdm::StatusCodeToString(status_code); + + if (!response) { + OLA_WARN << "Failed to unpack LLRP RDM message, ignoring request."; + return; + } else { + OLA_DEBUG << "Got RDM response " << response->ToString(); + } + + if (!response->DestinationUID().DirectedToUID(*manager_uid)) { + OLA_WARN << "Destination UID " << response->DestinationUID() << " was not " + << "directed to us"; + return; + } + + OLA_INFO << "Got RDM response from " << response->SourceUID(); + if (response->ResponseType() == ola::rdm::RDM_ACK) { + HandleAckResponse(response->SourceUID().ManufacturerId(), + (response->CommandClass() == + ola::rdm::RDMCommand::SET_COMMAND_RESPONSE), + response->ParamId(), + response->ParamData(), + response->ParamDataSize()); + } else if (response->ResponseType() == ola::rdm::RDM_NACK_REASON) { + uint16_t nack_reason; + if (response->ParamDataSize() != sizeof(nack_reason)) { + OLA_WARN << "Invalid NACK reason size of " << response->ParamDataSize(); + } else { + memcpy(reinterpret_cast(&nack_reason), response->ParamData(), + sizeof(nack_reason)); + nack_reason = NetworkToHost(nack_reason); + std::cout << "Request NACKed: " << + ola::rdm::NackReasonToString(nack_reason) << std::endl; + } + } else { + OLA_WARN << "Unknown RDM response type " + << ola::strings::ToHex(response->ResponseType()); + } +} + +int main(int argc, char* argv[]) { + ola::AppInit(&argc, argv, "[options]", "Run a very simple E1.33 LLRP Manager."); + + if (argc >= 2) { + pid_name = argv[1]; + + // split out rdm message params from the pid name (ignore program name) + rdm_inputs.resize(argc - 2); + for(int i = 0; i < argc - 2; i++) { + rdm_inputs[i] = argv[i+2]; + } + OLA_DEBUG << "Parsed RDM"; + } else { + OLA_INFO << "No RDM to parse"; + } + + m_pid_helper.reset(new PidStoreHelper("")); + m_pid_helper->Init(); + + manager_uid.reset(UID::FromString(FLAGS_manager_uid)); + if (!manager_uid.get()) { + OLA_WARN << "Invalid UID: " << FLAGS_manager_uid; + ola::DisplayUsage(); + exit(ola::EXIT_USAGE); + } else { + OLA_INFO << "Started LLRP Manager with UID " << *manager_uid; + } + + ola::io::SelectServer ss; + + if (!m_socket.Init()) { + return false; + } + std::cout << "Init!" << std::endl; + + std::cout << "Using CID " << cid << std::endl; + + IPV4Address *addr = IPV4Address::FromString("239.255.250.134"); + + if (!m_socket.Bind(IPV4SocketAddress(*addr, + ola::acn::LLRP_PORT))) { + return false; + } + std::cout << "Bind!" << std::endl; + + const std::string m_preferred_ip = FLAGS_interface; + ola::network::InterfacePicker::Options options; + options.include_loopback = FLAGS_allow_loopback; + if (!picker->ChooseInterface(&m_interface, m_preferred_ip, options)) { + OLA_INFO << "Failed to find an interface"; + return false; + } + + std::cout << "IF " << m_interface << std::endl; + + // If we enable multicast loopback, we can test two bits of software on the + // same machine, but we get, and must ignore, all our own requests too + if (!m_socket.JoinMulticast(m_interface.ip_address, *addr, true)) { + OLA_WARN << "Failed to join multicast group " << addr; + return false; + } + + ola::acn::RootInflator root_inflator; + ola::acn::LLRPInflator llrp_inflator; + ola::acn::LLRPProbeReplyInflator llrp_probe_reply_inflator; + llrp_probe_reply_inflator.SetLLRPProbeReplyHandler( + ola::NewCallback(&HandleLLRPProbeReply)); + ola::acn::RDMInflator llrp_rdm_inflator(ola::acn::VECTOR_LLRP_RDM_CMD); + llrp_rdm_inflator.SetGenericRDMHandler( + ola::NewCallback(&HandleRDM)); + + // setup all the inflators + root_inflator.AddInflator(&llrp_inflator); + llrp_inflator.AddInflator(&llrp_probe_reply_inflator); + llrp_inflator.AddInflator(&llrp_rdm_inflator); + + IncomingUDPTransport m_incoming_udp_transport(&m_socket, &root_inflator); + m_socket.SetOnData(ola::NewCallback(&m_incoming_udp_transport, + &IncomingUDPTransport::Receive)); + ss.AddReadDescriptor(&m_socket); + + // TODO(Peter): Add the ability to filter on UID or UID+CID to avoid the probing + + // TODO(Peter): Send this three times + // TODO(Peter): Deal with known UID list etc and proper discovery + SendLLRPProbeRequest(); + ss.Run(); + + return 0; +} diff --git a/tools/e133/llrp-target.cpp b/tools/e133/llrp-target.cpp new file mode 100644 index 000000000..b7159ec8c --- /dev/null +++ b/tools/e133/llrp-target.cpp @@ -0,0 +1,342 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * llrp-target.cpp + * Run a very simple E1.33 LLRP Target. + * Copyright (C) 2020 Peter Newman + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "libs/acn/HeaderSet.h" +#include "libs/acn/LLRPHeader.h" +#include "libs/acn/LLRPInflator.h" +#include "libs/acn/LLRPPDU.h" +#include "libs/acn/LLRPProbeReplyPDU.h" +#include "libs/acn/LLRPProbeRequestInflator.h" +#include "libs/acn/PreamblePacker.h" +#include "libs/acn/RDMInflator.h" +#include "libs/acn/RDMPDU.h" +#include "libs/acn/RootHeader.h" +#include "libs/acn/RootInflator.h" +#include "libs/acn/RootSender.h" +#include "libs/acn/Transport.h" +#include "libs/acn/UDPTransport.h" + +using std::string; +using std::vector; +using std::auto_ptr; + +using ola::acn::CID; +using ola::acn::IncomingUDPTransport; +using ola::acn::LLRPHeader; +using ola::acn::LLRPProbeReplyPDU; +using ola::acn::LLRPProbeRequestInflator; +using ola::acn::OutgoingUDPTransport; +using ola::acn::OutgoingUDPTransportImpl; +using ola::acn::RootHeader; +using ola::network::Interface; +using ola::network::IPV4Address; +using ola::network::IPV4SocketAddress; +using ola::network::MACAddress; +using ola::rdm::RDMReply; +using ola::rdm::RDMResponse; +using ola::rdm::UID; + +DEFINE_string(uid, "7a70:00000001", "The UID of the target."); + +auto_ptr picker( + ola::network::InterfacePicker::NewPicker()); +ola::network::Interface m_interface; +const std::string m_preferred_ip; +ola::network::UDPSocket m_socket; +uint8_t *m_recv_buffer; +std::auto_ptr target_uid; +std::auto_ptr dummy_responder; + +ola::acn::PreamblePacker m_packer; +CID cid = CID::Generate(); +ola::acn::RootSender m_root_sender(cid, true); + +bool CheckCIDAddressedToUs(const CID destination_cid) { + return (destination_cid == CID::LLRPBroadcastCID() || + destination_cid == cid); +} + +bool CompareInterfaceMACs(Interface a, Interface b) { + return a.hw_address < b.hw_address; +} + +Interface FindLowestMAC() { + // TODO(Peter): Get some clarification on whether we only care about active + // interfaces, or any installed ones? + // TODO(Peter): Work out what to do here if running on localhost only? Return + // 00:00:00:00:00:00 + vector interfaces = picker->GetInterfaces(false); + vector::iterator result = std::min_element(interfaces.begin(), + interfaces.end(), + CompareInterfaceMACs); + return *result; +} + +void HandleLLRPProbeRequest( + const ola::acn::HeaderSet *headers, + const LLRPProbeRequestInflator::LLRPProbeRequest &request) { + OLA_DEBUG << "Potentially handling probe from " << request.lower << " to " + << request.upper; + + const LLRPHeader llrp_header = headers->GetLLRPHeader(); + if (!CheckCIDAddressedToUs(llrp_header.DestinationCid())) { + OLA_INFO << "Ignoring probe request as it's not addressed to us or the LLRP broadcast CID"; + return; + } + + if ((*target_uid < request.lower) || (*target_uid > request.upper)) { + OLA_INFO << "Ignoring probe request as we are not in the target UID range"; + return; + } + + OLA_DEBUG << "Known UIDs are: " << request.known_uids; + + if (request.known_uids.Contains(*target_uid)) { + OLA_INFO << "Ignoring probe request as we are already in the known UID " + << "list"; + return; + } + + // TODO(Peter): Check the filter bits! + + const RootHeader root_header = headers->GetRootHeader(); + + OLA_DEBUG << "Source CID: " << root_header.GetCid(); + OLA_DEBUG << "TN: " << llrp_header.TransactionNumber(); + + LLRPHeader reply_llrp_header = LLRPHeader(root_header.GetCid(), + llrp_header.TransactionNumber()); + + IPV4Address *target_address = IPV4Address::FromString("239.255.250.134"); + + OutgoingUDPTransportImpl transport_impl = OutgoingUDPTransportImpl(&m_socket, &m_packer); + OutgoingUDPTransport transport(&transport_impl, + *target_address, + ola::acn::LLRP_PORT); + + LLRPProbeReplyPDU probe_reply( + LLRPProbeReplyPDU::VECTOR_PROBE_REPLY_DATA, + *target_uid, + FindLowestMAC().hw_address, + LLRPProbeReplyPDU::LLRP_COMPONENT_TYPE_NON_RDMNET); + + ola::acn::LLRPPDU pdu(ola::acn::VECTOR_LLRP_PROBE_REPLY, reply_llrp_header, &probe_reply); + + // TODO(Peter): Delay sending by 0 to LLRP_MAX_BACKOFF! + + m_root_sender.SendPDU(ola::acn::VECTOR_ROOT_LLRP, pdu, &transport); + OLA_DEBUG << "Sent PDU"; +} + +void RDMRequestComplete( + ola::acn::HeaderSet headers, + ola::rdm::RDMReply *reply) { + OLA_INFO << "Got RDM reply to send"; + OLA_DEBUG << reply->ToString(); + + const RDMResponse *response = reply->Response(); + uint8_t response_type = response->ResponseType(); + + if (response_type == ola::rdm::RDM_ACK_TIMER || + response_type == ola::rdm::ACK_OVERFLOW) { + // Technically we shouldn't have even actioned the request but we can't + // really do that in OLA, as we don't know what it might return until we've + // done it + OLA_DEBUG << "Got a disallowed ACK, mangling to NR_ACTION_NOT_SUPPORTED"; + response = NackWithReason(response, ola::rdm::NR_ACTION_NOT_SUPPORTED); + } else { + OLA_DEBUG << "Got an acceptable response type: " + << (unsigned int)response_type; + } + + const RootHeader root_header = headers.GetRootHeader(); + const LLRPHeader llrp_header = headers.GetLLRPHeader(); + + OLA_DEBUG << "Source CID: " << root_header.GetCid(); + OLA_DEBUG << "TN: " << llrp_header.TransactionNumber(); + + LLRPHeader reply_llrp_header = LLRPHeader(root_header.GetCid(), + llrp_header.TransactionNumber()); + + IPV4Address *target_address = IPV4Address::FromString("239.255.250.134"); + + OutgoingUDPTransportImpl transport_impl = OutgoingUDPTransportImpl(&m_socket, &m_packer); + OutgoingUDPTransport transport(&transport_impl, + *target_address, + ola::acn::LLRP_PORT); + + ola::io::ByteString raw_reply; + ola::rdm::RDMCommandSerializer::Pack(*response, &raw_reply); + + ola::acn::RDMPDU rdm_reply(raw_reply); + + ola::acn::LLRPPDU pdu(ola::acn::VECTOR_LLRP_RDM_CMD, reply_llrp_header, &rdm_reply); + + m_root_sender.SendPDU(ola::acn::VECTOR_ROOT_LLRP, pdu, &transport); + OLA_DEBUG << "Sent RDM PDU"; +} + +void HandleRDM( + const ola::acn::HeaderSet *headers, + const string &raw_request) { + IPV4SocketAddress target = headers->GetTransportHeader().Source(); + OLA_INFO << "Got RDM request from " << target; + + if (!CheckCIDAddressedToUs(headers->GetLLRPHeader().DestinationCid())) { + OLA_INFO << "Ignoring RDM request as it's not addressed to us or the LLRP broadcast CID"; + return; + } + + // attempt to unpack as a request + ola::rdm::RDMRequest *request = ola::rdm::RDMRequest::InflateFromData( + reinterpret_cast(raw_request.data()), + raw_request.size()); + + if (!request) { + OLA_WARN << "Failed to unpack LLRP RDM message, ignoring request."; + return; + } else { + OLA_DEBUG << "Got RDM request " << request->ToString(); + } + + if (!request->DestinationUID().DirectedToUID(*target_uid)) { + OLA_WARN << "Destination UID " << request->DestinationUID() << " was not " + << "directed to us"; + return; + } + + if (!((request->SubDevice() == ola::rdm::ROOT_RDM_DEVICE) || + (request->SubDevice() == ola::rdm::ALL_RDM_SUBDEVICES))) { + OLA_WARN << "Subdevice " << request->SubDevice() << " was not the root or " + << "broadcast subdevice, NACKing"; + // Immediately send a NACK + RDMReply reply( + ola::rdm::RDM_COMPLETED_OK, + NackWithReason(request, ola::rdm::NR_SUB_DEVICE_OUT_OF_RANGE)); + RDMRequestComplete(*headers, &reply); + } else { + // Dispatch the message to the responder + dummy_responder->SendRDMRequest( + request, + ola::NewSingleCallback(&RDMRequestComplete, + *headers)); + } +} + +int main(int argc, char* argv[]) { + ola::AppInit(&argc, argv, "[options]", "Run a very simple E1.33 LLRP Target."); + + target_uid.reset(UID::FromString(FLAGS_uid)); + if (!target_uid.get()) { + OLA_WARN << "Invalid UID: " << FLAGS_uid; + ola::DisplayUsage(); + exit(ola::EXIT_USAGE); + } else { + OLA_INFO << "Started LLRP Target with UID " << *target_uid; + } + + dummy_responder.reset(new ola::rdm::DummyResponder(*target_uid)); + + ola::io::SelectServer ss; + + if (!m_socket.Init()) { + return false; + } + std::cout << "Init!" << std::endl; + + std::cout << "Using CID " << cid << std::endl; + + if (!m_socket.Bind(IPV4SocketAddress(IPV4Address::WildCard(), + ola::acn::LLRP_PORT))) { + return false; + } + std::cout << "Bind!" << std::endl; + + IPV4Address *addr = IPV4Address::FromString("239.255.250.133"); + + ola::network::InterfacePicker::Options options; + options.include_loopback = false; + if (!picker->ChooseInterface(&m_interface, m_preferred_ip, options)) { + OLA_INFO << "Failed to find an interface"; + return false; + } + + std::cout << "IF " << m_interface << std::endl; + + // If we enable multicast loopback, we can test two bits of software on the + // same machine, but we get, and must ignore, all our own requests too + if (!m_socket.JoinMulticast(m_interface.ip_address, *addr, true)) { + OLA_WARN << "Failed to join multicast group " << addr; + return false; + } + + ola::acn::RootInflator root_inflator; + ola::acn::LLRPInflator llrp_inflator; + ola::acn::LLRPProbeRequestInflator llrp_probe_request_inflator; + llrp_probe_request_inflator.SetLLRPProbeRequestHandler( + ola::NewCallback(&HandleLLRPProbeRequest)); + ola::acn::RDMInflator llrp_rdm_inflator(ola::acn::VECTOR_LLRP_RDM_CMD); + llrp_rdm_inflator.SetGenericRDMHandler( + ola::NewCallback(&HandleRDM)); + + // setup all the inflators + root_inflator.AddInflator(&llrp_inflator); + llrp_inflator.AddInflator(&llrp_probe_request_inflator); + llrp_inflator.AddInflator(&llrp_rdm_inflator); + + IncomingUDPTransport m_incoming_udp_transport(&m_socket, &root_inflator); + m_socket.SetOnData(ola::NewCallback(&m_incoming_udp_transport, + &IncomingUDPTransport::Receive)); + ss.AddReadDescriptor(&m_socket); + + ss.Run(); + + return 0; +} diff --git a/tools/rdm/ModelCollector.py b/tools/rdm/ModelCollector.py index f688bfe26..e26c61392 100644 --- a/tools/rdm/ModelCollector.py +++ b/tools/rdm/ModelCollector.py @@ -220,7 +220,11 @@ def _HandleDeviceInfo(self, data): 'sensor_count', 'sub_device_count'] for field in fields: - this_device[field] = data[field] + if field in data: + this_device[field] = data[field] + else: + print('Failed to get %s from device info for UID %s' + % (field, self.uid)) this_device['software_versions'][data['software_version']] = { 'languages': [], diff --git a/tools/rdm/TestCategory.py b/tools/rdm/TestCategory.py index cecd7aa9e..81830f07b 100644 --- a/tools/rdm/TestCategory.py +++ b/tools/rdm/TestCategory.py @@ -34,6 +34,7 @@ class TestCategory(object): 'DISPLAY_SETTINGS': 'Display Settings', 'CONFIGURATION': 'Configuration', 'CONTROL': 'Control', + 'E133_MANAGEMENT': 'E1.33 (RDMnet) Management', # And others for things that don't quite fit 'CORE': 'Core Functionality', 'ERROR_CONDITIONS': 'Error Conditions', diff --git a/tools/rdm/TestDefinitions.py b/tools/rdm/TestDefinitions.py index e97b0f58b..a2119eb0b 100644 --- a/tools/rdm/TestDefinitions.py +++ b/tools/rdm/TestDefinitions.py @@ -28,6 +28,7 @@ RDM_MANUFACTURER_SD_MAX, RDM_MANUFACTURER_SD_MIN, RDM_MAX_DOMAIN_NAME_LENGTH, RDM_MAX_HOSTNAME_LENGTH, + RDM_MAX_SEARCH_DOMAIN_LENGTH, RDM_MAX_SERIAL_NUMBER_LENGTH, RDM_MAX_STRING_LENGTH, RDM_MAX_TEST_DATA_PATTERN_LENGTH, @@ -8450,11 +8451,14 @@ class AllSubDevicesGetSearchDomain(TestMixins.AllSubDevicesGetMixin, PID = 'SEARCH_DOMAIN' -# class GetSearchDomain(TestMixins., -# OptionalParameterTestFixture): -# CATEGORY = TestCategory. -# PID = 'SEARCH_DOMAIN' -# TODO(peter): Test get +class GetSearchDomain(TestMixins.GetStringMixin, + OptionalParameterTestFixture): + CATEGORY = TestCategory.E133_MANAGEMENT + PID = 'SEARCH_DOMAIN' + EXPECTED_FIELDS = ['search_domain'] + PROVIDES = ['search_domain'] + MAX_LENGTH = RDM_MAX_SEARCH_DOMAIN_LENGTH + # TODO(Peter): Validate invalid search domains? class GetSearchDomainWithData(TestMixins.GetWithDataMixin, @@ -8465,7 +8469,7 @@ class GetSearchDomainWithData(TestMixins.GetWithDataMixin, # class SetSearchDomain(TestMixins., # OptionalParameterTestFixture): -# CATEGORY = TestCategory. +# CATEGORY = TestCategory.E133_MANAGEMENT # PID = 'SEARCH_DOMAIN' # TODO(peter): Test set @@ -8476,12 +8480,6 @@ class SetSearchDomainWithNoData(TestMixins.SetWithNoDataMixin, PID = 'SEARCH_DOMAIN' -class SetSearchDomainWithExtraData(TestMixins.SetWithDataMixin, - OptionalParameterTestFixture): - """Send a SET SEARCH_DOMAIN command with extra data.""" - PID = 'SEARCH_DOMAIN' - - class AllSubDevicesGetBrokerStatus(TestMixins.AllSubDevicesGetMixin, OptionalParameterTestFixture): """Send a get BROKER_STATUS to ALL_SUB_DEVICES.""" @@ -8490,7 +8488,7 @@ class AllSubDevicesGetBrokerStatus(TestMixins.AllSubDevicesGetMixin, # class GetBrokerStatus(TestMixins., # OptionalParameterTestFixture): -# CATEGORY = TestCategory. +# CATEGORY = TestCategory.E133_MANAGEMENT # PID = 'BROKER_STATUS' # TODO(peter): Test get @@ -8503,7 +8501,7 @@ class GetBrokerStatusWithData(TestMixins.GetWithDataMixin, # class SetBrokerStatus(TestMixins., # OptionalParameterTestFixture): -# CATEGORY = TestCategory. +# CATEGORY = TestCategory.E133_MANAGEMENT # PID = 'BROKER_STATUS' # TODO(peter): Test set diff --git a/tools/rdm/list_rdm_tests.py b/tools/rdm/list_rdm_tests.py index 1377e6bc1..dd898e57f 100755 --- a/tools/rdm/list_rdm_tests.py +++ b/tools/rdm/list_rdm_tests.py @@ -24,6 +24,8 @@ import sys import textwrap +from ola.RDMConstants import RDM_MAX_PARAM_DATA_LENGTH + from ola import PidStore __author__ = 'Peter Newman' @@ -152,20 +154,22 @@ def GetWithExtraData(names, pid, pid_test_base_name, get_size): print(' """GET %s with more than %d byte%s of data."""' % (pid.name, get_size, 's' if get_size > 1 else '')) print(' PID = \'%s\'' % (pid.name)) + if get_size >= RDM_MAX_PARAM_DATA_LENGTH: + print((" # TODO(%s): Should we even have this test, get_size is " + "already at least the max PDL!") % (getpass.getuser())) dummy_data = GenerateDummyData(get_size) if dummy_data is None: print((" # DATA = b'foo' # TODO(%s): Specify extra data if this isn't " "enough.") % (getpass.getuser())) - print(" # Ensure the first %d bytes are sane/valid." % (get_size)) elif dummy_data != 'foo': # Doesn't match default, explicitly set value print((" DATA = b'%s' # TODO(%s): Specify extra data if this isn't " "enough.") % (dummy_data, getpass.getuser())) - print(" # Ensure the first %d bytes are sane/valid." % (get_size)) else: print((" # DATA = b'%s' # TODO(%s): Specify extra data if this isn't " "enough.") % (dummy_data, getpass.getuser())) - print(" # Ensure the first %d bytes are sane/valid." % (get_size)) + + print(" # Ensure the first %d bytes are sane/valid." % (get_size)) print('') print('') @@ -279,20 +283,22 @@ def SetWithExtraData(names, pid, pid_test_base_name, set_size): 'OptionalParameterTestFixture']) print(' """Send a SET %s command with extra data."""' % (pid.name)) print(' PID = \'%s\'' % (pid.name)) + if set_size >= RDM_MAX_PARAM_DATA_LENGTH: + print((" # TODO(%s): Should we even have this test, set_size is " + "already at least the max PDL!") % (getpass.getuser())) dummy_data = GenerateDummyData(set_size) if dummy_data is None: print((" # DATA = b'foo' # TODO(%s): Specify extra data if this isn't " "enough.") % (getpass.getuser())) - print(" # Ensure the first %d bytes are sane/valid." % (set_size)) elif dummy_data != 'foo': # Doesn't match default, explicitly set value print((" DATA = b'%s' # TODO(%s): Specify extra data if this isn't " "enough.") % (dummy_data, getpass.getuser())) - print(" # Ensure the first %d bytes are sane/valid." % (set_size)) else: print((" # DATA = b'%s' # TODO(%s): Specify extra data if this isn't " "enough.") % (dummy_data, getpass.getuser())) - print(" # Ensure the first %d bytes are sane/valid." % (set_size)) + + print(" # Ensure the first %d bytes are sane/valid." % (set_size)) print('') print('')