/* rdm/basicdevice.cpp Copyright (c) 2023 Kevin Matz (kevin.matz@gmail.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "E1.37-1.h" #include "config.h" #include "basicdevice.h" namespace RDM { BasicDevice::BasicDevice() : deviceManufacturerLabel(MY_ESTA_MANUFACTURER_LABEL) , deviceModelDescription("Basic RDM Sub-Device") , deviceModelID(0) , deviceProductCategory(PRODUCT_CATEGORY_NOT_DECLARED) , status_reporting_threshold_(STATUS_ADVISORY) { queued_statuses.emplace(STATUS_ADVISORY, std::queue()); queued_statuses.emplace(STATUS_WARNING, std::queue()); queued_statuses.emplace(STATUS_ERROR, std::queue()); Parameter *parameter; /// \cite RDM 9.2.3 Required Sub-Device Messages /// Devices supporting the use of sub-devices shall support the /// SUPPORTED_PARAMETERS message in order for the controller to determine /// which additional messages are supported by the sub-devices. parameter = addParameter(SUPPORTED_PARAMETERS); parameter->onGet([this](MsgPair msg){actionGetSupportedParameters(msg);}); parameter = addParameter(PARAMETER_DESCRIPTION); parameter->onGet([this](MsgPair msg){actionGetParameterDescription(msg);}); /// \cite RDM 10.5.1 Get Device Info (DEVICE_INFO) /// This parameter is used to retrieve a variety of information about the /// device that is normally required by a controller. parameter = addParameter(DEVICE_INFO); parameter->onGet([this](MsgPair msg){actionGetDeviceInfo(msg);}); /// \cite RDM 10.5.9 Get Software Version Label (SOFTWARE_VERSION_LABEL) /// This parameter is used to get a descriptive ASCII text label for the /// device’s operating software version. parameter = addParameter(SOFTWARE_VERSION_LABEL); parameter->onGet([this](MsgPair msg){actionGetSoftwareVersionLabel(msg);}); /// \cite RDM 10.11.1 Get/Set Identify Device (IDENTIFY_DEVICE) /// This parameter is used for the user to physically identify the device /// represented by the UID. parameter = addParameter(IDENTIFY_DEVICE); parameter->onGet([this](MsgPair msg){actionGetIdentifyDevice(msg);}); parameter->onSet([this](MsgPair msg){actionSetIdentifyDevice(msg);}); /// \cite RDM 10.11.2 Reset Device (RESET_DEVICE) /// This parameter is used to instruct the responder to reset itself. parameter = addParameter(RESET_DEVICE); parameter->onSet([this](MsgPair msg){actionSetResetDevice(msg);}); } BasicDevice::~BasicDevice() { for (auto& [_, parameter] : parameters_) delete parameter; for (auto sensor : sensors_) delete sensor; } /** * @brief BasicDevice::identify * @param state */ void BasicDevice::identify(bool state) { identifying_ = state; } /** * @brief BasicDevice::reset * @param hard */ void BasicDevice::reset(bool hard) { (void)hard; } bool BasicDevice::hasManufacturerPIDs() const { auto [_, parameter] = *parameters_.cend()--; return parameter->isManufacturer(); } /** * @brief BasicDevice::dispatch * @param msg */ void BasicDevice::dispatch(MsgPair msg) { auto [call, response] = msg; if (!parameters_.count(call->mdb.pid)) return response->nak(NR_UNKNOWN_PID); if (call->mdb.pid == last_rx_pid_) ++ack_overflow_page_; else { ack_overflow_page_ = 0; last_rx_pid_ = call->mdb.pid; } parameters_.at(call->mdb.pid)->dispatch(msg); } /** * @brief BasicDevice::enqueueStatus * @param status */ void BasicDevice::enqueueStatus(StatusPtr status) { auto queue = status->status_type & 0xf; if (!queued_statuses.count(queue)) return; if (queue >= status_reporting_threshold_) queued_statuses.at(queue).push(status); } /** * @brief BasicDevice::addParameter * @param metadata * @return */ Parameter *BasicDevice::addParameter(const ParameterDescription metadata) { if (parameters_.count(metadata.pid)) return parameters_.at(metadata.pid); auto [it, _] = parameters_.emplace(metadata.pid, new Parameter(metadata)); return it->second; } /** * @brief BasicDevice::actionGetSupportedParameters * @param msg */ void BasicDevice::actionGetSupportedParameters(MsgPair msg) { auto [_, response] = msg; unsigned int count = parameters_.size(); unsigned int length = count * sizeof(PID); unsigned int lastPage = length / 0xfe; unsigned int first = ack_overflow_page_ * ( 0xfe / sizeof(PID) ); if (first >= count) { ack_overflow_page_ = 0; first = 0; } auto it = parameters_.cbegin(); if (first != 0) std::advance(it, first); while (it != parameters_.cend() && response->mdb.pdl() < 0xfe) { auto [pid, _] = *it; switch (pid) { case DMX_PERSONALITY.pid: case DMX_PERSONALITY_DESCRIPTION.pid: case DMX_START_ADDRESS.pid: case DMX_BLOCK_ADDRESS.pid: case DMX_STARTUP_MODE.pid: case DMX_FAIL_MODE.pid: if (!hasDMXaddress()) continue; break; case PARAMETER_DESCRIPTION.pid: if (!hasManufacturerPIDs()) continue; break; case SENSOR_VALUE.pid: case SENSOR_DEFINITION.pid: case RECORD_SENSORS.pid: if (!hasSensors()) continue; break; default: break; } response->appendParameterData(pid); it++; } if (length > 0xfe && ack_overflow_page_ != lastPage) response->responseType = RESPONSE_TYPE_ACK_OVERFLOW; } /** * @brief BasicDevice::actionGetParameterDescription * @param msg */ void BasicDevice::actionGetParameterDescription(MsgPair msg) { auto [command, response] = msg; /// \todo return a Parameter Description response->nak(NR_UNSUPPORTED_COMMAND_CLASS); } /** * @brief BasicDevice::actionGetDeviceInfo * @param msg */ void BasicDevice::actionGetDeviceInfo(MsgPair msg) { auto [_, response] = msg; response->appendParameterData(RDM_PROTOCOL_VERSION); response->appendParameterData(deviceModelID); response->appendParameterData(deviceProductCategory); response->appendParameterData(LIB_VERSION); response->appendParameterData((uint16_t)0); // DMX512 Footprint response->appendParameterData((uint16_t)0); // DMX512 Personality response->appendParameterData((uint16_t)0); // DMX512 START address 1 response->appendParameterData((uint16_t)0); // DMX512 START address 2 response->appendParameterData((uint16_t)0); // Sub-Device count response->appendParameterData(sensors_.size()); } /** * @brief BasicDevice::actionGetSoftwareVersionLabel * @param msg */ void BasicDevice::actionGetSoftwareVersionLabel(MsgPair msg) { auto [_, response] = msg; std::string label = std::string(LIB_VERSION_LABEL); for (size_t i = 0; i < label.size() && i <= 32; i++) response->appendParameterData(label.at(i)); } /** * @brief BasicDevice::actionGetIdentifyDevice * @param msg */ void BasicDevice::actionGetIdentifyDevice(MsgPair msg) { auto [_, response] = msg; response->appendParameterData(identifying_); } /** * @brief BasicDevice::actionSetIdentifyDevice * @param msg */ void BasicDevice::actionSetIdentifyDevice(MsgPair msg) { auto [command, _] = msg; identify(command->mdb.pd.front()); } /** * @brief BasicDevice::actionSetResetDevice * @param msg */ void BasicDevice::actionSetResetDevice(MsgPair msg) { auto [command, response] = msg; switch (command->mdb.pd.front()) { case 0x01: reset(false); break; case 0xff: reset(true); break; default: return response->nak(NR_DATA_OUT_OF_RANGE); } } } // namespace RDM