OpenLCP/protocol/esta/rdm/basicdevice.cpp
2023-05-03 15:56:48 -04:00

306 lines
8.6 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
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<StatusPtr>());
queued_statuses.emplace(STATUS_WARNING, std::queue<StatusPtr>());
queued_statuses.emplace(STATUS_ERROR, std::queue<StatusPtr>());
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
/// devices 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<uint8_t>(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<uint8_t>(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