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

OpenLightingProject / ola / 16900099157

12 Aug 2025 05:43AM UTC coverage: 45.72% (-0.02%) from 45.742%
16900099157

Pull #2016

github

web-flow
Merge c368ef6ae into eaf937e80
Pull Request #2016: Bump actions/checkout from 4 to 5

7586 of 17462 branches covered (43.44%)

22424 of 49046 relevant lines covered (45.72%)

51.77 hits per line

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

86.36
/common/rdm/DiscoveryAgent.cpp
1
/*
2
 * This library is free software; you can redistribute it and/or
3
 * modify it under the terms of the GNU Lesser General Public
4
 * License as published by the Free Software Foundation; either
5
 * version 2.1 of the License, or (at your option) any later version.
6
 *
7
 * This library 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 GNU
10
 * Lesser General Public License for more details.
11
 *
12
 * You should have received a copy of the GNU Lesser General Public
13
 * License along with this library; if not, write to the Free Software
14
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
 *
16
 * DiscoveryAgent.cpp
17
 * Implements the RDM Discovery algorithm.
18
 * Copyright (C) 2011 Simon Newton
19
 */
20

21
#include "ola/Callback.h"
22
#include "ola/Logging.h"
23
#include "ola/rdm/DiscoveryAgent.h"
24
#include "ola/rdm/UID.h"
25
#include "ola/rdm/UIDSet.h"
26
#include "ola/strings/Format.h"
27
#include "ola/util/Utils.h"
28

