From af47a4cfb78e65e0a0c2cacfd4b29216c705836b Mon Sep 17 00:00:00 2001 From: Kevin Matz Date: Mon, 9 Aug 2021 15:04:49 -0400 Subject: [PATCH] action-able basic PIDs --- CMakeLists.txt | 2 + rdm/controller.cpp | 3 + rdm/device.cpp | 329 +++++++++++++++++++++++++++++++++++++++++++-- rdm/device.h | 44 ++++-- rdm/parameter.cpp | 22 ++- rdm/parameter.h | 20 ++- rdm/rdm.h | 5 + rdm/responder.cpp | 46 ++----- rdm/responder.h | 3 - rdm/sensor.cpp | 34 +++++ rdm/sensor.h | 34 +++++ 11 files changed, 473 insertions(+), 69 deletions(-) create mode 100644 rdm/sensor.cpp create mode 100644 rdm/sensor.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e92e8d..bde7691 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,8 @@ set(SOURCE_FILES rdm/rdm.cpp rdm/responder.h rdm/responder.cpp + rdm/sensor.h + rdm/sensor.cpp rdm/uid.h rdmnet/rdmnet.h sacn/data.cpp diff --git a/rdm/controller.cpp b/rdm/controller.cpp index fda4694..8d4a804 100644 --- a/rdm/controller.cpp +++ b/rdm/controller.cpp @@ -33,6 +33,9 @@ Controller::Controller() : Responder() , next_transaction_(0) { + deviceModelID = 2; + deviceModelDescription = "Basic RDM Controller"; + deviceProductCategory = PRODUCT_CATEGORY_CONTROL_CONTROLLER; } diff --git a/rdm/device.cpp b/rdm/device.cpp index 5ba74e5..db0fbdd 100644 --- a/rdm/device.cpp +++ b/rdm/device.cpp @@ -21,8 +21,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#pragma once +#include "config.h" #include "device.h" namespace RDM { @@ -30,13 +30,76 @@ namespace RDM { /** * @brief Device::Device */ -Device::Device() +Device::Device(Device* parent) + : DMX::Device() + , deviceModelID(0) + , deviceModelDescription("Basic RDM Device") + , deviceProductCategory(PRODUCT_CATEGORY_NOT_DECLARED) + , parent_(parent) { - // 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. - parameters_[SUPPORTED_PARAMETERS] = new Parameter; + id.manufacturer = MY_ESTA_MANUFACTURER_ID; + + /// 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. + parameters_.try_emplace(SUPPORTED_PARAMETERS, new Parameter()); + parameters_.at(SUPPORTED_PARAMETERS)->getAction(std::bind( + &Device::actionGetSupportedParameters, + this, std::placeholders::_1, + std::placeholders::_2)); + /// 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. + parameters_.try_emplace(DEVICE_INFO, new Parameter()); + parameters_.at(DEVICE_INFO)->getAction(std::bind( + &Device::actionGetDeviceInfo, + this, std::placeholders::_1, + std::placeholders::_2)); + /// 10.5.2 Get Product Detail ID List (PRODUCT_DETAIL_ID_LIST) + /// This parameter shall be used for requesting technology details for a + /// device. + parameters_.try_emplace(PRODUCT_DETAIL_ID_LIST, new Parameter()); + parameters_.at(PRODUCT_DETAIL_ID_LIST)->getAction(std::bind( + &Device::actionGetProductDetailIdList, + this, std::placeholders::_1, + std::placeholders::_2)); + /// 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. + parameters_.try_emplace(DEVICE_MODEL_DESCRIPTION, new Parameter()); + parameters_.at(DEVICE_MODEL_DESCRIPTION)->getAction(std::bind( + &Device::actionGetDevModelDescription, + this, std::placeholders::_1, + std::placeholders::_2)); + /// 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. + parameters_.try_emplace(MANUFACTURER_LABEL, new Parameter()); + parameters_.at(MANUFACTURER_LABEL)->getAction(std::bind( + &Device::actionGetManufacturerLabel, + this, std::placeholders::_1, + std::placeholders::_2)); + /// 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. + parameters_.try_emplace(SOFTWARE_VERSION_LABEL, new Parameter()); + parameters_.at(SOFTWARE_VERSION_LABEL)->getAction(std::bind( + &Device::actionGetSoftwareVersionLabel, + this, std::placeholders::_1, + std::placeholders::_2)); + /// 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. + parameters_.try_emplace(IDENTIFY_DEVICE, new Parameter()); + parameters_.at(IDENTIFY_DEVICE)->getAction(std::bind( + &Device::actionGetIdentifyDevice, + this, std::placeholders::_1, + std::placeholders::_2)); + parameters_.at(IDENTIFY_DEVICE)->setAction(std::bind( + &Device::actionSetIdentifyDevice, + this, std::placeholders::_1, + std::placeholders::_2)); } @@ -45,8 +108,70 @@ Device::Device() */ Device::~Device() { + for( auto& [_, device] : sub_devices_) + delete device; for (auto& [_, parameter] : parameters_) delete parameter; + for (auto sensor : sensors_) + delete sensor; +} + + +/** + * @brief Device::addSubDevice + * @param number + * @param dev + * + * Root devices (ie. Respoders) shall override with a meaningful implimentation + */ +void Device::addSubDevice(uint16_t number, Device *dev) +{ + if (parent_) + return; + + if (sub_devices_.count(number)) + delete sub_devices_.at(number); + sub_devices_[number] = dev; +} + + +/** + * @brief Device::subDevice + * @param number + * @return + */ +Device* Device::subDevice(uint16_t number) +{ + if (parent_) + return nullptr; + + if (sub_devices_.count(number)) + return sub_devices_.at(number); + return nullptr; +} + + +/** + * @brief Device::subDeviceCount + * @return + */ +uint16_t Device::subDeviceCount() const +{ + if (parent_) + return parent_->subDeviceCount(); + return sub_devices_.size(); +} + + +/** + * @brief Device::addProductDetailId + * @param id + */ +void Device::addProductDetailId(uint16_t id) +{ + product_detail_list_.push_back(id); + while (product_detail_list_.size() > 6) + product_detail_list_.pop_front(); } @@ -58,12 +183,8 @@ Device::~Device() void Device::get(const Message *message, Message *response) { response->commandClass = GET_COMMAND_RESPONSE; - if (!parameters_.count(message->propertyID)) - { - response->responseType = RESPONSE_TYPE_NACK_REASON; - response->appendData(NR_UNKNOWN_PID); - return; - } + if (!actionPrep_(message, response)) + return; parameters_.at(message->propertyID)->get(message, response); } @@ -76,13 +197,193 @@ void Device::get(const Message *message, Message *response) void Device::set(const Message *message, Message *response) { response->commandClass = SET_COMMAND_RESPONSE; + if (!actionPrep_(message, response)) + return; + parameters_.at(message->propertyID)->set(message, response); +} + + +/** + * @brief Device::actionPrep + * @return + */ +bool Device::actionPrep_(const Message *message, Message *response) +{ if (!parameters_.count(message->propertyID)) { response->responseType = RESPONSE_TYPE_NACK_REASON; response->appendData(NR_UNKNOWN_PID); + return false; + } + + if (message->propertyID == last_rx_pid_) + ++ack_overflow_page; + else { + ack_overflow_page = 0; + last_rx_pid_ = message->propertyID; + } + return true; +} + + +/** + * @brief Device::actionGetSupportedParameters + * @param message + * @param response + */ +void Device::actionGetSupportedParameters( + __attribute__((unused)) const Message *message, Message *response) +{ + uint count = parameters_.size(); + uint length = count * sizeof(PID); + uint lastPage = length / 0xfe; + uint first = ack_overflow_page * ( 0xfe / sizeof(PID) ); + if (first >= count) { + ack_overflow_page = 0; + first = 0; + } + + if (length > 0xfe && ack_overflow_page != lastPage) + response->responseType = RESPONSE_TYPE_ACK_OVERFLOW; + else + response->responseType = RESPONSE_TYPE_ACK; + + auto pid = parameters_.begin(); + if (first != 0) + std::advance(pid, first); + while (pid != parameters_.end() && response->length() < 0xfe) + { + response->appendData(pid->first); + pid++; + } +} + + +/** + * @brief Device::actionGetDeviceInfo + * @param message + * @param response + */ +void Device::actionGetDeviceInfo( + __attribute__((unused)) const Message *message, Message *response) +{ + response->responseType = RESPONSE_TYPE_ACK; + response->appendData(RDM_PROTOCOL_VERSION); + response->appendData(deviceModelID); + response->appendData(deviceProductCategory); + response->appendData(LIB_VERSION); + response->appendData(DMX::Device::footprint()); + response->appendData (DMX::Device::personality()); + response->appendData (DMX::Device::personalityCount()); + response->appendData(DMX::Device::address()); + response->appendData(subDeviceCount()); + response->appendData (sensors_.size()); +} + + +/** + * @brief Device::actionGetProductDetailIdList + * @param message + * @param response + */ +void Device::actionGetProductDetailIdList( + __attribute__((unused)) const Message *message, Message *response) +{ + response->responseType = RESPONSE_TYPE_ACK; + if (product_detail_list_.empty()) + { + response->appendData(PRODUCT_DETAIL_NOT_DECLARED); return; } - parameters_.at(message->propertyID)->set(message, response); + for ( uint16_t id : product_detail_list_ ) + response->appendData(id); +} + + +/** + * @brief Device::actionGetDevModelDescription + * @param message + * @param response + */ +void Device::actionGetDevModelDescription( + __attribute__((unused)) const Message *message, Message *response) +{ + response->responseType = RESPONSE_TYPE_ACK; + for (size_t i = 0; i < deviceModelDescription.size(); i++) + { + if (i > 32) + break; + response->appendData(deviceModelDescription.at(i)); + } +} + + +/** + * @brief Device::actionGetManufacturerLabel + * @param message + * @param response + */ +void Device::actionGetManufacturerLabel( + __attribute__((unused)) const Message *message, Message *response) +{ + response->responseType = RESPONSE_TYPE_ACK; + std::string label = std::string(MY_ESTA_MANUFACTURER_LABEL); + for (size_t i = 0; i < label.size(); i++) + { + if (i > 32) + break; + response->appendData(label.at(i)); + } +} + + +/** + * @brief Device::actionGetSoftwareVersionLabel + * @param message + * @param response + */ +void Device::actionGetSoftwareVersionLabel( + __attribute__((unused)) const Message *message, Message *response) +{ + response->responseType = RESPONSE_TYPE_ACK; + std::string label = std::string(LIB_VERSION_LABEL); + for (size_t i = 0; i < label.size(); i++) + { + if (i > 32) + break; + response->appendData(label.at(i)); + } +} + + +/** + * @brief Device::actionGetIdentifyDevice + * @param message + * @param response + */ +void Device::actionGetIdentifyDevice( + __attribute__((unused)) const Message *message, Message *response) +{ + response->responseType = RESPONSE_TYPE_ACK; + response->appendData(identifying_); +} + + +/** + * @brief Device::actionSetIdentifyDevice + * @param message + * @param response + */ +void Device::actionSetIdentifyDevice(const Message *message, Message *response) +{ + if (message->data()->size() != 1) + { + response->responseType = RESPONSE_TYPE_NACK_REASON; + response->appendData(NR_FORMAT_ERROR); + return; + } + response->responseType = RESPONSE_TYPE_ACK; + identify(message->data()->front()); } } // namespace RDM diff --git a/rdm/device.h b/rdm/device.h index c58946d..bcdc579 100644 --- a/rdm/device.h +++ b/rdm/device.h @@ -23,34 +23,62 @@ */ #pragma once - +#include "dmx/device.h" #include "parameter.h" #include "rdm.h" +#include "sensor.h" #include "uid.h" +#include +#include #include namespace RDM { class Device + : public DMX::Device { public: - Device(); + Device(Device* parent = nullptr); virtual ~Device(); - const UID id() const { return id_; } + void addSubDevice(uint16_t number, Device* dev); + Device* subDevice(uint16_t number); + uint16_t subDeviceCount() const; + void addProductDetailId(uint16_t); - virtual void get(const Message* message, Message* response); - virtual void set(const Message* message, Message* response); + void get(const Message* message, Message* response); + void set(const Message* message, Message* response); - void setManufacturerId(uint16_t man) { id_.manufacturer = man; } - void setDeviceId(uint32_t dev) { id_.device = dev; } + virtual void identify(bool state) { identifying_ = state; } + + UID id; + uint16_t deviceModelID; + std::string deviceModelDescription; + uint16_t deviceProductCategory; protected: + std::unordered_map sub_devices_; std::unordered_map parameters_; + std::vector sensors_; + std::list product_detail_list_; + + bool actionPrep_(const Message *message, Message *response); + + void actionGetSupportedParameters (const Message *message, Message *response); + void actionGetDeviceInfo (const Message *message, Message *response); + void actionGetProductDetailIdList (const Message *message, Message *response); + void actionGetDevModelDescription (const Message *message, Message *response); + void actionGetManufacturerLabel (const Message *message, Message *response); + void actionGetSoftwareVersionLabel(const Message *message, Message *response); + void actionGetIdentifyDevice (const Message *message, Message *response); + void actionSetIdentifyDevice (const Message *message, Message *response); private: - UID id_; + Device* parent_; + PID last_rx_pid_ = 0; + uint ack_overflow_page = 0; + bool identifying_ = false; }; diff --git a/rdm/parameter.cpp b/rdm/parameter.cpp index ed23154..8d39cb6 100644 --- a/rdm/parameter.cpp +++ b/rdm/parameter.cpp @@ -42,8 +42,13 @@ Parameter::Parameter() void Parameter::get(__attribute__((unused)) const Message* message, Message* response) const { - response->responseType = RESPONSE_TYPE_NACK_REASON; - response->appendData(NR_UNSUPPORTED_COMMAND_CLASS); + if (!getter_){ + response->responseType = RESPONSE_TYPE_NACK_REASON; + response->appendData(NR_UNSUPPORTED_COMMAND_CLASS); + return; + } + + getter_(message, response); } @@ -54,11 +59,16 @@ void Parameter::get(__attribute__((unused)) const Message* message, * @param nak */ void Parameter::set(__attribute__((unused)) const Message* message, - Message* response) + Message* response) const { - response->responseType = RESPONSE_TYPE_NACK_REASON; - response->appendData(NR_UNSUPPORTED_COMMAND_CLASS); + if (!setter_) + { + response->responseType = RESPONSE_TYPE_NACK_REASON; + response->appendData(NR_UNSUPPORTED_COMMAND_CLASS); + return; + } + + setter_(message, response); } - } // namespace RDM diff --git a/rdm/parameter.h b/rdm/parameter.h index 5857365..fc87087 100644 --- a/rdm/parameter.h +++ b/rdm/parameter.h @@ -26,16 +26,30 @@ #include "rdm.h" #include "message.h" +#include + namespace RDM { +using PidAction = std::function; + +/** + * @brief The Parameter class + */ class Parameter { public: Parameter(); - virtual ~Parameter(); + ~Parameter(); - virtual void get(const Message* message, Message* response) const; - virtual void set(const Message* message, Message* response); + void get(const Message* message, Message* response) const; + void set(const Message* message, Message* response) const; + + void getAction(const PidAction action) { getter_ = action; }; + void setAction(const PidAction action) { setter_ = action; }; + +private: + PidAction getter_; + PidAction setter_; }; diff --git a/rdm/rdm.h b/rdm/rdm.h index c3ed99f..b3d0228 100644 --- a/rdm/rdm.h +++ b/rdm/rdm.h @@ -35,6 +35,11 @@ using namespace DMX; using PID = uint16_t; + /// 10.5.1 Get Device Info (DEVICE_INFO) + /// RDM Protocol Version: + /// The version of this standard is 1.0 + static const uint16_t RDM_PROTOCOL_VERSION = 0x0100; + /// Appendix A: Defined Parameters (Normative) /// START Codes (Slot 0) diff --git a/rdm/responder.cpp b/rdm/responder.cpp index d874032..dcbc80d 100644 --- a/rdm/responder.cpp +++ b/rdm/responder.cpp @@ -31,13 +31,17 @@ namespace RDM { Responder::Responder() : Device() { + deviceModelID = 1; + deviceModelDescription = "Basic RDM Responder"; + // E1.20 required parameters - parameters_[DISC_UNIQUE_BRANCH] = new Parameter; - parameters_[DISC_MUTE] = new Parameter; - parameters_[DISC_UN_MUTE] = new Parameter; - parameters_[DEVICE_INFO] = new Parameter; - parameters_[SOFTWARE_VERSION_LABEL] = new Parameter; - parameters_[IDENTIFY_DEVICE] = new Parameter; +// DISC_UNIQUE_BRANCH +// DISC_MUTE +// DISC_UN_MUTE + + // addt'l parameters +// COMMS_STATUS +// QUEUED_MESSAGE } @@ -46,34 +50,6 @@ Responder::Responder() */ Responder::~Responder() { - for( auto& [_, dev] : sub_devices_) - delete dev; -} - - -/** - * @brief Responder::addSubDevice - * @param number - * @param dev - */ -void Responder::addSubDevice(uint16_t number, Device *dev) -{ - if (sub_devices_.count(number)) - delete sub_devices_.at(number); - sub_devices_[number] = dev; -} - - -/** - * @brief Responder::subDevice - * @param number - * @return - */ -Device* Responder::subDevice(uint16_t number) -{ - if (sub_devices_.count(number)) - return sub_devices_.at(number); - return nullptr; } @@ -273,7 +249,7 @@ void Responder::receive(const Message *message) return; response = new Message(); - response->source = id(); + response->source = id; response->destination = message->source; response->subDevice = message->subDevice; response->propertyID = message->propertyID; diff --git a/rdm/responder.h b/rdm/responder.h index 763af88..578647b 100644 --- a/rdm/responder.h +++ b/rdm/responder.h @@ -40,8 +40,6 @@ public: virtual ~Responder(); void receive(const std::vector& data); - void addSubDevice(uint16_t number, Device* dev); - Device* subDevice(uint16_t number); protected: virtual void send(); @@ -59,7 +57,6 @@ protected: std::queue queued_messages_; private: - std::unordered_map sub_devices_; }; diff --git a/rdm/sensor.cpp b/rdm/sensor.cpp new file mode 100644 index 0000000..53c911f --- /dev/null +++ b/rdm/sensor.cpp @@ -0,0 +1,34 @@ +/* + sensor.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 "sensor.h" + +namespace RDM { + +Sensor::Sensor() +{ + +} + +} // namespace RDM diff --git a/rdm/sensor.h b/rdm/sensor.h new file mode 100644 index 0000000..2e4cf86 --- /dev/null +++ b/rdm/sensor.h @@ -0,0 +1,34 @@ +/* + sensor.h + + 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. +*/ +#pragma once + +namespace RDM { + +class Sensor +{ +public: + Sensor(); +}; + +} // namespace RDM