/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 Library General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * InternalRDMController.cpp * Manager internally generated RDM requests * Copyright (C) 2010 Simon Newton */ #include #include #include #include #include "ola/StringUtils.h" #include "olad/InternalRDMController.h" namespace ola { using ola::rdm::RDMCommand; const char InternalRDMController::MISMATCHED_RDM_RESPONSE_VAR[] = "rdm-mismatched-responses"; const char InternalRDMController::EXPIRED_RDM_REQUESTS_VAR[] = "rdm-expired-requests"; const char InternalRDMController::BROADCAST_RDM_REQUESTS_VAR[] = "rdm-broadcast-requests"; const char InternalRDMController::FAILED_RDM_REQUESTS_VAR[] = "rdm-failed-requests"; /* * Create a new OutstandingRDMRequest */ OutstandingRDMRequest::OutstandingRDMRequest( const RDMRequest *request, rdm_controller_callback *callback): m_source_uid(request->SourceUID()), m_sub_device(request->SubDevice()), m_transaction_number(request->TransactionNumber()), m_command_class(request->CommandClass()), m_expires(), m_callback(callback) { Clock::CurrentTime(&m_expires); m_expires += ola::TimeInterval(EXPIRY_TIMEOUT_S, 0); } /* * Returns true if this response matches this request */ bool OutstandingRDMRequest::Matches(const RDMResponse *response) { if (!response) return false; if (response->DestinationUID() == m_source_uid && response->TransactionNumber() == m_transaction_number && response->SubDevice() == m_sub_device) { if (response->CommandClass() == RDMCommand::GET_COMMAND_RESPONSE && m_command_class == RDMCommand::GET_COMMAND) return true; if (response->CommandClass() == RDMCommand::SET_COMMAND_RESPONSE && m_command_class == RDMCommand::SET_COMMAND) return true; } return false; } /* * Return true if this request has expired */ bool OutstandingRDMRequest::HasExpired(const TimeStamp &now) { return now > m_expires; } /* * Run the callback for this request */ void OutstandingRDMRequest::RunCallback(const rdm_response_data &data) { if (m_callback) m_callback->Run(data); m_callback = NULL; } InternalRDMController::InternalRDMController(const UID &default_uid, PortManager *port_manager, class ExportMap *export_map): m_default_uid(default_uid), m_port_manager(port_manager), m_export_map(export_map) { m_export_map->GetUIntMapVar(MISMATCHED_RDM_RESPONSE_VAR); m_export_map->GetUIntMapVar(EXPIRED_RDM_REQUESTS_VAR); m_export_map->GetUIntMapVar(BROADCAST_RDM_REQUESTS_VAR); m_export_map->GetUIntMapVar(FAILED_RDM_REQUESTS_VAR); } /* * Tear down this InternalRDMController */ InternalRDMController::~InternalRDMController() { // delete all ports map::iterator port_iter; for (port_iter = m_input_ports.begin(); port_iter != m_input_ports.end(); ++port_iter) { m_port_manager->UnPatchPort(port_iter->second); delete port_iter->second; } m_input_ports.clear(); // delete out OutstandingRDMRequests map >::iterator iter; deque::iterator request_iter; rdm_response_data data = {RDM_RESPONSE_TIMED_OUT, NULL}; for (iter = m_outstanding_requests.begin(); iter != m_outstanding_requests.end(); ++iter) { for (request_iter = iter->second.begin(); request_iter != iter->second.end(); ++request_iter) { (*request_iter)->RunCallback(data); delete *request_iter; } } m_transaction_numbers.clear(); } /* * Send an RDMRequest, this may run the callback immediately if the send failed */ bool InternalRDMController::SendRDMRequest( Universe *universe, const UID &destination, uint8_t sub_device, uint16_t param_id, const string &data, bool is_set, rdm_controller_callback *callback, const UID *source) { map::iterator port_iter = m_input_ports.find(universe->UniverseId()); if (port_iter == m_input_ports.end()) { // create a new InternalInputPort for this universe InternalInputPort *input_port = new InternalInputPort( universe->UniverseId(), this); if (!m_port_manager->PatchPort(input_port, universe->UniverseId())) { OLA_WARN << "Failed to patch internal input port to universe " << universe->UniverseId() << ", aborting RDM request"; delete callback; return false; } port_iter = m_input_ports.insert(pair( universe->UniverseId(), input_port)).first; } UID source_uid = m_default_uid; if (source) source_uid = *source; // lookup transaction number uint8_t transaction_number = 0; pair transaction_key(universe->UniverseId(), source_uid); map, uint8_t>::iterator transaction_iter = m_transaction_numbers.find(transaction_key); if (transaction_iter == m_transaction_numbers.end()) m_transaction_numbers[transaction_key] = 0; else transaction_number = ++transaction_iter->second; ola::rdm::RDMRequest *request = NULL; if (is_set) { request = new ola::rdm::RDMSetRequest( source_uid, destination, transaction_number, 1, // port id 0, // message count sub_device, param_id, reinterpret_cast(data.data()), data.size()); } else { request = new ola::rdm::RDMGetRequest( source_uid, destination, transaction_number, 1, // port id 0, // message count sub_device, param_id, reinterpret_cast(data.data()), data.size()); } // this has to be done before the request is deleted below OutstandingRDMRequest *outstanding_request = new OutstandingRDMRequest(request, callback); // We need to push this on now, because HandleRDMResponse could be called // immediately deque &request_list = m_outstanding_requests[universe->UniverseId()]; if (!destination.IsBroadcast()) request_list.push_back(outstanding_request); if (port_iter->second->HandleRDMRequest(request)) { if (destination.IsBroadcast()) { (*m_export_map->GetUIntMapVar(BROADCAST_RDM_REQUESTS_VAR))[ IntToString(universe->UniverseId())]++; rdm_response_data data = {RDM_RESPONSE_BROADCAST, NULL}; callback->Run(data); delete outstanding_request; return true; } else { return true; } } // the request failed, remove it from the request queue request_list.pop_back(); (*m_export_map->GetUIntMapVar(FAILED_RDM_REQUESTS_VAR))[ IntToString(universe->UniverseId())]++; delete outstanding_request; return false; } /* * Handle RDM responses */ bool InternalRDMController::HandleRDMResponse( unsigned int universe, const ola::rdm::RDMResponse *response) { // try to locate a match OutstandingRDMRequest *request = NULL; map >::iterator iter = m_outstanding_requests.find(universe); if (iter == m_outstanding_requests.end()) { // things have gone horibly wrong OLA_WARN << "Got a RDMResponse but can't find universe " << universe; delete response; return false; } deque::iterator request_iter; for (request_iter = iter->second.begin(); request_iter != iter->second.end(); ++request_iter) { if ((*request_iter)->Matches(response)) { request = *request_iter; iter->second.erase(request_iter); break; } } if (!request) { OLA_WARN << "Unable to locate a matching request for RDM response"; (*m_export_map->GetUIntMapVar(MISMATCHED_RDM_RESPONSE_VAR))[ IntToString(universe)]++; delete response; return false; } rdm_response_data data = {RDM_RESPONSE_OK, response}; request->RunCallback(data); delete request; delete response; return true; } /* * Check for any exprired requests * @param now the current time */ void InternalRDMController::CheckTimeouts(const TimeStamp &now) { vector > expired_requests; map >::iterator iter; deque::iterator request_iter; vector >::iterator expired_iter; for (iter = m_outstanding_requests.begin(); iter != m_outstanding_requests.end(); ++iter) { for (request_iter = iter->second.begin(); request_iter != iter->second.end();) { if ((*request_iter)->HasExpired(now)) { expired_requests.push_back( pair(iter->first, *request_iter)); request_iter = iter->second.erase(request_iter); } else { request_iter++; } } } rdm_response_data data = {RDM_RESPONSE_TIMED_OUT, NULL}; for (expired_iter = expired_requests.begin(); expired_iter != expired_requests.end(); ++expired_iter) { (*m_export_map->GetUIntMapVar(EXPIRED_RDM_REQUESTS_VAR))[ IntToString(expired_iter->first)]++; expired_iter->second->RunCallback(data); delete expired_iter->second; } expired_requests.clear(); } } // ola