syndilights/open-lighting-architecture/ola-0.8.4/plugins/usbpro/DmxTriDevice.cpp

621 lines
17 KiB
C++

/*
* 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 <string.h>
#include <algorithm>
#include <map>
#include <string>
#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<uint8_t*>(&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<int>(command_id);
}
} else {
OLA_INFO << "DMX-TRI got response " << static_cast<int>(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<int>(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<uint8_t*>(&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<int>(request->CommandClass());
return;
}
if (request->DestinationUID().IsBroadcast()) {
message.index = 0;
} else {
map<UID, uint8_t>::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<uint8_t*>(&message));
}
/*
* Send a queued get message
*/
void DmxTriDevice::DispatchQueuedGet(const ola::rdm::RDMRequest* request) {
map<UID, uint8_t>::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<uint8_t*>(&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<int>(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<int>(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<int>(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<int>(return_code);
}
m_uid_count--;
if (m_uid_count) {
FetchNextUID();
} else {
// notify the universe
UIDSet uid_set;
map<UID, uint8_t>::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<int>(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<int>(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<int>(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