diff --git a/common/rdm/GeneralResponder.cpp b/common/rdm/GeneralResponder.cpp new file mode 100644 index 000000000..1b671fddf --- /dev/null +++ b/common/rdm/GeneralResponder.cpp @@ -0,0 +1,295 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * GeneralResponder.cpp + * Copyright (C) 2025 Peter Newman + */ + +#if HAVE_CONFIG_H +#include +#endif // HAVE_CONFIG_H + +#include +#include +#include +#include "ola/Constants.h" +#include "ola/Logging.h" +#include "ola/strings/Format.h" +#include "ola/base/Array.h" +#include "ola/network/NetworkUtils.h" +#include "ola/rdm/GeneralResponder.h" +#include "ola/rdm/OpenLightingEnums.h" +#include "ola/rdm/RDMEnums.h" +#include "ola/rdm/ResponderHelper.h" +#include "ola/stl/STLUtils.h" + +namespace ola { +namespace rdm { + +using ola::network::HostToNetwork; +using ola::network::NetworkToHost; +using std::string; +using std::vector; +using std::auto_ptr; + +GeneralResponder::RDMOps *GeneralResponder::RDMOps::instance = NULL; + +const ResponderOps::ParamHandler + GeneralResponder::PARAM_HANDLERS[] = { + { PID_DEVICE_INFO, + &GeneralResponder::GetDeviceInfo, + NULL}, + { PID_PRODUCT_DETAIL_ID_LIST, + &GeneralResponder::GetProductDetailList, + NULL}, + { PID_DEVICE_MODEL_DESCRIPTION, + &GeneralResponder::GetDeviceModelDescription, + NULL}, + { PID_MANUFACTURER_LABEL, + &GeneralResponder::GetManufacturerLabel, + NULL}, + { PID_DEVICE_LABEL, + &GeneralResponder::GetDeviceLabel, + NULL}, + { PID_SOFTWARE_VERSION_LABEL, + &GeneralResponder::GetSoftwareVersionLabel, + NULL}, + { PID_IDENTIFY_DEVICE, + &GeneralResponder::GetIdentify, + &GeneralResponder::SetIdentify}, + { PID_MANUFACTURER_URL, + &GeneralResponder::GetManufacturerURL, + NULL}, + { PID_PRODUCT_URL, + &GeneralResponder::GetProductURL, + NULL}, + { PID_FIRMWARE_URL, + &GeneralResponder::GetFirmwareURL, + NULL}, + { PID_SHIPPING_LOCK, + &GeneralResponder::GetShippingLock, + &GeneralResponder::SetShippingLock}, + { PID_SERIAL_NUMBER, + &GeneralResponder::GetSerialNumber, + NULL}, + { PID_TEST_DATA, + &GeneralResponder::GetTestData, + &GeneralResponder::SetTestData}, + { PID_COMMS_STATUS_NSC, + &GeneralResponder::GetCommsStatusNSC, + &GeneralResponder::SetCommsStatusNSC}, + { PID_LIST_TAGS, + &GeneralResponder::GetListTags, + NULL}, + { PID_ADD_TAG, + NULL, + &GeneralResponder::SetAddTag}, + { PID_REMOVE_TAG, + NULL, + &GeneralResponder::SetRemoveTag}, + { PID_CHECK_TAG, + &GeneralResponder::GetCheckTag, + NULL}, + { PID_CLEAR_TAGS, + NULL, + &GeneralResponder::SetClearTags}, + { PID_DEVICE_UNIT_NUMBER, + &GeneralResponder::GetDeviceUnitNumber, + &GeneralResponder::SetDeviceUnitNumber}, + { 0, NULL, NULL}, +}; + + +/** + * New GeneralResponder + */ +GeneralResponder::GeneralResponder(const UID &uid) + : m_uid(uid), + m_identify_mode(false), + m_shipping_lock(SHIPPING_LOCK_STATE_PARTIALLY_LOCKED), + m_nsc_status(NSCStatus::NSCStatusOptions()), + m_device_unit_number(0) { +} + + +GeneralResponder::~GeneralResponder() { + m_tags.Clear(); +} + +/* + * Handle an RDM Request + */ +void GeneralResponder::SendRDMRequest(RDMRequest *request, + RDMCallback *callback) { + RDMOps::Instance()->HandleRDMRequest(this, m_uid, ROOT_RDM_DEVICE, request, + callback); +} + +void GeneralResponder::UpdateNSCStats(const DmxBuffer &buffer) { + m_nsc_status.UpdateStats(buffer); +} + +RDMResponse *GeneralResponder::GetDeviceInfo( + const RDMRequest *request) { + return ResponderHelper::GetDeviceInfo( + request, OLA_E137_5_MODEL, PRODUCT_CATEGORY_TEST, + 1, 0, 1, 1, ZERO_FOOTPRINT_DMX_ADDRESS, 0, 0); +} + +RDMResponse *GeneralResponder::GetProductDetailList( + const RDMRequest *request) { + // Shortcut for only one item in the vector + return ResponderHelper::GetProductDetailList( + request, vector(1, PRODUCT_DETAIL_TEST)); +} + +RDMResponse *GeneralResponder::GetIdentify( + const RDMRequest *request) { + return ResponderHelper::GetBoolValue(request, m_identify_mode); +} + +RDMResponse *GeneralResponder::SetIdentify( + const RDMRequest *request) { + bool old_value = m_identify_mode; + RDMResponse *response = ResponderHelper::SetBoolValue( + request, &m_identify_mode); + if (m_identify_mode != old_value) { + OLA_INFO << "General Device " << m_uid << ", identify mode " + << (m_identify_mode ? "on" : "off"); + } + return response; +} + +RDMResponse *GeneralResponder::GetDeviceModelDescription( + const RDMRequest *request) { + return ResponderHelper::GetString(request, "OLA General Parameter Device"); +} + +RDMResponse *GeneralResponder::GetManufacturerLabel( + const RDMRequest *request) { + return ResponderHelper::GetString(request, OLA_MANUFACTURER_LABEL); +} + +RDMResponse *GeneralResponder::GetDeviceLabel(const RDMRequest *request) { + return ResponderHelper::GetString(request, "General Parameter Device"); +} + +RDMResponse *GeneralResponder::GetSoftwareVersionLabel( + const RDMRequest *request) { + return ResponderHelper::GetString(request, string("OLA Version ") + VERSION); +} + +RDMResponse *GeneralResponder::GetManufacturerURL( + const RDMRequest *request) { + return ResponderHelper::GetString( + // TODO(Peter): This field's length isn't limited in the spec + request, OLA_MANUFACTURER_URL, 0, UINT8_MAX); +} + +RDMResponse *GeneralResponder::GetProductURL( + const RDMRequest *request) { + return ResponderHelper::GetString( + request, + "https://openlighting.org/rdm-tools/dummy-responders/", + 0, + UINT8_MAX); // TODO(Peter): This field's length isn't limited in the spec +} + +RDMResponse *GeneralResponder::GetFirmwareURL( + const RDMRequest *request) { + return ResponderHelper::GetString( + request, + "https://github.com/OpenLightingProject/ola", + 0, + UINT8_MAX); // TODO(Peter): This field's length isn't limited in the spec +} + +RDMResponse *GeneralResponder::GetShippingLock( + const RDMRequest *request) { + uint8_t value = m_shipping_lock; + return ResponderHelper::GetUInt8Value(request, value); +} + +RDMResponse *GeneralResponder::SetShippingLock( + const RDMRequest *request) { + uint8_t new_value; + if (!ResponderHelper::ExtractUInt8(request, &new_value)) { + return NackWithReason(request, NR_FORMAT_ERROR); + } + + // SHIPPING_LOCK_STATE_PARTIALLY_LOCKED isn't allowed within SET + if (new_value > static_cast(SHIPPING_LOCK_STATE_LOCKED)) { + return NackWithReason(request, NR_DATA_OUT_OF_RANGE); + } + + m_shipping_lock = static_cast(new_value); + return ResponderHelper::EmptySetResponse(request); +} + +RDMResponse *GeneralResponder::GetSerialNumber(const RDMRequest *request) { + // Return the device part of the UID + std::ostringstream str; + str << ola::strings::ToHex(m_uid.DeviceId(), false); + return ResponderHelper::GetString(request, + str.str(), + 0, + MAX_RDM_SERIAL_NUMBER_LENGTH); +} + +RDMResponse *GeneralResponder::GetTestData(const RDMRequest *request) { + return ResponderHelper::GetTestData(request); +} + +RDMResponse *GeneralResponder::SetTestData(const RDMRequest *request) { + return ResponderHelper::SetTestData(request); +} + +RDMResponse *GeneralResponder::GetCommsStatusNSC(const RDMRequest *request) { + return ResponderHelper::GetCommsStatusNSC(request, &m_nsc_status); +} + +RDMResponse *GeneralResponder::SetCommsStatusNSC(const RDMRequest *request) { + return ResponderHelper::SetCommsStatusNSC(request, &m_nsc_status); +} + +RDMResponse *GeneralResponder::GetListTags(const RDMRequest *request) { + return ResponderHelper::GetListTags(request, &m_tags); +} + +RDMResponse *GeneralResponder::SetAddTag(const RDMRequest *request) { + return ResponderHelper::SetAddTag(request, &m_tags); +} + +RDMResponse *GeneralResponder::SetRemoveTag(const RDMRequest *request) { + return ResponderHelper::SetRemoveTag(request, &m_tags); +} + +RDMResponse *GeneralResponder::GetCheckTag(const RDMRequest *request) { + return ResponderHelper::GetCheckTag(request, &m_tags); +} + +RDMResponse *GeneralResponder::SetClearTags(const RDMRequest *request) { + return ResponderHelper::SetClearTags(request, &m_tags); +} + +RDMResponse *GeneralResponder::GetDeviceUnitNumber(const RDMRequest *request) { + return ResponderHelper::GetUInt32Value(request, m_device_unit_number); +} + +RDMResponse *GeneralResponder::SetDeviceUnitNumber(const RDMRequest *request) { + return ResponderHelper::SetUInt32Value(request, &m_device_unit_number); +} +} // namespace rdm +} // namespace ola diff --git a/common/rdm/Makefile.mk b/common/rdm/Makefile.mk index c9142ba9d..694e8c615 100644 --- a/common/rdm/Makefile.mk +++ b/common/rdm/Makefile.mk @@ -18,6 +18,7 @@ common_libolacommon_la_SOURCES += \ common/rdm/DummyResponder.cpp \ common/rdm/FakeNetworkManager.cpp \ common/rdm/FakeNetworkManager.h \ + common/rdm/GeneralResponder.cpp \ common/rdm/GroupSizeCalculator.cpp \ common/rdm/GroupSizeCalculator.h \ common/rdm/MessageDeserializer.cpp \ diff --git a/include/ola/rdm/GeneralResponder.h b/include/ola/rdm/GeneralResponder.h new file mode 100644 index 000000000..a777bb0ff --- /dev/null +++ b/include/ola/rdm/GeneralResponder.h @@ -0,0 +1,114 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * GeneralResponder.h + * Copyright (C) 2025 Peter Newman + */ + +/** + * @addtogroup rdm_resp + * @{ + * @file GeneralResponder.h + * @brief An RDM responder that supports E1.37-5 PIDs + * @} + */ + +#ifndef INCLUDE_OLA_RDM_GENERALRESPONDER_H_ +#define INCLUDE_OLA_RDM_GENERALRESPONDER_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace ola { +namespace rdm { + +/** + * A responder with E1.37-5 support. + */ +class GeneralResponder: public RDMControllerInterface { + public: + explicit GeneralResponder(const UID &uid); + ~GeneralResponder(); + + void SendRDMRequest(RDMRequest *request, RDMCallback *callback); + + void UpdateNSCStats(const DmxBuffer &buffer); + + private: + /** + * The RDM Operations for the GeneralResponder. + */ + class RDMOps : public ResponderOps { + public: + static RDMOps *Instance() { + if (!instance) { + instance = new RDMOps(); + } + return instance; + } + + private: + RDMOps() : ResponderOps(PARAM_HANDLERS) {} + + static RDMOps *instance; + }; + + const UID m_uid; + bool m_identify_mode; + rdm_shipping_lock_state m_shipping_lock; + NSCStatus m_nsc_status; + TagSet m_tags; + uint32_t m_device_unit_number; + + RDMResponse *GetDeviceInfo(const RDMRequest *request); + RDMResponse *GetProductDetailList(const RDMRequest *request); + RDMResponse *GetIdentify(const RDMRequest *request); + RDMResponse *SetIdentify(const RDMRequest *request); + RDMResponse *GetManufacturerLabel(const RDMRequest *request); + RDMResponse *GetDeviceLabel(const RDMRequest *request); + RDMResponse *GetDeviceModelDescription(const RDMRequest *request); + RDMResponse *GetSoftwareVersionLabel(const RDMRequest *request); + RDMResponse *GetManufacturerURL(const RDMRequest *request); + RDMResponse *GetProductURL(const RDMRequest *request); + RDMResponse *GetFirmwareURL(const RDMRequest *request); + RDMResponse *GetShippingLock(const RDMRequest *request); + RDMResponse *SetShippingLock(const RDMRequest *request); + RDMResponse *GetSerialNumber(const RDMRequest *request); + RDMResponse *GetTestData(const RDMRequest *request); + RDMResponse *SetTestData(const RDMRequest *request); + RDMResponse *GetCommsStatusNSC(const RDMRequest *request); + RDMResponse *SetCommsStatusNSC(const RDMRequest *request); + RDMResponse *GetListTags(const RDMRequest *request); + RDMResponse *SetAddTag(const RDMRequest *request); + RDMResponse *SetRemoveTag(const RDMRequest *request); + RDMResponse *GetCheckTag(const RDMRequest *request); + RDMResponse *SetClearTags(const RDMRequest *request); + RDMResponse *GetDeviceUnitNumber(const RDMRequest *request); + RDMResponse *SetDeviceUnitNumber(const RDMRequest *request); + + static const ResponderOps::ParamHandler PARAM_HANDLERS[]; +}; +} // namespace rdm +} // namespace ola +#endif // INCLUDE_OLA_RDM_GENERALRESPONDER_H_ diff --git a/include/ola/rdm/Makefile.mk b/include/ola/rdm/Makefile.mk index 11c9a4704..7bb3c477b 100644 --- a/include/ola/rdm/Makefile.mk +++ b/include/ola/rdm/Makefile.mk @@ -11,6 +11,7 @@ olardminclude_HEADERS = \ include/ola/rdm/DimmerSubDevice.h \ include/ola/rdm/DiscoveryAgent.h \ include/ola/rdm/DummyResponder.h \ + include/ola/rdm/GeneralResponder.h \ include/ola/rdm/MessageDeserializer.h \ include/ola/rdm/MessageSerializer.h \ include/ola/rdm/MovingLightResponder.h \ diff --git a/include/ola/rdm/OpenLightingEnums.h b/include/ola/rdm/OpenLightingEnums.h index 9260ce920..c1b12c42b 100644 --- a/include/ola/rdm/OpenLightingEnums.h +++ b/include/ola/rdm/OpenLightingEnums.h @@ -80,6 +80,9 @@ typedef enum { OLA_E137_DIMMER_MODEL = 8, // A E1.37-2 responder OLA_E137_2_MODEL = 9, + // A E1.37-5 responder + // TODO(Peter): Document this on the wiki! + OLA_E137_5_MODEL = 10, } ola_rdm_model_id; extern const char OLA_MANUFACTURER_LABEL[]; diff --git a/plugins/dummy/DummyPlugin.cpp b/plugins/dummy/DummyPlugin.cpp index 6c257f031..8c844258c 100644 --- a/plugins/dummy/DummyPlugin.cpp +++ b/plugins/dummy/DummyPlugin.cpp @@ -46,6 +46,7 @@ const char DummyPlugin::DEVICE_NAME[] = "Dummy Device"; const char DummyPlugin::DIMMER_COUNT_KEY[] = "dimmer_count"; const char DummyPlugin::DIMMER_SUBDEVICE_COUNT_KEY[] = "dimmer_subdevice_count"; const char DummyPlugin::DUMMY_DEVICE_COUNT_KEY[] = "dummy_device_count"; +const char DummyPlugin::GENERAL_COUNT_KEY[] = "general_device_count"; const char DummyPlugin::MOVING_LIGHT_COUNT_KEY[] = "moving_light_count"; const char DummyPlugin::NETWORK_COUNT_KEY[] = "network_device_count"; const char DummyPlugin::PLUGIN_NAME[] = "Dummy"; diff --git a/plugins/dummy/DummyPlugin.h b/plugins/dummy/DummyPlugin.h index e8c3f7814..6fb21327f 100644 --- a/plugins/dummy/DummyPlugin.h +++ b/plugins/dummy/DummyPlugin.h @@ -58,11 +58,14 @@ class DummyPlugin: public Plugin { static const char DIMMER_COUNT_KEY[]; static const char DIMMER_SUBDEVICE_COUNT_KEY[]; static const char DUMMY_DEVICE_COUNT_KEY[]; + static const char GENERAL_COUNT_KEY[]; static const char MOVING_LIGHT_COUNT_KEY[]; static const char NETWORK_COUNT_KEY[]; static const char PLUGIN_NAME[]; static const char PLUGIN_PREFIX[]; static const char SENSOR_COUNT_KEY[]; + + // TODO(Peter): Is this redundant? static const char SUBDEVICE_COUNT_KEY[]; }; } // namespace dummy diff --git a/plugins/dummy/DummyPort.cpp b/plugins/dummy/DummyPort.cpp index 73f4c2338..5ad58e7c8 100644 --- a/plugins/dummy/DummyPort.cpp +++ b/plugins/dummy/DummyPort.cpp @@ -28,6 +28,7 @@ #include "ola/rdm/AdvancedDimmerResponder.h" #include "ola/rdm/DimmerResponder.h" #include "ola/rdm/DummyResponder.h" +#include "ola/rdm/GeneralResponder.h" #include "ola/rdm/MovingLightResponder.h" #include "ola/rdm/NetworkResponder.h" #include "ola/rdm/SensorResponder.h" @@ -103,6 +104,8 @@ DummyPort::DummyPort(DummyDevice *parent, &m_responders, &allocator, options.number_of_sensor_responders); AddResponders( &m_responders, &allocator, options.number_of_network_responders); + AddResponders( + &m_responders, &allocator, options.number_of_general_responders); } diff --git a/plugins/dummy/DummyPort.h b/plugins/dummy/DummyPort.h index 658d5beee..3c2da7d6f 100644 --- a/plugins/dummy/DummyPort.h +++ b/plugins/dummy/DummyPort.h @@ -48,7 +48,8 @@ class DummyPort: public BasicOutputPort { number_of_ack_timer_responders(0), number_of_advanced_dimmers(1), number_of_sensor_responders(1), - number_of_network_responders(1) { + number_of_network_responders(1), + number_of_general_responders(1) { } uint8_t number_of_dimmers; @@ -59,6 +60,7 @@ class DummyPort: public BasicOutputPort { uint8_t number_of_advanced_dimmers; uint8_t number_of_sensor_responders; uint8_t number_of_network_responders; + uint8_t number_of_general_responders; }; diff --git a/plugins/dummy/DummyPortTest.cpp b/plugins/dummy/DummyPortTest.cpp index 3634a29be..e5a9975e6 100644 --- a/plugins/dummy/DummyPortTest.cpp +++ b/plugins/dummy/DummyPortTest.cpp @@ -885,7 +885,7 @@ void DummyPortTest::testListInterfaces() { void DummyPortTest::VerifyUIDs(const UIDSet &uids) { UIDSet expected_uids; - for (unsigned int i = 0; i < 6; i++) { + for (unsigned int i = 0; i < 7; i++) { UID uid(OPEN_LIGHTING_ESTA_CODE, 0xffffff00 + i); expected_uids.AddUID(uid); } diff --git a/plugins/dummy/README.md b/plugins/dummy/README.md index 8a6a7c222..f40b808f2 100644 --- a/plugins/dummy/README.md +++ b/plugins/dummy/README.md @@ -35,6 +35,9 @@ The number of sub-devices each dimmer device should have. `dummy_device_count = 1` The number of dummy devices to create. +`general_device_count = 1` +The number of E1.37-5 general devices to create. + `moving_light_count = 1` The number of moving light devices to create.