• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OpenLightingProject / ola / 24609073669

18 Apr 2026 04:36PM UTC coverage: 44.864% (-0.9%) from 45.72%
24609073669

push

github

web-flow
Merge pull request #2043 from peternewman/nortle-ftdi

Revert some stuff via 146cf26 which accidentally snuck into #1999 by mistake

8554 of 19846 branches covered (43.1%)

22105 of 49271 relevant lines covered (44.86%)

48.53 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

62.24
/plugins/usbpro/DmxTriWidget.cpp
1
/*
2
 * This program is free software; you can redistribute it and/or modify
3
 * it under the terms of the GNU General Public License as published by
4
 * the Free Software Foundation; either version 2 of the License, or
5
 * (at your option) any later version.
6
 *
7
 * This program is distributed in the hope that it will be useful,
8
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 * GNU Library General Public License for more details.
11
 *
12
 * You should have received a copy of the GNU General Public License
13
 * along with this program; if not, write to the Free Software
14
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
15
 *
16
 * DmxTriWidget.cpp
17
 * The Jese DMX TRI device.
18
 * Copyright (C) 2010 Simon Newton
19
 */
20

21
#include <string.h>
22
#include <map>
23
#include <memory>
24
#include <string>
25
#include <vector>
26
#include "ola/Constants.h"
27
#include "ola/Logging.h"
28
#include "ola/base/Array.h"
29
#include "ola/io/ByteString.h"
30
#include "ola/network/NetworkUtils.h"
31
#include "ola/rdm/RDMCommand.h"
32
#include "ola/rdm/RDMCommandSerializer.h"
33
#include "ola/rdm/RDMEnums.h"
34
#include "ola/rdm/UID.h"
35
#include "ola/rdm/UIDSet.h"
36
#include "ola/stl/STLUtils.h"
37
#include "ola/strings/Format.h"
38
#include "plugins/usbpro/BaseUsbProWidget.h"
39
#include "plugins/usbpro/DmxTriWidget.h"
40