29
namespace ola {
30
namespace rdm {
31

32
using ola::utils::JoinUInt8;
33

34
DiscoveryAgent::DiscoveryAgent(DiscoveryTargetInterface *target)
34✔
35
    : m_target(target),
34✔
36
      m_on_complete(NULL),
34✔
37
      m_unmute_callback(
34✔
38
          ola::NewCallback(this, &DiscoveryAgent::UnMuteComplete)),
34✔
39
      m_incremental_mute_callback(
34✔
40
        ola::NewCallback(this, &DiscoveryAgent::IncrementalMuteComplete)),
34✔
41
      m_branch_mute_callback(
34✔
42
        ola::NewCallback(this, &DiscoveryAgent::BranchMuteComplete)),
34✔
43
      m_branch_callback(
34✔
44
        ola::NewCallback(this, &DiscoveryAgent::BranchComplete)),
34✔
45
      m_muting_uid(0, 0),
34✔
46
      m_unmute_count(0),
34✔
47
      m_mute_attempts(0),
34✔
48
      m_tree_corrupt(false) {
68✔
49
}
34✔
50

51
DiscoveryAgent::~DiscoveryAgent() {
34✔
52
  Abort();
34✔
53
}
34✔
54

55
void DiscoveryAgent::Abort() {
48✔
56
  while (!m_uid_ranges.empty()) {
48✔
57
    UIDRange *range = m_uid_ranges.top();
×
58
    delete range;
×
59
    m_uid_ranges.pop();
×
60
  }
61

62
  if (m_on_complete) {
48✔
63
    DiscoveryCompleteCallback *callback = m_on_complete;
×
64
    m_on_complete = NULL;
×
65
    UIDSet uids;
×
66
    callback->Run(false, uids);
×
67
  }
×
68
}
48✔
69

70
void DiscoveryAgent::StartFullDiscovery(
11✔
71
    DiscoveryCompleteCallback *on_complete) {
72
  InitDiscovery(on_complete, false);
11✔
73
}
11✔
74

75
void DiscoveryAgent::StartIncrementalDiscovery(
7✔
76
    DiscoveryCompleteCallback *on_complete) {
77
  InitDiscovery(on_complete, true);
7✔
78
}
7✔
79

80
/*
81
 * Start the discovery process
82
 * @param on_complete the callback to run when discovery completes
83
 * @param incremental true if this is incremental, false otherwise
84
 */
85
void DiscoveryAgent::InitDiscovery(
18✔
86
    DiscoveryCompleteCallback *on_complete,
87
    bool incremental) {
88
  if (m_on_complete) {
18✔
89
    OLA_WARN << "Discovery procedure already running";
×
90
    UIDSet uids;
×
91
    on_complete->Run(false, uids);
×
92
    return;
×
93
  }
×
94
  m_on_complete = on_complete;
18✔
95

96
  // this should be empty, but clear it out anyway
97
  while (!m_uids_to_mute.empty()) {
18✔
98
    m_uids_to_mute.pop();
×
99
  }
100

101
  // this should also be empty
102
  while (!m_uid_ranges.empty()) {
18✔
103
    FreeCurrentRange();
×
104
  }
105

106
  if (incremental) {
18✔
107
    UIDSet::Iterator iter = m_uids.Begin();
7✔
108
    for (; iter != m_uids.End(); ++iter) {
19✔
109
      m_uids_to_mute.push(*iter);
12✔
110
    }
111
  } else {
112
    m_uids.Clear();
11✔
113
  }
114

115
  m_bad_uids.Clear();
18✔
116
  m_split_uids.Clear();
18✔
117
  m_tree_corrupt = false;
18✔
118

119
  // push the first range on to the branch stack
120
  UID lower(0, 0);
18✔
121
  m_uid_ranges.push(new UIDRange(lower, UID::AllDevices(), NULL));
18✔
122

123
  m_unmute_count = 0;
18✔
124
  m_target->UnMuteAll(m_unmute_callback.get());
18✔
125
}
126

127
/*
128
 * Called when the UnMute completes. This resends the Unmute command up to
129
 * BROADCAST_UNMUTE_REPEATS times and then starts muting previously known
130
 * devices (incremental only).
131
 */
132
void DiscoveryAgent::UnMuteComplete() {
54✔
133
  if (m_uid_ranges.empty()) {
54✔
134
    // Abort() was called
135
    return;
136
  }
137

138
  m_unmute_count++;
54✔
139
  if (m_unmute_count < BROADCAST_UNMUTE_REPEATS) {
54✔
140
    m_target->UnMuteAll(m_unmute_callback.get());
36✔
141
    return;
36✔
142
  }
143
  MaybeMuteNextDevice();
18✔
144
}
145

146
/*
147
 * If we're in incremental mode, mute previously discovered devices. Otherwise
148
 * proceed to the branch stage.
149
 */
150
void DiscoveryAgent::MaybeMuteNextDevice() {
30✔
151
  if (m_uids_to_mute.empty()) {
30✔
152
    SendDiscovery();
18✔
153
  } else {
154
    m_muting_uid = m_uids_to_mute.front();
12✔
155
    m_uids_to_mute.pop();
12✔
156
    OLA_DEBUG << "Muting previously discovered responder: " << m_muting_uid;
12✔
157
    m_target->MuteDevice(m_muting_uid, m_incremental_mute_callback.get());
12✔
158
  }
159
}
30✔
160

161
/**
162
 * Called when we mute a device during incremental discovery.
163
 */
164
void DiscoveryAgent::IncrementalMuteComplete(bool status) {
12✔
165
  if (!status) {
12✔
166
    m_uids.RemoveUID(m_muting_uid);
3✔
167
    OLA_WARN << "Unable to mute " << m_muting_uid << ", device has gone";
3✔
168
  } else {
169
    OLA_DEBUG << "Muted " << m_muting_uid;
9✔
170
  }
171
  MaybeMuteNextDevice();
12✔
172
}
12✔
173

174
/*
175
 * Send a Discovery Unique Branch request.
176
 */
177
void DiscoveryAgent::SendDiscovery() {
1,110✔
178
  if (m_uid_ranges.empty()) {
1,353✔
179
    // we're hit the end of the stack, now we're done
180
    if (m_on_complete) {
18✔
181
      m_on_complete->Run(!m_tree_corrupt, m_uids);
18✔
182
      m_on_complete = NULL;
18✔
183
    } else {
184
      OLA_WARN << "Discovery complete but no callback";
×
185
    }
186
    return;
18✔
187
  }
188
  UIDRange *range = m_uid_ranges.top();
1,335✔
189
  if (range->uids_discovered == 0) {
1,335✔
190
    range->attempt++;
1,079✔
191
  }
192

193
  if (range->failures == MAX_BRANCH_FAILURES ||
1,335✔
194
      range->attempt == MAX_EMPTY_BRANCH_ATTEMPTS ||
1,335✔
195
      range->branch_corrupt) {
1,326✔
196
    // limit reached, move on to the next branch
197
    OLA_DEBUG << "Hit failure limit for (" << range->lower << ", "
243✔
198
              << range->upper << ")";
×
199
    if (range->parent) {
243✔
200
      range->parent->branch_corrupt = true;
237✔
201
    }
202
    FreeCurrentRange();
243✔
203
    SendDiscovery();
243✔
204
  } else {
205
    OLA_DEBUG << "DUB " << range->lower << " - " << range->upper
1,092✔
206
              << ", attempt " << range->attempt << ", uids found: "
×
207
              << range->uids_discovered << ", failures " << range->failures
×
208
              << ", corrupted " << range->branch_corrupt;
×
209
    m_target->Branch(range->lower, range->upper, m_branch_callback.get());
1,092✔
210
  }
211
}
212

213
/*
214
 * Handle a DUB response (inc. timeouts).
215
 * @param data the raw response, excluding the start code
216
 * @param length the length of the response, 0 if no response was received.
217
 */
218
void DiscoveryAgent::BranchComplete(const uint8_t *data, unsigned int length) {
1,092✔
219
  OLA_INFO << "BranchComplete, got " << length;
1,092✔
220
  if (length == 0) {
1,092✔
221
    // timeout
222
    if (!m_uid_ranges.empty()) {
617✔
223
      FreeCurrentRange();
617✔
224
    }
225
    SendDiscovery();
617✔
226
    return;
1,587✔
227
  }
228

229
  // Must at least have the separator, the EUID and the checksum
230
  if (length < 1 + EUID_SIZE + CHECKSUM_SIZE) {
475✔
231
    HandleCollision();
×
232
    return;
×
233
  }
234

235
  unsigned int offset = 0;
236
  while (data[offset] != PREAMBLE_SEPARATOR && offset < PREAMBLE_SIZE - 1) {
3,800✔
237
    if (data[offset] != PREAMBLE) {
3,325✔
238
      OLA_INFO << "Preamble " << offset << " " << strings::ToHex(data[offset]);
×
239
      HandleCollision();
×
240
      return;
×
241
    }
242
    offset++;
3,325✔
243
  }
244

245
  if (data[offset] != PREAMBLE_SEPARATOR) {
475✔
246
    OLA_INFO << "Preamble separator" << offset << " "
×
247
             << strings::ToHex(data[offset]);
×
248
    HandleCollision();
×
249
    return;
×
250
  }
251

252
  offset++;
475✔
253
  unsigned int remaining = length - offset;
475✔
254
  if (remaining < EUID_SIZE + CHECKSUM_SIZE) {
475✔
255
    OLA_INFO << "Insufficient data remaining, was " << remaining;
38✔
256
    HandleCollision();
38✔
257
    return;
38✔
258
  }
259

260
  typedef struct {
437✔
261
    uint8_t euid11;
262
    uint8_t euid10;
263
    uint8_t euid9;
264
    uint8_t euid8;
265
    uint8_t euid7;
266
    uint8_t euid6;
267
    uint8_t euid5;
268
    uint8_t euid4;
269
    uint8_t euid3;
270
    uint8_t euid2;
271
    uint8_t euid1;
272
    uint8_t euid0;
273
    uint8_t ecs3;
274
    uint8_t ecs2;
275
    uint8_t ecs1;
276
    uint8_t ecs0;
277
  } dub_response_structure;
278

279
  const dub_response_structure *response =
437✔
280
      reinterpret_cast<const dub_response_structure*>(data + offset);
437✔
281

282
  uint16_t calculated_checksum = 0;
437✔
283
  for (unsigned int i = offset; i < offset + EUID_SIZE; i++) {
5,681✔
284
    calculated_checksum += data[i];
5,244✔
285
  }
286

287
  uint16_t recovered_checksum = JoinUInt8((response->ecs3 & response->ecs2),
437✔
288
                                          (response->ecs1 & response->ecs0));
437✔
289

290
  if (recovered_checksum != calculated_checksum) {
437✔
291
    OLA_INFO << "Recovered checksum: " << recovered_checksum << " != "
630✔
292
             << "calculated checksum: " << calculated_checksum;
315✔
293
    HandleCollision();
315✔
294
    return;
315✔
295
  }
296

297
  // ok this is a valid response
298
  uint16_t manufacturer_id = JoinUInt8((response->euid11 & response->euid10),
122✔
299
                                       (response->euid9 & response->euid8));
122✔
300
  uint32_t device_id = JoinUInt8((response->euid7 & response->euid6),
122✔
301
                                 (response->euid5 & response->euid4),
122✔
302
                                 (response->euid3 & response->euid2),
122✔
303
                                 (response->euid1 & response->euid0));
122✔
304

305
  UIDRange *range = m_uid_ranges.top();
122✔
306

307
  // we store this as an instance variable so we don't have to create a new
308
  // callback each time.
309
  UID located_uid = UID(manufacturer_id, device_id);
122✔
310
  if (m_uids.Contains(located_uid)) {
122✔
311
    OLA_WARN << "Previously muted responder " << located_uid
136✔
312
             << " continues to respond";
68✔
313
    range->failures++;
68✔
314
    if (!m_split_uids.Contains(located_uid)) {
68✔
315
      m_split_uids.AddUID(located_uid);
4✔
316
      // ignore this and continue either side of it.
317
      SplitAroundBadUID(located_uid);
4✔
318
    } else {
319
      // Treat this as a collision and continue branching down
320
      HandleCollision();
64✔
321
    }
322
  } else if (m_bad_uids.Contains(located_uid)) {
54✔
323
    // we've already tried this one
324
    OLA_INFO << "Previously bad responder " << located_uid
40✔
325
             << " continues to respond";
20✔
326
    range->failures++;
20✔
327
    if (!m_split_uids.Contains(located_uid)) {
20✔
328
      // ignore this and continue either side of it.
329
      SplitAroundBadUID(located_uid);
20✔
330
    } else {
331
      // Continue branching
332
      HandleCollision();
×
333
    }
334
  } else {
335
    m_muting_uid = located_uid;
34✔
336
    m_mute_attempts = 0;
34✔
337
    OLA_INFO << "Muting " << m_muting_uid;
34✔
338
    m_target->MuteDevice(m_muting_uid, m_branch_mute_callback.get());
34✔
339
  }
340
}
341

342
/*
343
 * Called when we successful mute a device during the branch stage.
344
 */
345
void DiscoveryAgent::BranchMuteComplete(bool status) {
52✔
346
  m_mute_attempts++;
52✔
347
  if (status) {
52✔
348
    m_uids.AddUID(m_muting_uid);
31✔
349
    m_uid_ranges.top()->uids_discovered++;
31✔
350
  } else {
351
    // failed to mute, if we haven't reached the limit try it again
352
    if (m_mute_attempts < MAX_MUTE_ATTEMPTS) {
21✔
353
      OLA_INFO << "Muting " << m_muting_uid;
18✔
354
      m_target->MuteDevice(m_muting_uid, m_branch_mute_callback.get());
18✔
355
      return;
18✔
356
    } else {
357
      // this UID is bad, either it was a phantom or it doesn't respond to
358
      // mute commands
359
      OLA_INFO << m_muting_uid << " didn't respond to MUTE, marking as bad";
3✔
360
      m_bad_uids.AddUID(m_muting_uid);
3✔
361
    }
362
  }
363
  SendDiscovery();
34✔
364
}
365

366
/*
367
 * Handle a DUB collision.
368
 */
369
void DiscoveryAgent::HandleCollision() {
430✔
370
  UIDRange *range = m_uid_ranges.top();
430✔
371
  UID lower_uid = range->lower;
430✔
372
  UID upper_uid = range->upper;
430✔
373

374
  if (lower_uid == upper_uid) {
430✔
375
    range->failures++;
20✔
376
    OLA_WARN << "End of tree reached!!!";
20✔
377
    SendDiscovery();
20✔
378
    return;
20✔
379
  }
380

381
  // work out the mid point
382
  uint64_t mid = (lower_uid.ToUInt64() + upper_uid.ToUInt64()) / 2;
410✔
383
  UID mid_uid(mid);
410✔
384
  mid++;
410✔
385
  UID mid_plus_one_uid(mid);
410✔
386
  OLA_INFO << "Collision, splitting into: " << lower_uid << " - " << mid_uid
820✔
387
           << " , " << mid_plus_one_uid << " - " << upper_uid;
410✔
388

389
  range->uids_discovered = 0;
410✔
390
  // add both ranges to the stack
391
  m_uid_ranges.push(new UIDRange(lower_uid, mid_uid, range));
410✔
392
  m_uid_ranges.push(new UIDRange(mid_plus_one_uid, upper_uid, range));
410✔
393
  SendDiscovery();
410✔
394
}
395

396
/*
397
 * Split around a bad UID.
398
 * This is a more specialised version of HandleCollision
399
 */
400
void DiscoveryAgent::SplitAroundBadUID(UID bad_uid) {
24✔
401
  UIDRange *range = m_uid_ranges.top();
24✔
402
  UID lower_uid = range->lower;
24✔
403
  UID upper_uid = range->upper;
24✔
404

405
  if (lower_uid == upper_uid) {
24✔
406
    range->failures++;
×
407
    OLA_WARN << "End of tree reached!!!";
×
408
    SendDiscovery();
×
409
    return;
13✔
410
  } else if (bad_uid < lower_uid || bad_uid > upper_uid) {
24✔
411
    OLA_INFO << "Bad UID " << bad_uid << " not within range " << lower_uid
26✔
412
             << " - " << upper_uid << ", assuming it's a phantom!";
13✔
413
    HandleCollision();
13✔
414
    return;
13✔
415
  }
416

417
  OLA_INFO << "Bad UID, attempting split either side of: " << bad_uid;
11✔
418
  UID mid_minus_one_uid(bad_uid.ToUInt64() - 1);
11✔
419
  UID mid_plus_one_uid(bad_uid.ToUInt64() + 1);
11✔
420

421
  range->uids_discovered = 0;
11✔
422
  if (mid_minus_one_uid >= lower_uid) {
11✔
423
    OLA_INFO << "Splitting either side of " << bad_uid << ", adding "
22✔
424
             << lower_uid << " - " << mid_minus_one_uid;
11✔
425
    m_uid_ranges.push(new UIDRange(lower_uid, mid_minus_one_uid, range));
11✔
426
  }
427
  if (mid_plus_one_uid <= upper_uid) {
11✔
428
    OLA_INFO << "Splitting either side of " << bad_uid << ", adding "
22✔
429
             << mid_plus_one_uid << " - " << upper_uid;
11✔
430
    m_uid_ranges.push(new UIDRange(mid_plus_one_uid, upper_uid, range));
11✔
431
  }
432
  SendDiscovery();
11✔
433
}
434

435
/*
436
 * Deletes the current range from the stack, and pops it.
437
 */
438
void DiscoveryAgent::FreeCurrentRange() {
860✔
439
  UIDRange *range = m_uid_ranges.top();
860✔
440
  if (m_uid_ranges.size() == 1) {
860✔
441
    // top of stack
442
    if (range->branch_corrupt) {
18✔
443
      OLA_INFO << "Discovery tree is corrupted";
6✔
444
      m_tree_corrupt = true;
6✔
445
    }
446
  } else {
447
    range->parent->uids_discovered += range->uids_discovered;
842✔
448
  }
449
  delete range;
860✔
450
  m_uid_ranges.pop();
860✔
451
}
860✔
452
}  // namespace rdm
453
}  // 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

© 2025 Coveralls, Inc