/* * 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. * * DmxTriDevice.h * The Jese DMX TRI device. * Copyright (C) 2010 Simon Newton */ #include #include #include #include #include "ola/BaseTypes.h" #include "ola/Logging.h" #include "ola/network/NetworkUtils.h" #include "ola/rdm/RDMCommand.h" #include "ola/rdm/RDMEnums.h" #include "ola/rdm/UID.h" #include "ola/rdm/UIDSet.h" #include "plugins/usbpro/DmxTriDevice.h" namespace ola { namespace plugin { namespace usbpro { using std::string; using ola::network::NetworkToHost; using ola::network::HostToNetwork; using ola::rdm::RDMCommand; using ola::rdm::RDMRequest; using ola::rdm::UID; using ola::rdm::UIDSet; /* * New DMX TRI device */ DmxTriDevice::DmxTriDevice(const ola::PluginAdaptor *plugin_adaptor, ola::AbstractPlugin *owner, const string &name, UsbWidget *widget, uint16_t esta_id, uint16_t device_id, uint32_t serial): UsbDevice(owner, name, widget), m_plugin_adaptor(plugin_adaptor), m_rdm_timeout_id(ola::network::INVALID_TIMEOUT), m_uid_count(0), m_rdm_request_pending(false), m_last_esta_id(UID::ALL_MANUFACTURERS), m_rdm_response(NULL) { std::stringstream str; str << std::hex << esta_id << "-" << device_id << "-" << NetworkToHost(serial); m_device_id = str.str(); DmxTriOutputPort *output_port = new DmxTriOutputPort(this); AddPort(output_port); m_widget->SetMessageHandler(this); } /* * Shutdown */ DmxTriDevice::~DmxTriDevice() { // delete all outstanding requests while (!m_pending_requests.empty()) { const RDMRequest *request = m_pending_requests.front(); delete request; m_pending_requests.pop(); } } /* * Kick off the RDM discovery process */ bool DmxTriDevice::StartHook() { RunRDMDiscovery(); return true; } /* * Remove the rdm timeout if it's still running */ void DmxTriDevice::PrePortStop() { if (m_rdm_timeout_id != ola::network::INVALID_TIMEOUT) { m_plugin_adaptor->RemoveTimeout(m_rdm_timeout_id); m_rdm_timeout_id = ola::network::INVALID_TIMEOUT; } } /* * Send a dmx msg * @returns true if we sent ok, false otherwise */ bool DmxTriDevice::SendDMX(const DmxBuffer &buffer) const { struct { uint8_t start_code; uint8_t dmx[DMX_UNIVERSE_SIZE]; } widget_dmx; if (!IsEnabled()) return true; widget_dmx.start_code = 0; unsigned int length = DMX_UNIVERSE_SIZE; buffer.Get(widget_dmx.dmx, &length); return m_widget->SendMessage(UsbWidget::DMX_LABEL, length + 1, reinterpret_cast(&widget_dmx)); } /* * Handle an RDM Request, ownership of the request object is transferred to us. */ bool DmxTriDevice::HandleRDMRequest(const ola::rdm::RDMRequest *request) { // if we can't find this UID, fail now. While in discovery mode the // m_uid_index_map will be clear so skip the check in this case. const UID &dest_uid = request->DestinationUID(); if (!dest_uid.IsBroadcast() && !InDiscoveryMode() && m_uid_index_map.find(dest_uid) == m_uid_index_map.end()) { delete request; return false; } m_pending_requests.push(request); MaybeSendRDMRequest(); return true; } /* * Kick off the discovery process if it's not already running */ void DmxTriDevice::RunRDMDiscovery() { if (InDiscoveryMode()) // process already running return; if (!SendDiscoveryStart()) { OLA_WARN << "Failed to begin RDM discovery"; return; } // setup a stat every RDM_STATUS_INTERVAL_MS until we're done m_rdm_timeout_id = m_plugin_adaptor->RegisterRepeatingTimeout( RDM_STATUS_INTERVAL_MS, NewClosure(this, &DmxTriDevice::CheckDiscoveryStatus)); } /* * Check the status of the RDM discovery process. * This is called periodically while discovery is running */ bool DmxTriDevice::CheckDiscoveryStatus() { if (!IsEnabled()) return false; return SendDiscoveryStat(); } /* * Handle a message received from the widget */ void DmxTriDevice::HandleMessage(UsbWidget* widget, uint8_t label, unsigned int length, const uint8_t *data) { if (label == EXTENDED_COMMAND_LABEL) { if (length < DATA_OFFSET) { OLA_WARN << "DMX-TRI frame too small"; return; } uint8_t command_id = data[0]; uint8_t return_code = data[1]; length -= DATA_OFFSET; data = length ? data + DATA_OFFSET: NULL; switch (command_id) { case DISCOVER_AUTO_COMMAND_ID: HandleDiscoveryAutoResponse(return_code, data, length); break; case DISCOVER_STATUS_COMMAND_ID: HandleDiscoverStatResponse(return_code, data, length); break; case REMOTE_UID_COMMAND_ID: HandleRemoteUIDResponse(return_code, data, length); break; case REMOTE_GET_COMMAND_ID: HandleRemoteRDMResponse(return_code, data, length); break; case REMOTE_SET_COMMAND_ID: HandleRemoteRDMResponse(return_code, data, length); break; case QUEUED_GET_COMMAND_ID: HandleQueuedGetResponse(return_code, data, length); break; case SET_FILTER_COMMAND_ID: HandleSetFilterResponse(return_code, data, length); break; default: OLA_WARN << "Unknown DMX-TRI CI: " << static_cast(command_id); } } else { OLA_INFO << "DMX-TRI got response " << static_cast(label); } (void) widget; } /* * Return true if discovery is running */ bool DmxTriDevice::InDiscoveryMode() const { return (m_rdm_timeout_id != ola::network::INVALID_TIMEOUT || m_uid_count); } /* * Send a DiscoAuto message to begin the discovery process. */ bool DmxTriDevice::SendDiscoveryStart() { uint8_t command_id = DISCOVER_AUTO_COMMAND_ID; return m_widget->SendMessage(EXTENDED_COMMAND_LABEL, sizeof(command_id), &command_id); } /* * Send a DiscoAuto message to begin the discovery process. */ void DmxTriDevice::FetchNextUID() { if (!m_uid_count) return; OLA_INFO << "fetching index " << static_cast(m_uid_count); uint8_t data[] = {REMOTE_UID_COMMAND_ID, m_uid_count}; m_widget->SendMessage(EXTENDED_COMMAND_LABEL, sizeof(data), data); } /* * Send a SetFilter command */ bool DmxTriDevice::SendSetFilter(uint16_t esta_id) { uint8_t data[] = {SET_FILTER_COMMAND_ID, esta_id >> 8, esta_id & 0xff}; return m_widget->SendMessage(EXTENDED_COMMAND_LABEL, sizeof(data), reinterpret_cast(&data)); } /* * Send a DiscoStat message to begin the discovery process. */ bool DmxTriDevice::SendDiscoveryStat() { uint8_t command_id = DISCOVER_STATUS_COMMAND_ID; return m_widget->SendMessage(EXTENDED_COMMAND_LABEL, sizeof(command_id), &command_id); } /* * If we're not in discovery mode, send the next request. This will call * SetFilter and defer the send if it's a broadcast UID. */ void DmxTriDevice::MaybeSendRDMRequest() { if (!IsEnabled()) return; if (InDiscoveryMode() || m_pending_requests.empty() || m_rdm_request_pending) return; m_rdm_request_pending = true; const RDMRequest *request = m_pending_requests.front(); if (request->DestinationUID().IsBroadcast() && request->DestinationUID().ManufacturerId() != m_last_esta_id) { SendSetFilter(request->DestinationUID().ManufacturerId()); } else { DispatchNextRequest(); } } /* * Send the next RDM request, this assumes that SetFilter has been called */ void DmxTriDevice::DispatchNextRequest() { const RDMRequest *request = m_pending_requests.front(); if (request->ParamId() == ola::rdm::PID_QUEUED_MESSAGE && request->CommandClass() == RDMCommand::GET_COMMAND) { // these are special if (request->ParamDataSize()) DispatchQueuedGet(request); else OLA_WARN << "Missing param data in queued message get"; return; } struct rdm_message { uint8_t command; uint8_t index; uint16_t sub_device; uint16_t param_id; uint8_t data[ola::rdm::RDMCommand::MAX_PARAM_DATA_LENGTH]; } __attribute__((packed)); rdm_message message; if (request->CommandClass() == RDMCommand::GET_COMMAND) { message.command = REMOTE_GET_COMMAND_ID; } else if (request->CommandClass() == RDMCommand::SET_COMMAND) { message.command = REMOTE_SET_COMMAND_ID; } else { OLA_WARN << "Request was not get or set: " << static_cast(request->CommandClass()); return; } if (request->DestinationUID().IsBroadcast()) { message.index = 0; } else { map::const_iterator iter = m_uid_index_map.find(request->DestinationUID()); if (iter == m_uid_index_map.end()) { OLA_WARN << request->DestinationUID() << " not found in uid map"; return; } message.index = iter->second; } message.sub_device = HostToNetwork(request->SubDevice()); message.param_id = HostToNetwork(request->ParamId()); if (request->ParamDataSize()) memcpy(message.data, request->ParamData(), request->ParamDataSize()); unsigned int size = sizeof(message) - ola::rdm::RDMCommand::MAX_PARAM_DATA_LENGTH + request->ParamDataSize(); m_widget->SendMessage(EXTENDED_COMMAND_LABEL, size, reinterpret_cast(&message)); } /* * Send a queued get message */ void DmxTriDevice::DispatchQueuedGet(const ola::rdm::RDMRequest* request) { map::const_iterator iter = m_uid_index_map.find(request->DestinationUID()); if (iter == m_uid_index_map.end()) { OLA_WARN << request->DestinationUID() << " not found in uid map"; return; } uint8_t data[] = {QUEUED_GET_COMMAND_ID, iter->second, request->ParamData()[0]}; m_widget->SendMessage(EXTENDED_COMMAND_LABEL, sizeof(data), reinterpret_cast(&data)); } /* * Stop the discovery process */ void DmxTriDevice::StopDiscovery() { if (m_rdm_timeout_id != ola::network::INVALID_TIMEOUT) { m_plugin_adaptor->RemoveTimeout(m_rdm_timeout_id); m_rdm_timeout_id = ola::network::INVALID_TIMEOUT; } } /* * Handle the response from calling DiscoAuto */ void DmxTriDevice::HandleDiscoveryAutoResponse(uint8_t return_code, const uint8_t *data, unsigned int length) { if (return_code != EC_NO_ERROR) { OLA_WARN << "DMX_TRI discovery returned error " << static_cast(return_code); StopDiscovery(); } (void) data; (void) length; } /* * Handle the response from calling discovery stat */ void DmxTriDevice::HandleDiscoverStatResponse(uint8_t return_code, const uint8_t *data, unsigned int length) { switch (return_code) { case EC_NO_ERROR: break; case EC_RESPONSE_MUTE: OLA_WARN << "Failed to mute device, aborting discovery"; StopDiscovery(); return; case EC_RESPONSE_DISCOVERY: OLA_WARN << "Duplicated or erroneous device detected, aborting discovery"; StopDiscovery(); return; case EC_RESPONSE_UNEXPECTED: OLA_INFO << "Got an unexpected RDM response during discovery"; break; default: OLA_WARN << "DMX_TRI discovery returned error " << static_cast(return_code); StopDiscovery(); return; } if (length < 1) { OLA_WARN << "DiscoStat command too short, was " << length; return; } if (data[1] == 0) { OLA_DEBUG << "Discovery process has completed, " << static_cast(data[0]) << " devices found"; StopDiscovery(); m_uid_count = data[0]; m_uid_index_map.clear(); if (m_uid_count) FetchNextUID(); } } /* * Handle the response to a RemoteGet command */ void DmxTriDevice::HandleRemoteUIDResponse(uint8_t return_code, const uint8_t *data, unsigned int length) { if (!m_uid_count) { // not expecting any responses OLA_INFO << "Got an unexpected RemoteUID response"; return; } if (return_code == EC_NO_ERROR) { if (length < ola::rdm::UID::UID_SIZE) { OLA_INFO << "Short RemoteUID response, was " << length; } else { const UID uid(data); m_uid_index_map[uid] = m_uid_count; } } else if (return_code == EC_CONSTRAINT) { // this is returned if the index is wrong OLA_INFO << "RemoteUID returned RC_Constraint, some how we botched the" << " discovery process, subtracting 1 and attempting to continue"; } else { OLA_INFO << "RemoteUID returned " << static_cast(return_code); } m_uid_count--; if (m_uid_count) { FetchNextUID(); } else { // notify the universe UIDSet uid_set; map::iterator iter = m_uid_index_map.begin(); for (; iter != m_uid_index_map.end(); ++iter) { uid_set.AddUID(iter->first); } GetOutputPort(0)->NewUIDList(uid_set); // start sending rdm commands again MaybeSendRDMRequest(); } } /* * Handle the response to a RemoteGet command */ void DmxTriDevice::HandleRemoteRDMResponse(uint8_t return_code, const uint8_t *data, unsigned int length) { OLA_WARN << "got get response! " << static_cast(return_code) << " length " << length; const RDMRequest *request = m_pending_requests.front(); if (return_code == EC_NO_ERROR || return_code == EC_RESPONSE_WAIT || return_code == EC_RESPONSE_MORE) { ola::rdm::RDMResponse *response; if (request->CommandClass() == RDMCommand::GET_COMMAND) { response = new ola::rdm::RDMGetResponse( request->DestinationUID(), request->SourceUID(), request->TransactionNumber(), ola::rdm::ACK, // this is a hack, there is no way to expose # of queues messages return_code == EC_RESPONSE_WAIT ? 1 : 0, request->SubDevice(), request->ParamId(), data, length); } else { response = new ola::rdm::RDMSetResponse( request->DestinationUID(), request->SourceUID(), request->TransactionNumber(), ola::rdm::ACK, // this is a hack, there is no way to expose # of queues messages return_code == EC_RESPONSE_WAIT ? 1 : 0, request->SubDevice(), request->ParamId(), data, length); } if (m_rdm_response) { // if this is part of an overflowed response we need to combine it ola::rdm::RDMResponse *combined_response = ola::rdm::RDMResponse::CombineResponses(m_rdm_response, response); delete m_rdm_response; delete response; m_rdm_response = combined_response; } else { m_rdm_response = response; } if (m_rdm_response) { if (return_code == EC_RESPONSE_MORE) { // send the same command again; DispatchNextRequest(); } else { GetOutputPort(0)->HandleRDMResponse(m_rdm_response); m_rdm_response = NULL; } } } else { // TODO(simonn): Implement the correct response here when we error out OLA_WARN << "Response was returned with 0x" << std::hex << static_cast(return_code); if (m_rdm_response) { delete m_rdm_response; m_rdm_response = NULL; } } delete request; m_pending_requests.pop(); m_rdm_request_pending = false; } /* * Handle the response to a QueuedGet command */ void DmxTriDevice::HandleQueuedGetResponse(uint8_t return_code, const uint8_t *data, unsigned int length) { OLA_INFO << "got queued message response"; // TODO(simon): implement this (void) return_code; (void) data; (void) length; } /* * Handle a setfilter response */ void DmxTriDevice::HandleSetFilterResponse(uint8_t return_code, const uint8_t *data, unsigned int length) { if (return_code == EC_NO_ERROR) { m_last_esta_id = m_pending_requests.front()->DestinationUID().ManufacturerId(); DispatchNextRequest(); } else { OLA_WARN << "SetFilter returned " << static_cast(return_code) << ", we have no option but to drop the rdm request"; delete m_pending_requests.front(); m_pending_requests.pop(); m_rdm_request_pending = false; MaybeSendRDMRequest(); } (void) data; (void) length; } } // usbpro } // plugin } // ola