41
namespace ola {
42
namespace plugin {
43
namespace usbpro {
44

45
using ola::io::ByteString;
46
using ola::network::HostToNetwork;
47
using ola::network::NetworkToHost;
48
using ola::rdm::RDMCommand;
49
using ola::rdm::RDMCommandSerializer;
50
using ola::rdm::RDMDiscoveryCallback;
51
using ola::rdm::RDMReply;
52
using ola::rdm::RDMRequest;
53
using ola::rdm::RunRDMCallback;
54
using ola::rdm::UID;
55
using ola::rdm::UIDSet;
56
using ola::strings::ToHex;
57
using std::auto_ptr;
58
using std::map;
59
using std::string;
60
using std::vector;
61

62
/*
63
 * New DMX TRI Widget
64
 */
65
DmxTriWidgetImpl::DmxTriWidgetImpl(
13✔
66
    ola::thread::SchedulerInterface *scheduler,
67
    ola::io::ConnectedDescriptor *descriptor,
68
    bool use_raw_rdm)
13✔
69
    : BaseUsbProWidget(descriptor),
70
      m_scheduler(scheduler),
13✔
71
      m_uid_count(0),
13✔
72
      m_last_esta_id(UID::ALL_MANUFACTURERS),
13✔
73
      m_use_raw_rdm(use_raw_rdm),
13✔
74
      m_disc_stat_timeout_id(ola::thread::INVALID_TIMEOUT),
13✔
75
      m_discovery_callback(NULL),
13✔
76
      m_discovery_state(NO_DISCOVERY_ACTION),
13✔
77
      m_rdm_request_callback(NULL),
13✔
78
      m_pending_rdm_request(NULL),
13✔
79
      m_transaction_number(0),
13✔
80
      m_last_command(RESERVED_COMMAND_ID),
13✔
81
      m_expected_command(RESERVED_COMMAND_ID) {
13✔
82
}
13✔
83

84

85
/*
86
 * Shutdown
87
 */
88
DmxTriWidgetImpl::~DmxTriWidgetImpl() {
13✔
89
  Stop();
13✔
90
}
13✔
91

92

93
/**
94
 * Stop the rdm discovery process if it's running
95
 */
96
void DmxTriWidgetImpl::Stop() {
13✔
97
  if (m_disc_stat_timeout_id != ola::thread::INVALID_TIMEOUT) {
13✔
98
    m_scheduler->RemoveTimeout(m_disc_stat_timeout_id);
×
99
    m_disc_stat_timeout_id = ola::thread::INVALID_TIMEOUT;
×
100
  }
101

102
  // timeout any existing message
103
  if (m_rdm_request_callback) {
13✔
104
    HandleRDMError(ola::rdm::RDM_TIMEOUT);
×
105
  }
106

107
  if (m_discovery_callback) {
13✔
108
    RDMDiscoveryCallback *callback = m_discovery_callback;
×
109
    m_discovery_callback = NULL;
×
110
    RunDiscoveryCallback(callback);
×
111
  }
112
}
13✔
113

114

115
/**
116
 * Send a DMX frame. This may queue the frame if there is a transaction
117
 * pending.
118
 */
119
bool DmxTriWidgetImpl::SendDMX(const DmxBuffer &buffer) {
3✔
120
  // Update the buffer, if there is already a frame pending, we send take the
121
  // latest one.
122
  if (m_outgoing_dmx.Size()) {
3✔
123
    OLA_INFO << "TRI widget dropping frame";
1✔
124
  }
125
  m_outgoing_dmx = buffer;
3✔
126
  MaybeSendNextRequest();
3✔
127
  return true;
3✔
128
}
129

130

131
/*
132
 * Send an RDM request. Because this is wrapped in a QueueingRDMController it's
133
 * only going to be called one-at-a-time. This will queue the RDM request if
134
 * there is a transaction pending.
135
 */
136
void DmxTriWidgetImpl::SendRDMRequest(RDMRequest *request_ptr,
21✔
137
                                      ola::rdm::RDMCallback *on_complete) {
138
  auto_ptr<RDMRequest> request(request_ptr);
21✔
139

140
  if (request->CommandClass() == RDMCommand::DISCOVER_COMMAND &&
21✔
141
      !m_use_raw_rdm) {
1✔
142
    RunRDMCallback(on_complete, ola::rdm::RDM_PLUGIN_DISCOVERY_NOT_SUPPORTED);
1✔
143
    return;
144
  }
145

146
  if (m_rdm_request_callback) {
20✔
147
    OLA_FATAL << "Previous request hasn't completed yet, dropping request";
×
148
    RunRDMCallback(on_complete, ola::rdm::RDM_FAILED_TO_SEND);
×
149
    return;
150
  }
151

152
  // store pointers
153
  m_pending_rdm_request.reset(request.release());
20✔
154
  m_rdm_request_callback = on_complete;
20✔
155
  MaybeSendNextRequest();
20✔
156
}
21✔
157

158

159
/**
160
 * Start the discovery process
161
 */
162
void DmxTriWidgetImpl::RunFullDiscovery(RDMDiscoveryCallback *callback) {
14✔
163
  // Right now there is no difference between full and incremental discovery.
164
  RunIncrementalDiscovery(callback);
14✔
165
}
14✔
166

167

168
/**
169
 * Start incremental discovery, which on the TRI is the same as full.
170
 */
171
void DmxTriWidgetImpl::RunIncrementalDiscovery(
14✔
172
    RDMDiscoveryCallback *callback) {
173
  if (m_discovery_callback) {
14✔
174
    OLA_FATAL << "Call to RunFullDiscovery while discovery is already running"
×
175
      << ", the DiscoverableQueueingRDMController has broken!";
×
176
    // the best we can do is run the callback now
177
    RunDiscoveryCallback(callback);
×
178
    return;
×
179
  }
180

181
  m_discovery_callback = callback;
14✔
182
  m_discovery_state = DISCOVER_AUTO_REQUIRED;
14✔
183
  MaybeSendNextRequest();
14✔
184
}
185

186

187
/**
188
 * Send the outstanding DMXBuffer.
189
 */
190
void DmxTriWidgetImpl::SendDMXBuffer() {
2✔
191
  // CommandID, Options, Start Code + data
192
  uint8_t send_buffer[3 + DMX_UNIVERSE_SIZE];
2✔
193
  send_buffer[0] = SINGLE_TX_COMMAND_ID;
2✔
194
  send_buffer[1] = 0;  // wait for command completion
2✔
195
  send_buffer[2] = DMX512_START_CODE;
2✔
196

197
  unsigned int length = DMX_UNIVERSE_SIZE;
2✔
198
  m_outgoing_dmx.Get(send_buffer + 3, &length);
2✔
199
  m_outgoing_dmx.Reset();
2✔
200
  SendCommandToTRI(EXTENDED_COMMAND_LABEL, send_buffer, length + 3);
2✔
201
}
2✔
202

203

204
/**
205
 * Send the queued up RDM command.
206
 */
207
void DmxTriWidgetImpl::SendQueuedRDMCommand() {
20✔
208
  // If we can't find this UID, fail now.
209
  const UID &dest_uid = m_pending_rdm_request->DestinationUID();
20✔
210
  if (!dest_uid.IsBroadcast() && !STLContains(m_uid_index_map, dest_uid)) {
20✔
211
    HandleRDMError(ola::rdm::RDM_UNKNOWN_UID);
1✔
212
    return;
1✔
213
  }
214

215
  if (m_use_raw_rdm) {
19✔
216
    SendRawRDMRequest();
×
217
    return;
×
218
  }
219

220
  if (dest_uid.IsBroadcast() && dest_uid.ManufacturerId() != m_last_esta_id) {
19✔
221
    // we have to send a Set filter command
222
    uint16_t esta_id = dest_uid.ManufacturerId();
3✔
223
    uint8_t data[] = {SET_FILTER_COMMAND_ID,
3✔
224
                      static_cast<uint8_t>(esta_id >> 8),
3✔
225
                      static_cast<uint8_t>(esta_id & 0xff)
226
                     };
3✔
227
    if (!SendCommandToTRI(EXTENDED_COMMAND_LABEL,
3✔
228
                          reinterpret_cast<uint8_t*>(&data),
229
                          sizeof(data))) {
230
      OLA_INFO << "Failed to send set filter, aborting request";
×
231
      HandleRDMError(ola::rdm::RDM_FAILED_TO_SEND);
×
232
    }
233
    return;
3✔
234
  } else {
235
    DispatchRequest();
16✔
236
  }
237
}
238

239

240
/**
241
 * Call the UIDSet handler with the latest UID list.
242
 */
243
void DmxTriWidgetImpl::RunDiscoveryCallback(RDMDiscoveryCallback *callback) {
14✔
244
  if (!callback)
14✔
245
    return;
×
246

247
  UIDSet uid_set;
14✔
248
  UIDToIndexMap::iterator iter = m_uid_index_map.begin();
14✔
249
  for (; iter != m_uid_index_map.end(); ++iter) {
36✔
250
    uid_set.AddUID(iter->first);
44✔
251
  }
252
  callback->Run(uid_set);
14✔
253
}
14✔
254

255

256
/*
257
 * Check the status of the RDM discovery process.
258
 * This is called periodically while discovery is running
259
 */
260
bool DmxTriWidgetImpl::CheckDiscoveryStatus() {
13✔
261
  m_discovery_state = DISCOVER_STATUS_REQUIRED;
13✔
262
  MaybeSendNextRequest();
13✔
263
  return true;
13✔
264
}
265

266

267
/*
268
 * Handle a message received from the widget
269
 */
270
void DmxTriWidgetImpl::HandleMessage(uint8_t label,
69✔
271
                                     const uint8_t *data,
272
                                     unsigned int length) {
273
  if (label == EXTENDED_COMMAND_LABEL) {
69✔
274
    if (length < DATA_OFFSET) {
69✔
275
      OLA_WARN << "DMX-TRI frame too small";
×
276
      return;
×
277
    }
278

279
    uint8_t command_id = data[0];
69✔
280
    uint8_t return_code = data[1];
69✔
281
    length -= DATA_OFFSET;
69✔
282
    data = length ? data + DATA_OFFSET: NULL;
69✔
283

284
    if (command_id != m_expected_command) {
69✔
285
      OLA_WARN << "Received an unexpected command response, expected "
×
286
               << ToHex(m_expected_command) << ", got " << ToHex(command_id);
×
287
    }
288
    m_last_command = m_expected_command;
69✔
289
    m_expected_command = RESERVED_COMMAND_ID;
69✔
290

291
    switch (command_id) {
69✔
292
      case SINGLE_TX_COMMAND_ID:
2✔
293
        HandleSingleTXResponse(return_code);
2✔
294
        break;
2✔
295
      case DISCOVER_AUTO_COMMAND_ID:
13✔
296
        HandleDiscoveryAutoResponse(return_code, data, length);
13✔
297
        break;
13✔
298
      case DISCOVER_STATUS_COMMAND_ID:
13✔
299
        HandleDiscoverStatResponse(return_code, data, length);
13✔
300
        break;
13✔
301
      case REMOTE_UID_COMMAND_ID:
20✔
302
        HandleRemoteUIDResponse(return_code, data, length);
20✔
303
        break;
20✔
304
      case RAW_RDM_COMMAND_ID:
×
305
        HandleRawRDMResponse(return_code, data, length);
×
306
        break;
×
307
      case REMOTE_GET_COMMAND_ID:
16✔
308
        HandleRemoteRDMResponse(return_code, data, length);
16✔
309
        break;
16✔
310
      case REMOTE_SET_COMMAND_ID:
×
311
        HandleRemoteRDMResponse(return_code, data, length);
×
312
        break;
×
313
      case QUEUED_GET_COMMAND_ID:
2✔
314
        HandleQueuedGetResponse(return_code, data, length);
2✔
315
        break;
2✔
316
      case SET_FILTER_COMMAND_ID:
3✔
317
        HandleSetFilterResponse(return_code, data, length);
3✔
318
        break;
3✔
319
      default:
×
320
        OLA_WARN << "Unknown DMX-TRI CI: " << ToHex(command_id);
×
321
    }
322
  } else {
323
    OLA_INFO << "DMX-TRI got response " << static_cast<int>(label);
×
324
  }
325
}
326

327

328
/*
329
 * Send a DiscoAuto message to begin the discovery process.
330
 */
331
void DmxTriWidgetImpl::SendDiscoveryAuto() {
14✔
332
  m_discovery_state = NO_DISCOVERY_ACTION;
14✔
333
  uint8_t command_id = DISCOVER_AUTO_COMMAND_ID;
14✔
334
  if (!SendCommandToTRI(EXTENDED_COMMAND_LABEL, &command_id,
14✔
335
                        sizeof(command_id))) {
336
    OLA_WARN << "Unable to begin RDM discovery";
1✔
337
    RDMDiscoveryCallback *callback = m_discovery_callback;
1✔
338
    m_discovery_callback = NULL;
1✔
339
    RunDiscoveryCallback(callback);
1✔
340
  } else {
341
    // setup a stat every RDM_STATUS_INTERVAL_MS until we're done
342
    m_disc_stat_timeout_id = m_scheduler->RegisterRepeatingTimeout(
13✔
343
        RDM_STATUS_INTERVAL_MS,
344
        NewCallback(this, &DmxTriWidgetImpl::CheckDiscoveryStatus));
345
  }
346
}
14✔
347

348

349
/*
350
 * Send a RemoteUID message to fetch UID in TRI register.
351
 */
352
void DmxTriWidgetImpl::FetchNextUID() {
20✔
353
  m_discovery_state = NO_DISCOVERY_ACTION;
20✔
354
  if (!m_uid_count)
20✔
355
    return;
×
356

357
  OLA_INFO << "Fetching index  " << static_cast<int>(m_uid_count);
20✔
358
  uint8_t data[] = {REMOTE_UID_COMMAND_ID, m_uid_count};
20✔
359
  SendCommandToTRI(EXTENDED_COMMAND_LABEL, data, sizeof(data));
20✔
360
}
361

362
/*
363
 * Send a DiscoStat message to get status of the discovery process.
364
 */
365
void DmxTriWidgetImpl::SendDiscoveryStat() {
13✔
366
  m_discovery_state = NO_DISCOVERY_ACTION;
13✔
367
  uint8_t command = DISCOVER_STATUS_COMMAND_ID;
13✔
368
  if (!SendCommandToTRI(EXTENDED_COMMAND_LABEL, &command, sizeof(command))) {
13✔
369
    RDMDiscoveryCallback *callback = m_discovery_callback;
×
370
    m_discovery_callback = NULL;
×
371
    RunDiscoveryCallback(callback);
×
372
  }
373
}
13✔
374

375

376
/**
377
 * Send a raw RDM command, bypassing all the handling the RDM-TRI does.
378
 */
379
void DmxTriWidgetImpl::SendRawRDMRequest() {
×
380
  m_pending_rdm_request->SetTransactionNumber(m_transaction_number);
×
381
  m_pending_rdm_request->SetPortId(1);  // port id is always 1
×
382

383
  // add two bytes for the command & option field
384
  ByteString data;
×
385
  data.push_back(RAW_RDM_COMMAND_ID);
×
386
  // a 2 means we don't wait for a break in the response.
387
  data.push_back(m_pending_rdm_request->IsDUB() ? 2 : 0);
×
388

389
  if (!RDMCommandSerializer::Pack(*m_pending_rdm_request, &data)) {
×
390
    OLA_WARN << "Failed to pack RDM request";
×
391
    HandleRDMError(ola::rdm::RDM_FAILED_TO_SEND);
×
392
    return;
×
393
  }
394

395
  OLA_INFO << "Sending raw request to "
×
396
           << m_pending_rdm_request->DestinationUID()
×
397
           << " with command " << ToHex(m_pending_rdm_request->CommandClass())
×
398
           << " and param " << ToHex(m_pending_rdm_request->ParamId());
×
399

400
  if (SendCommandToTRI(EXTENDED_COMMAND_LABEL, data.data(), data.size())) {
×
401
    m_transaction_number++;
×
402
  } else {
403
    HandleRDMError(ola::rdm::RDM_FAILED_TO_SEND);
×
404
  }
405
}
×
406

407

408
/*
409
 * Send the next RDM request, this assumes that SetFilter has been called
410
 */
411
void DmxTriWidgetImpl::DispatchRequest() {
18✔
412
  const ola::rdm::RDMRequest *request = m_pending_rdm_request.get();
18✔
413
  if (request->ParamId() == ola::rdm::PID_QUEUED_MESSAGE &&
20✔
414
      request->CommandClass() == RDMCommand::GET_COMMAND) {
2✔
415
    // these are special
416
    if (request->ParamDataSize()) {
2✔
417
      DispatchQueuedGet();
2✔
418
    } else {
419
      OLA_WARN << "Missing param data in queued message get";
×
420
      HandleRDMError(ola::rdm::RDM_FAILED_TO_SEND);
×
421
    }
422
    return;
2✔
423
  }
424

425
  PACK(
16✔
426
  struct rdm_message {
427
    uint8_t command;
428
    uint8_t index;
429
    uint16_t sub_device;
430
    uint16_t param_id;
431
    uint8_t data[RDMCommandSerializer::MAX_PARAM_DATA_LENGTH];
432
  });
433

434
  rdm_message message;
16✔
435

436
  if (request->CommandClass() == RDMCommand::GET_COMMAND) {
16✔
437
    message.command = REMOTE_GET_COMMAND_ID;
16✔
438
  } else if (request->CommandClass() == RDMCommand::SET_COMMAND) {
×
439
    message.command = REMOTE_SET_COMMAND_ID;
×
440
  } else {
441
    OLA_WARN << "Request was not get or set: "
×
442
             << static_cast<int>(request->CommandClass());
×
443
    HandleRDMError(ola::rdm::RDM_FAILED_TO_SEND);
×
444
    return;
×
445
  }
446

447
  if (request->DestinationUID().IsBroadcast()) {
16✔
448
    message.index = 0;
3✔
449
  } else {
450
    UIDToIndexMap::const_iterator iter =
13✔
451
      m_uid_index_map.find(request->DestinationUID());
13✔
452
    if (iter == m_uid_index_map.end()) {
13✔
453
      OLA_WARN << request->DestinationUID() << " not found in uid map";
×
454
      HandleRDMError(ola::rdm::RDM_UNKNOWN_UID);
×
455
      return;
×
456
    }
457
    message.index = iter->second;
13✔
458
  }
459
  message.sub_device = HostToNetwork(request->SubDevice());
16✔
460
  message.param_id = HostToNetwork(request->ParamId());
16✔
461
  if (request->ParamDataSize())
16✔
462
    memcpy(message.data, request->ParamData(), request->ParamDataSize());
2✔
463

464
  unsigned int size = sizeof(message) -
16✔
465
    RDMCommandSerializer::MAX_PARAM_DATA_LENGTH + request->ParamDataSize();
16✔
466

467
  OLA_INFO << "Sending request to " << request->DestinationUID()
48✔
468
           << " with command " << ToHex(request->CommandClass())
16✔
469
           << " and param " << ToHex(request->ParamId());
16✔
470

471
  bool r = SendCommandToTRI(EXTENDED_COMMAND_LABEL,
16✔
472
                            reinterpret_cast<uint8_t*>(&message),
473
                            size);
474
  if (!r) {
16✔
475
    HandleRDMError(ola::rdm::RDM_FAILED_TO_SEND);
×
476
  }
477
}
478

479

480
/*
481
 * Send a queued get message
482
 */
483
void DmxTriWidgetImpl::DispatchQueuedGet() {
2✔
484
  UIDToIndexMap::const_iterator iter =
2✔
485
    m_uid_index_map.find(m_pending_rdm_request->DestinationUID());
2✔
486
  if (iter == m_uid_index_map.end()) {
2✔
487
    OLA_WARN << m_pending_rdm_request->DestinationUID()
×
488
             << " not found in uid map";
×
489
    HandleRDMError(ola::rdm::RDM_FAILED_TO_SEND);
×
490
    return;
×
491
  }
492
  uint8_t data[3] = {QUEUED_GET_COMMAND_ID,
2✔
493
                     iter->second,
2✔
494
                     m_pending_rdm_request->ParamData()[0]};
2✔
495

496
  if (!SendCommandToTRI(EXTENDED_COMMAND_LABEL,
2✔
497
                        reinterpret_cast<uint8_t*>(&data),
498
                        arraysize(data))) {
499
    HandleRDMError(ola::rdm::RDM_FAILED_TO_SEND);
×
500
  }
501
}
502

503

504
/*
505
 * Stop the discovery process
506
 */
507
void DmxTriWidgetImpl::StopDiscovery() {
13✔
508
  if (m_disc_stat_timeout_id != ola::thread::INVALID_TIMEOUT) {
13✔
509
    m_scheduler->RemoveTimeout(m_disc_stat_timeout_id);
13✔
510
    m_disc_stat_timeout_id = ola::thread::INVALID_TIMEOUT;
13✔
511
  }
512
}
13✔
513

514

515
/**
516
 * Handle the response from Single TX.
517
 */
518
void DmxTriWidgetImpl::HandleSingleTXResponse(uint8_t return_code) {
2✔
519
  if (return_code != EC_NO_ERROR)
2✔
520
    OLA_WARN << "Error sending DMX data. TRI return code was "
×
521
             << ToHex(return_code);
×
522
  MaybeSendNextRequest();
2✔
523
}
2✔
524

525

526
/*
527
 * Handle the response from calling DiscoAuto
528
 */
529
void DmxTriWidgetImpl::HandleDiscoveryAutoResponse(uint8_t return_code,
13✔
530
                                                   const uint8_t*,
531
                                                   unsigned int) {
532
  if (return_code != EC_NO_ERROR) {
13✔
533
    if (return_code == EC_UNKNOWN_COMMAND)
1✔
534
      OLA_INFO << "This DMX-TRI doesn't support RDM";
1✔
535
    else
536
      OLA_WARN << "DMX_TRI discovery returned error " <<
×
537
        static_cast<int>(return_code);
×
538
    StopDiscovery();
1✔
539
    RDMDiscoveryCallback *callback = m_discovery_callback;
1✔
540
    m_discovery_callback = NULL;
1✔
541
    RunDiscoveryCallback(callback);
1✔
542
  }
543
}
13✔
544

545

546
/*
547
 * Handle the response from calling discovery stat
548
 */
549
void DmxTriWidgetImpl::HandleDiscoverStatResponse(uint8_t return_code,
13✔
550
                                                  const uint8_t *data,
551
                                                  unsigned int length) {
552
  if (return_code == EC_NO_ERROR || return_code == EC_RESPONSE_UNEXPECTED) {
13✔
553
    if (return_code == EC_RESPONSE_UNEXPECTED)
12✔
554
      OLA_INFO << "Got an unexpected RDM response during discovery";
×
555

556
    if (length < 1) {
12✔
557
      // don't cancel the timer, try again after RDM_STATUS_INTERVAL_MS
558
      OLA_WARN << "DiscoStat command too short, was " << length;
×
559
      return;
×
560
    }
561

562
    if (data[1] == 0) {
12✔
563
      OLA_DEBUG << "Discovery process has completed, "
11✔
564
                << static_cast<int>(data[0]) << " devices found";
×
565
      StopDiscovery();
11✔
566
      m_uid_count = data[0];
11✔
567
      m_uid_index_map.clear();
11✔
568
      if (m_uid_count) {
11✔
569
        m_discovery_state = FETCH_UID_REQUIRED;
10✔
570
        MaybeSendNextRequest();
10✔
571
      } else {
572
        RDMDiscoveryCallback *callback = m_discovery_callback;
1✔
573
        m_discovery_callback = NULL;
1✔
574
        RunDiscoveryCallback(callback);
1✔
575
      }
576
    }
577
  } else {
578
    // These are all fatal
579
    switch (return_code) {
1✔
580
      case EC_RESPONSE_MUTE:
1✔
581
        OLA_WARN << "Unable to mute device, aborting discovery";
1✔
582
        break;
1✔
583
      case EC_RESPONSE_DISCOVERY:
×
584
        OLA_WARN <<
×
585
          "Duplicated or erroneous device detected, aborting discovery";
×
586
        break;
×
587
      default:
×
588
        OLA_WARN << "DMX_TRI discovery returned error " <<
×
589
          static_cast<int>(return_code);
×
590
        break;
×
591
    }
592
    // clear out the old map
593
    m_uid_index_map.clear();
1✔
594
    StopDiscovery();
1✔
595
    RDMDiscoveryCallback *callback = m_discovery_callback;
1✔
596
    m_discovery_callback = NULL;
1✔
597
    RunDiscoveryCallback(callback);
1✔
598
  }
599
}
600

601

602
/*
603
 * Handle the response to a RemoteGet command
604
 */
605
void DmxTriWidgetImpl::HandleRemoteUIDResponse(uint8_t return_code,
20✔
606
                                               const uint8_t *data,
607
                                               unsigned int length) {
608
  if (!m_uid_count) {
20✔
609
    // not expecting any responses
610
    OLA_INFO << "Got an unexpected RemoteUID response";
×
611
    return;
×
612
  }
613

614
  if (return_code == EC_NO_ERROR) {
20✔
615
    if (length < ola::rdm::UID::UID_SIZE) {
20✔
616
      OLA_INFO << "Short RemoteUID response, was " << length;
×
617
    } else {
618
      const UID uid(data);
20✔
619
      m_uid_index_map[uid] = m_uid_count;
20✔
620
    }
621
  } else if (return_code == EC_CONSTRAINT) {
×
622
    // this is returned if the index is wrong
623
    OLA_INFO << "RemoteUID returned RC_Constraint, some how we botched the"
×
624
      << " discovery process, subtracting 1 and attempting to continue";
×
625
  } else {
626
    OLA_INFO << "RemoteUID returned " << static_cast<int>(return_code);
×
627
  }
628

629
  m_uid_count--;
20✔
630

631
  if (m_uid_count) {
20✔
632
    m_discovery_state = FETCH_UID_REQUIRED;
10✔
633
    MaybeSendNextRequest();
10✔
634
  } else {
635
    RDMDiscoveryCallback *callback = m_discovery_callback;
10✔
636
    m_discovery_callback = NULL;
10✔
637
    RunDiscoveryCallback(callback);
10✔
638
  }
639
}
640

641

642
/*
643
 * Handle the response to a raw RDM command
644
 * data will be NULL, if length is 0.
645
 */
646
void DmxTriWidgetImpl::HandleRawRDMResponse(uint8_t return_code,
×
647
                                            const uint8_t *data,
648
                                            unsigned int length) {
649
  OLA_INFO << "got raw RDM response with code: " << ToHex(return_code)
×
650
           << ", length: " << length;
×
651

652
  auto_ptr<ola::rdm::RDMRequest> request(m_pending_rdm_request);
×
653
  ola::rdm::RDMCallback *callback = m_rdm_request_callback;
×
654
  m_pending_rdm_request.reset();
×
655
  m_rdm_request_callback = NULL;
×
656

657
  if (callback == NULL || request.get() == NULL) {
×
658
    OLA_FATAL << "Got a response but missing callback or request object!";
×
659
    return;
×
660
  }
661

662
  if (return_code == EC_UNKNOWN_COMMAND) {
×
663
    // This means raw mode isn't supported and we should fall back to the
664
    // default mode
665
    m_use_raw_rdm = false;
×
666
    OLA_WARN <<
×
667
      "Raw RDM mode not supported, switching back to the managed RDM mode";
×
668
    SendRDMRequest(request.release(), callback);
×
669
    return;
670
  }
671

672
  // handle responses to DUB commands
673
  if (request->IsDUB()) {
×
674
    if (return_code == EC_RESPONSE_NONE) {
×
675
      RunRDMCallback(callback, ola::rdm::RDM_TIMEOUT);
×
676
    } else if (return_code == EC_NO_ERROR ||
×
677
               return_code == EC_RESPONSE_DISCOVERY) {
×
678
      rdm::RDMFrame frame(data, length);
×
679
      auto_ptr<RDMReply> reply(RDMReply::DUBReply(frame));
×
680
      callback->Run(reply.get());
×
681
    } else {
×
682
      OLA_WARN << "Un-handled DUB response " << ToHex(return_code);
×
683
      RunRDMCallback(callback, ola::rdm::RDM_INVALID_RESPONSE);
×
684
    }
685
    return;
×
686
  }
687

688
  const UID &dest_uid = request->DestinationUID();
×
689

690
  if (dest_uid.IsBroadcast()) {
×
691
    if (return_code != EC_RESPONSE_NONE) {
×
692
      OLA_WARN << "Unexpected response to broadcast message";
×
693
    }
694
    RunRDMCallback(callback, ola::rdm::RDM_WAS_BROADCAST);
×
695
    return;
696
  }
697

698
  if (return_code == EC_RESPONSE_NONE) {
×
699
    RunRDMCallback(callback, ola::rdm::RDM_TIMEOUT);
×
700
    return;
701
  }
702

703
  rdm::RDMFrame::Options options;
×
704
  options.prepend_start_code = true;
×
705
  auto_ptr<RDMReply> reply(RDMReply::FromFrame(
×
706
        rdm::RDMFrame(data, length, options)));
×
707
  callback->Run(reply.get());
×
708
}
×
709

710

711
/*
712
 * Handle the response to a Remote Get/Set command
713
 */
714
void DmxTriWidgetImpl::HandleRemoteRDMResponse(uint8_t return_code,
16✔
715
                                               const uint8_t *data,
716
                                               unsigned int length) {
717
  if (m_pending_rdm_request.get() == NULL) {
16✔
718
    OLA_FATAL << "Got a response but missing callback or request object!";
×
719
    return;
×
720
  }
721

722
  OLA_INFO << "Received RDM response with code " << ToHex(return_code)
48✔
723
           << ", " << length << " bytes, param "
16✔
724
           << ToHex(m_pending_rdm_request->ParamId());
16✔
725

726
  HandleGenericRDMResponse(return_code,
16✔
727
                           m_pending_rdm_request->ParamId(),
16✔
728
                           data,
729
                           length);
730
}
731

732

733
/*
734
 * Handle the response to a QueuedGet command
735
 */
736
void DmxTriWidgetImpl::HandleQueuedGetResponse(uint8_t return_code,
2✔
737
                                               const uint8_t *data,
738
                                               unsigned int length) {
739
  if (length < 2) {
2✔
740
    OLA_FATAL << "Queued response too small, was " << length << " bytes";
1✔
741
    HandleRDMError(ola::rdm::RDM_INVALID_RESPONSE);
1✔
742
    return;
1✔
743
  }
744

745
  uint16_t pid;
1✔
746
  memcpy(reinterpret_cast<uint8_t*>(&pid), data, sizeof(pid));
1✔
747
  pid = NetworkToHost(pid);
1✔
748

749
  data += 2;
1✔
750
  length -= 2;
1✔
751

752
  OLA_INFO << "Received queued message response with code "
3✔
753
           << ToHex(return_code) << ", " << length << " bytes, param "
1✔
754
           << ToHex(pid);
1✔
755

756
  if (!length)
1✔
757
    data = NULL;
×
758
  HandleGenericRDMResponse(return_code, pid, data, length);
1✔
759
}
760

761

762
/*
763
 * Handle a RDM response for both queued and normal messages.
764
 */
765
void DmxTriWidgetImpl::HandleGenericRDMResponse(uint8_t return_code,
17✔
766
                                                uint16_t pid,
767
                                                const uint8_t *data,
768
                                                unsigned int length) {
769
  auto_ptr<const ola::rdm::RDMRequest> request(m_pending_rdm_request.release());
17✔
770
  ola::rdm::RDMCallback *callback = m_rdm_request_callback;
17✔
771
  m_rdm_request_callback = NULL;
17✔
772

773
  if (callback == NULL || request.get() == NULL) {
17✔
774
    OLA_FATAL << "Got a response but missing callback or request object!";
×
775
    return;
×
776
  }
777

778
  ola::rdm::RDMResponse *response = NULL;
17✔
779
  ola::rdm::RDMStatusCode code = ola::rdm::RDM_COMPLETED_OK;
17✔
780
  ola::rdm::rdm_nack_reason reason;
17✔
781

782
  if (ReturnCodeToNackReason(return_code, &reason)) {
17✔
783
    response = ola::rdm::NackWithReason(request.get(), reason);
2✔
784
    code = ola::rdm::RDM_COMPLETED_OK;
2✔
785
  } else if (return_code == EC_NO_ERROR) {
15✔
786
    if (request->DestinationUID().IsBroadcast()) {
6✔
787
      code = ola::rdm::RDM_WAS_BROADCAST;
3✔
788
    } else {
789
      code = ola::rdm::RDM_COMPLETED_OK;
3✔
790
      response = ola::rdm::GetResponseWithPid(
3✔
791
          request.get(),
792
          pid,
793
          data,
794
          length);
795
    }
796
  } else if (return_code == EC_RESPONSE_TIME) {
9✔
797
    response = ola::rdm::GetResponseWithPid(request.get(),
1✔
798
                                            pid,
799
                                            data,
800
                                            length,
801
                                            ola::rdm::RDM_ACK_TIMER);
802
  } else if (return_code == EC_RESPONSE_WAIT) {
8✔
803
    // this is a hack, there is no way to expose # of queued messages
804
    response = ola::rdm::GetResponseWithPid(request.get(),
1✔
805
                                            pid,
806
                                            data,
807
                                            length,
808
                                            ola::rdm::RDM_ACK,
809
                                            1);
810
  } else if (return_code == EC_RESPONSE_MORE) {
7✔
811
    response = ola::rdm::GetResponseWithPid(request.get(),
1✔
812
                                            pid,
813
                                            data,
814
                                            length,
815
                                            ola::rdm::ACK_OVERFLOW);
816
  } else if (!TriToOlaReturnCode(return_code, &code)) {
6✔
817
    OLA_WARN << "Response was returned with " << ToHex(return_code);
×
818
    code = ola::rdm::RDM_INVALID_RESPONSE;
×
819
  }
820
  // Unfortunately we don't get to see the raw response here, which limits the
821
  // use of the TRI for testing. For testing use the raw mode.
822
  RDMReply reply(code, response);
17✔
823
  callback->Run(&reply);
17✔
824
}
17✔
825

826
/*
827
 * Handle a setfilter response
828
 */
829
void DmxTriWidgetImpl::HandleSetFilterResponse(uint8_t return_code,
3✔
830
                                               const uint8_t*, unsigned int) {
831
  if (!m_pending_rdm_request.get()) {
3✔
832
    OLA_WARN << "Set filter response but no RDM message to send!";
×
833
    return;
×
834
  }
835

836
  if (return_code == EC_NO_ERROR) {
3✔
837
    m_last_esta_id = m_pending_rdm_request->DestinationUID().ManufacturerId();
2✔
838
    DispatchRequest();
2✔
839
  } else {
840
    OLA_WARN << "SetFilter returned " << static_cast<int>(return_code) <<
2✔
841
      ", we have no option but to drop the rdm request";
1✔
842
    HandleRDMError(ola::rdm::RDM_FAILED_TO_SEND);
1✔
843
  }
844
}
845

846

847
/**
848
 * Return true if there is an outstanding transaction pending.
849
 */
850
bool DmxTriWidgetImpl::PendingTransaction() const {
141✔
851
  return m_expected_command != RESERVED_COMMAND_ID;
141✔
852
}
853

854
/**
855
 * Check if there is anything waiting to be sent.
856
 */
857
void DmxTriWidgetImpl::MaybeSendNextRequest() {
72✔
858
  // sending may fail, so we loop until there is nothing to do or there is a
859
  // transaction pending.
860
  bool first = true;
72✔
861
  while (true) {
141✔
862
    if (PendingTransaction()) {
141✔
863
      if (first)
69✔
864
        OLA_DEBUG << "Transaction in progress, delaying send";
2✔
865
      return;
69✔
866
    }
867
    first = false;
72✔
868

869
    if (m_outgoing_dmx.Size() && m_last_command != SINGLE_TX_COMMAND_ID) {
72✔
870
      // avoid starving out DMX frames
871
      SendDMXBuffer();
1✔
872
    } else if (m_pending_rdm_request.get()) {
71✔
873
      // there is an RDM command to send
874
      SendQueuedRDMCommand();
20✔
875
    } else if (m_discovery_state == DISCOVER_AUTO_REQUIRED) {
51✔
876
      SendDiscoveryAuto();
14✔
877
    } else if (m_discovery_state == DISCOVER_STATUS_REQUIRED) {
37✔
878
      SendDiscoveryStat();
13✔
879
    } else if (m_discovery_state == FETCH_UID_REQUIRED) {
24✔
880
      FetchNextUID();
20✔
881
    } else if (m_outgoing_dmx.Size()) {
4✔
882
      // there is a DMX frame to send
883
      SendDMXBuffer();
1✔
884
    } else {
885
      return;
886
    }
887
  }
888
}
889

890

891
/**
892
 * Return an error on the RDM callback and handle the clean up. This assumes
893
 * that m_pending_rdm_request and m_rdm_request_callback are non-null.
894
 */
895
void DmxTriWidgetImpl::HandleRDMError(ola::rdm::RDMStatusCode status_code) {
3✔
896
  ola::rdm::RDMCallback *callback = m_rdm_request_callback;
3✔
897
  m_rdm_request_callback = NULL;
3✔
898
  m_pending_rdm_request.reset();
3✔
899
  if (callback) {
3✔
900
    RunRDMCallback(callback, status_code);
3✔
901
  }
902
}
3✔
903

904

905
/**
906
 * Send data to the TRI, and set the flag indicating a transaction is pending.
907
 */
908
bool DmxTriWidgetImpl::SendCommandToTRI(uint8_t label, const uint8_t *data,
70✔
909
                                        unsigned int length) {
910
  bool r = SendMessage(label, data, length);
70✔
911
  if (r && label == EXTENDED_COMMAND_LABEL && length) {
70✔
912
    OLA_DEBUG << "Sent command " << ToHex(data[0]);
69✔
913
    m_expected_command = data[0];
69✔
914
  }
915
  return r;
70✔
916
}
917

918
/**
919
 * Convert a DMX TRI return code to the appropriate RDMStatusCode
920
 * @return true if this was a matching code, false otherwise
921
 */
922
bool DmxTriWidgetImpl::TriToOlaReturnCode(
6✔
923
    uint8_t return_code,
924
    ola::rdm::RDMStatusCode *code) {
925
  switch (return_code) {
6✔
926
    case EC_RESPONSE_TRANSACTION:
1✔
927
      *code = ola::rdm::RDM_TRANSACTION_MISMATCH;
1✔
928
      break;
1✔
929
    case EC_RESPONSE_SUB_DEVICE:
1✔
930
      *code = ola::rdm::RDM_SUB_DEVICE_MISMATCH;
1✔
931
      break;
1✔
932
    case EC_RESPONSE_FORMAT:
1✔
933
      *code = ola::rdm::RDM_INVALID_RESPONSE;
1✔
934
      break;
1✔
935
    case EC_RESPONSE_CHECKSUM:
1✔
936
      *code = ola::rdm::RDM_CHECKSUM_INCORRECT;
1✔
937
      break;
1✔
938
    case EC_RESPONSE_NONE:
1✔
939
      *code = ola::rdm::RDM_TIMEOUT;
1✔
940
      break;
1✔
941
    case EC_RESPONSE_IDENTITY:
1✔
942
      *code = ola::rdm::RDM_SRC_UID_MISMATCH;
1✔
943
      break;
1✔
944
    default:
945
      return false;
946
  }
947
  return true;
948
}
949

950

951
/**
952
 * Convert a DMX-TRI return code to Nack reason code if appropriate
953
 *
954
 * On the widget, the RDM NACK code is currently bitwise or-ed with 0x20 to
955
 * generate the error code
956
 */
957
bool DmxTriWidgetImpl::ReturnCodeToNackReason(
17✔
958
    uint8_t return_code,
959
    ola::rdm::rdm_nack_reason *reason) {
960
  switch (return_code) {
17✔
961
    case EC_UNKNOWN_PID:
1✔
962
      *reason = ola::rdm::NR_UNKNOWN_PID;
1✔
963
      break;
1✔
964
    case EC_FORMAT_ERROR:
×
965
      *reason = ola::rdm::NR_FORMAT_ERROR;
×
966
      break;
×
967
    case EC_HARDWARE_FAULT:
×
968
      *reason = ola::rdm::NR_HARDWARE_FAULT;
×
969
      break;
×
970
    case EC_PROXY_REJECT:
×
971
      *reason = ola::rdm::NR_PROXY_REJECT;
×
972
      break;
×
973
    case EC_WRITE_PROTECT:
×
974
      *reason = ola::rdm::NR_WRITE_PROTECT;
×
975
      break;
×
976
    case EC_UNSUPPORTED_COMMAND_CLASS:
×
977
      *reason = ola::rdm::NR_UNSUPPORTED_COMMAND_CLASS;
×
978
      break;
×
979
    case EC_OUT_OF_RANGE:
×
980
      *reason = ola::rdm::NR_DATA_OUT_OF_RANGE;
×
981
      break;
×
982
    case EC_BUFFER_FULL:
×
983
      *reason = ola::rdm::NR_BUFFER_FULL;
×
984
      break;
×
985
    case EC_FRAME_OVERFLOW:
×
986
      *reason = ola::rdm::NR_PACKET_SIZE_UNSUPPORTED;
×
987
      break;
×
988
    case EC_SUBDEVICE_UNKNOWN:
×
989
      *reason = ola::rdm::NR_SUB_DEVICE_OUT_OF_RANGE;
×
990
      break;
×
991
    case EC_PROXY_BUFFER_FULL:
1✔
992
      *reason = ola::rdm::NR_PROXY_BUFFER_FULL;
1✔
993
      break;
1✔
994
    case EC_ACTION_NOT_SUPPORTED:
×
995
      *reason = ola::rdm::NR_ACTION_NOT_SUPPORTED;
×
996
      break;
×
997
    case EC_ENDPOINT_NUMBER_INVALID:
×
998
      *reason = ola::rdm::NR_ENDPOINT_NUMBER_INVALID;
×
999
      break;
×
1000
    case EC_INVALID_ENDPOINT_MODE:
×
1001
      *reason = ola::rdm::NR_INVALID_ENDPOINT_MODE;
×
1002
      break;
×
1003
    case EC_UNKNOWN_UID:
×
1004
      *reason = ola::rdm::NR_UNKNOWN_UID;
×
1005
      break;
×
1006
    case EC_UNKNOWN_SCOPE:
×
1007
      *reason = ola::rdm::NR_UNKNOWN_SCOPE;
×
1008
      break;
×
1009
    case EC_INVALID_STATIC_CONFIG_TYPE:
×
1010
      *reason = ola::rdm::NR_INVALID_STATIC_CONFIG_TYPE;
×
1011
      break;
×
1012
    case EC_INVALID_IPV4_ADDRESS:
×
1013
      *reason = ola::rdm::NR_INVALID_IPV4_ADDRESS;
×
1014
      break;
×
1015
    case EC_INVALID_IPV6_ADDRESS:
×
1016
      *reason = ola::rdm::NR_INVALID_IPV6_ADDRESS;
×
1017
      break;
×
1018
    case EC_INVALID_PORT:
×
1019
      *reason = ola::rdm::NR_INVALID_PORT;
×
1020
      break;
×
1021
    case EC_DEVICE_ABSENT:
×
1022
      *reason = ola::rdm::NR_DEVICE_ABSENT;
×
1023
      break;
×
1024
    case EC_SENSOR_OUT_OF_RANGE:
×
1025
      *reason = ola::rdm::NR_SENSOR_OUT_OF_RANGE;
×
1026
      break;
×
1027
    case EC_SENSOR_FAULT:
×
1028
      *reason = ola::rdm::NR_SENSOR_FAULT;
×
1029
      break;
×
1030
    case EC_PACKING_NOT_SUPPORTED:
×
1031
      *reason = ola::rdm::NR_PACKING_NOT_SUPPORTED;
×
1032
      break;
×
1033
    case EC_ERROR_IN_PACKED_LIST_TRANSACTION:
×
1034
      *reason = ola::rdm::NR_ERROR_IN_PACKED_LIST_TRANSACTION;
×
1035
      break;
×
1036
    case EC_PROXY_DROP:
×
1037
      *reason = ola::rdm::NR_PROXY_DROP;
×
1038
      break;
×
1039
    case EC_ALL_CALL_SET_FAIL:
×
1040
      *reason = ola::rdm::NR_ALL_CALL_SET_FAIL;
×
1041
      break;
×
1042
    default:
1043
      return false;
1044
  }
1045
  return true;
1046
}
1047

1048

1049
/**
1050
 * DmxTriWidget Constructor
1051
 */
1052
DmxTriWidget::DmxTriWidget(ola::thread::SchedulerInterface *scheduler,
13✔
1053
                           ola::io::ConnectedDescriptor *descriptor,
1054
                           unsigned int queue_size,
1055
                           bool use_raw_rdm) {
13✔
1056
  m_impl = new DmxTriWidgetImpl(scheduler, descriptor, use_raw_rdm);
13✔
1057
  m_controller = new ola::rdm::DiscoverableQueueingRDMController(m_impl,
39✔
1058
                                                                 queue_size);
13✔
1059
}
13✔
1060

1061

1062
DmxTriWidget::~DmxTriWidget() {
13✔
1063
  // delete the controller after the impl because the controller owns the
1064
  // callback
1065
  delete m_impl;
13✔
1066
  delete m_controller;
13✔
1067
}
13✔
1068
}  // namespace usbpro
1069
}  // namespace plugin
1070
}  // namespace ola
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc