OpenLCP/protocol/esta/rdm/device.cpp
2023-05-02 13:00:41 -04:00

498 lines
14 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.

/*
device.cpp
Copyright (c) 2021 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 "device.h"
#include "config.h"
namespace RDM {
/**
* @brief Device::Device
* @param id
*/
Device::Device(UID id)
: RDM::BasicDevice()
, DMX::Device()
, uid(id)
{
deviceModelDescription = "Basic RDM Device";
Parameter *parameter;
/// \cite RDM 10.3.4 Clear Status ID (CLEAR_STATUS_ID)
/// This parameter is used to clear the status message queue.
parameter = addParameter(CLEAR_STATUS_ID);
parameter->onGet([this](MsgPair msg){actionSetClearStatusId(msg);});
/// \cite RDM 10.3.5 Get/Set Sub-Device Status Reporting Threshold
/// (SUB_DEVICE_STATUS_REPORT_THRESHOLD)
/// This parameter is used to set the verbosity of Sub-Device reporting using
/// the Status Type codes as enumerated in Table A-4 .
parameter = addParameter(SUB_DEVICE_STATUS_REPORT_THRESHOLD);
parameter->onGet([this](MsgPair msg){actionGetSubdeviceThreshold(msg);});
parameter->onSet([this](MsgPair msg){actionSetSubdeviceThreshold(msg);});
/// \cite RDM 10.5.2 Get Product Detail ID List (PRODUCT_DETAIL_ID_LIST)
/// This parameter shall be used for requesting technology details for a
/// device.
parameter = addParameter(PRODUCT_DETAIL_ID_LIST);
parameter->onGet([this](MsgPair msg){actionGetProductDetailIdList(msg);});
/// \cite RDM 10.5.3 Get Device Model Description (DEVICE_MODEL_DESCRIPTION)
/// This parameter provides a text description of up to 32 characters for the
/// device model type.
parameter = addParameter(DEVICE_MODEL_DESCRIPTION);
parameter->onGet([this](MsgPair msg){actionGetDevModelDescription(msg);});
/// \cite RDM 10.5.4 Get Manufacturer Label (MANUFACTURER_LABEL)
/// This parameter provides an ASCII text response with the Manufacturer name
/// for the device of up to 32 characters.
parameter = addParameter(MANUFACTURER_LABEL);
parameter->onGet([this](MsgPair msg){actionGetManufacturerLabel(msg);});
/// \cite RDM 10.5.7 Get Language Capabilities (LANGUAGE_CAPABILITIES)
/// This parameter is used to identify languages that the device supports for
/// using the LANGUAGE parameter.
parameter = addParameter(LANGUAGE_CAPABILITIES);
parameter->onGet([this](MsgPair msg){actionGetLanguage(msg);});
/// \cite RDM 10.5.8 Get/Set Language (LANGUAGE)
/// This parameter is used to change the language of the messages from
/// the device.
parameter = addParameter(LANGUAGE);
parameter->onGet([this](MsgPair msg){actionGetLanguage(msg);});
parameter->onSet([this](MsgPair msg){actionSetLanguage(msg);});
/// \cite RDM 10.6.1 Get/Set DMX512 Personality (DMX_PERSONALITY)
/// This parameter is used to set the responders DMX512 Personality.
parameter = addParameter(DMX_PERSONALITY);
parameter->onGet([this](MsgPair msg){actionGetDmxPersonality(msg);});
parameter->onSet([this](MsgPair msg){actionSetDmxPersonality(msg);});
/// \cite RDM 10.6.2 Get DMX512 Personality Description (DMX_PERSONALITY_DESCRIPTION)
/// This parameter is used to get a descriptive ASCII text label for a given
/// DMX512 Personality.
parameter = addParameter(DMX_PERSONALITY_DESCRIPTION);
parameter->onGet([this](MsgPair msg){actionGetDmxPersonalityDesc(msg);});
/// \cite RDM 10.6.3 Get/Set DMX512 Starting Address (DMX_START_ADDRESS)
/// This parameter is used to set or get the DMX512 start address.
parameter = addParameter(DMX_START_ADDRESS);
parameter->onGet([this](MsgPair msg){actionGetDmxStartAddress(msg);});
parameter->onSet([this](MsgPair msg){actionSetDmxStartAddress(msg);});
/// \cite RDM 10.7.1 Get Sensor Definition (SENSOR_DEFINITION)
/// This parameter is used to retrieve the definition of a specific sensor.
parameter = addParameter(SENSOR_DEFINITION);
parameter->onGet([this](MsgPair msg){actionSensorDispatch(msg);});
/// \cite RDM 10.7.2 Get/Set Sensor (SENSOR_VALUE)
/// This parameter shall be used to retrieve or reset sensor data.
parameter = addParameter(SENSOR_VALUE);
parameter->onGet([this](MsgPair msg){actionSensorDispatch(msg);});
parameter->onSet([this](MsgPair msg){actionSensorDispatch(msg);});
/// \cite RDM 10.7.3 Record Sensors (RECORD_SENSORS)
/// This parameter instructs devices such as dimming racks that monitor load
/// changes to store the current value for monitoring sensor changes.
parameter = addParameter(RECORD_SENSORS);
parameter->onSet([this](MsgPair msg){actionSensorDispatch(msg);});
}
/**
* @brief Device::~Device
*/
Device::~Device()
{
}
/**
* @brief Device::addSubDevice
* @param number
* @param dev
*/
void Device::addSubDevice(uint16_t number, std::shared_ptr<SubDevice> dev)
{
if (sub_devices_.count(number))
sub_devices_.at(number) = dev;
else
sub_devices_.emplace(number, dev);
}
/**
* @brief Device::deleteSubDevice
* @param number
*/
void Device::deleteSubDevice(uint16_t number)
{
sub_devices_.erase(number);
}
/**
* @brief Device::subDevice
* @param number
* @return
*/
std::shared_ptr<SubDevice> Device::subDevice(uint16_t number)
{
if (sub_devices_.count(number))
return sub_devices_.at(number);
return nullptr;
}
/**
* @brief Device::subDeviceCount
* @return
*/
uint16_t Device::subDeviceCount() const
{
return sub_devices_.size();
}
/**
* @brief Device::dispatch
* @param msg
*/
void Device::dispatch(MsgPair msg)
{
auto [call, response] = msg;
if (call->subDevice == 0)
return BasicDevice::dispatch(msg);
else if (call->mdb.cc == DISCOVERY_COMMAND)
return response->nak(NR_SUB_DEVICE_OUT_OF_RANGE);
if (call->subDevice == SUB_DEVICE_ALL_CALL)
{
/// \cite RDM 9.2.2 Using Sub-Devices
/// Broadcast GET commands sent to the SUB_DEVICE_ALL_CALL Sub-Device ID are
/// not allowed. Any responder receiving a GET command sent to this Sub-Device
/// ID shall respond with a NACK with a NACK Reason Code of
/// NR_SUB_DEVICE_OUT_OF_RANGE.
if (call->mdb.cc == GET_COMMAND)
return response->nak(NR_SUB_DEVICE_OUT_OF_RANGE);
response->do_not_send = true;
for (auto& [_, dev] : sub_devices_)
dev->dispatch(msg);
return;
}
if (sub_devices_.count(call->subDevice))
return sub_devices_.at(call->subDevice)->dispatch(msg);
response->nak(NR_SUB_DEVICE_OUT_OF_RANGE);
}
/**
* @brief Device::addProductDetail
* @param id
*/
void Device::addProductDetail(uint16_t id)
{
product_detail_list_.push_back(id);
while (product_detail_list_.size() > 6)
product_detail_list_.pop_front();
}
/**
* @brief Device::enqueueMessage
* @param message
* @param urgent
*/
void Device::enqueueMessage(MsgPtr message, bool urgent)
{
message->destination = controller_uid_;
if (urgent)
queued_messages_.push_front(message);
else
queued_messages_.push_back(message);
}
void Device::actionGetDeviceInfo(MsgPair msg)
{
auto [_, response] = msg;
response->appendParameterData(RDM_PROTOCOL_VERSION);
response->appendParameterData(deviceModelID);
response->appendParameterData(deviceProductCategory);
response->appendParameterData(LIB_VERSION);
response->appendParameterData(DMX::Device::footprint());
response->appendParameterData(DMX::Device::personality());
response->appendParameterData(DMX::Device::personalityCount());
response->appendParameterData(DMX::Device::address());
response->appendParameterData(subDeviceCount());
response->appendParameterData<uint8_t>(sensors_.size());
}
/**
* @brief Device::actionSetClearStatusId
* @param msg
*/
void Device::actionSetClearStatusId([[maybe_unused]]MsgPair msg)
{
for (auto& [_, queue] : queued_statuses)
while (!queue.empty())
queue.pop();
}
/**
* @brief Device::actionGetSubdeviceThreshold
* @param msg
*/
void Device::actionGetSubdeviceThreshold(MsgPair msg)
{
auto [_, response] = msg;
response->appendParameterData(status_reporting_threshold_);
}
/**
* @brief Device::actionSetSubdeviceThreshold
* @param msg
*/
void Device::actionSetSubdeviceThreshold(MsgPair msg)
{
auto [command, response] = msg;
uint8_t threshold = command->mdb.pd.front();
switch (threshold) {
case STATUS_ERROR:
status_reporting_threshold_ = threshold;
while (!queued_statuses.at(STATUS_WARNING).empty())
queued_statuses.at(STATUS_WARNING).pop();
while (!queued_statuses.at(STATUS_ADVISORY).empty())
queued_statuses.at(STATUS_ADVISORY).pop();
break;
case STATUS_WARNING:
status_reporting_threshold_ = threshold;
while (!queued_statuses.at(STATUS_ADVISORY).empty())
queued_statuses.at(STATUS_ADVISORY).pop();
break;
case STATUS_ADVISORY:
status_reporting_threshold_ = threshold;
break;
default:
response->nak(NR_DATA_OUT_OF_RANGE);
return;
}
}
/**
* @brief Device::actionGetProductDetailIdList
* @param msg
*/
void Device::actionGetProductDetailIdList(MsgPair msg)
{
auto [_, response] = msg;
if (product_detail_list_.empty())
response->appendParameterData(PRODUCT_DETAIL_NOT_DECLARED);
else
for (const auto detail : product_detail_list_)
response->appendParameterData(detail);
}
/**
* @brief Device::actionGetDevModelDescription
* @param msg
*/
void Device::actionGetDevModelDescription(MsgPair msg)
{
auto [_, response] = msg;
for (size_t i = 0; i < deviceModelDescription.size() && i <= 32; i++)
response->appendParameterData(deviceModelDescription.at(i));
}
/**
* @brief Device::actionGetManufacturerLabel
* @param msg
*/
void Device::actionGetManufacturerLabel(MsgPair msg)
{
auto [_, response] = msg;
for (size_t i = 0; i < deviceManufacturerLabel.size() && i <= 32; i++)
response->appendParameterData(deviceManufacturerLabel.at(i));
}
/**
* @brief Device::actionGetLanguage
* @param msg
*/
void Device::actionGetLanguage(MsgPair msg)
{
auto [_, response] = msg;
response->appendParameterData('e');
response->appendParameterData('n');
}
/**
* @brief Device::actionSetLanguage
* @param msg
*/
void Device::actionSetLanguage(MsgPair msg)
{
auto [command, response] = msg;
if (command->mdb.pd[0] != 'e' ||
command->mdb.pd[1] != 'n')
return response->nak(NR_DATA_OUT_OF_RANGE);
}
/**
* @brief Device::actionGetDmxPersonality
* @param msg
*/
void Device::actionGetDmxPersonality(MsgPair msg)
{
auto [_, response] = msg;
response->appendParameterData(DMX::Device::personality());
response->appendParameterData(DMX::Device::personalityCount());
}
/**
* @brief Device::actionSetDmxPersonality
* @param msg
*/
void Device::actionSetDmxPersonality(MsgPair msg)
{
auto [command, response] = msg;
uint8_t mode = command->mdb.pd.front();
if ( mode == 0 || mode > DMX::Device::personalityCount())
return response->nak(NR_DATA_OUT_OF_RANGE);
setPersonality(mode);
}
/**
* @brief Device::actionGetDmxPersonalityDesc
* @param msg
*/
void Device::actionGetDmxPersonalityDesc(MsgPair msg)
{
auto [command, response] = msg;
uint8_t mode = command->mdb.pd.front();
if ( mode == 0 || mode > DMX::Device::personalityCount())
return response->nak(NR_DATA_OUT_OF_RANGE);
response->appendParameterData(mode);
response->appendParameterData(personalities_.at(mode)->footprint());
for (size_t i = 0; i < personalities_.at(mode)->description().size(); i++)
{
if (i > 32)
break;
response->appendParameterData(personalities_.at(mode)->description().at(i));
}
}
/**
* @brief Device::actionGetDmxStartAddress
* @param msg
*/
void Device::actionGetDmxStartAddress(MsgPair msg)
{
auto [_, response] = msg;
if (footprint() == 0)
response->appendParameterData<uint16_t>(0xFFFF);
else
response->appendParameterData(address());
}
/**
* @brief Device::actionSetDmxStartAddress
* @param msg
*/
void Device::actionSetDmxStartAddress(MsgPair msg)
{
auto [command, response] = msg;
uint16_t addr = Message::readType<uint16_t>(command->mdb.pd, 0);
if (!setAddress(addr))
return response->nak(NR_DATA_OUT_OF_RANGE);
}
/**
* @brief Device::actionSensorDispatch
* @param msg
*/
void Device::actionSensorDispatch(MsgPair msg)
{
auto [command, response] = msg;
uint8_t index = command->mdb.pd.front();
switch (command->mdb.cc) {
case GET_COMMAND:
{
if (index == 0xFF || index >= sensors_.size())
{
response->nak(NR_DATA_OUT_OF_RANGE);
return;
}
auto sensor = sensors_.at(index);
switch (command->mdb.pid) {
case SENSOR_DEFINITION.pid:
sensor->actionGetSensorDefinition(index, response);
break;
case SENSOR_VALUE.pid:
sensor->actionGetSensorValue(index, response);
break;
}
}
break;
case SET_COMMAND:
{
if (index >= sensors_.size() && index != 0xFF)
{
response->nak(NR_DATA_OUT_OF_RANGE);
return;
}
auto setSensor = [index, msg](Sensor * sensor)
{
auto [command, response] = msg;
switch (command->mdb.pid) {
case SENSOR_VALUE.pid:
sensor->actionSetSensorValue(index, response);
break;
case RECORD_SENSORS.pid:
sensor->actionSetRecordSensors(response);
break;
}
};
if (index == 0xff)
for (Sensor* s : sensors_)
setSensor(s);
else
setSensor(sensors_.at(index));
}
break;
}
}
} // namespace RDM