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

OpenLightingProject / ola / 13823257993

12 Mar 2025 11:07PM UTC coverage: 45.572% (-0.2%) from 45.742%
13823257993

Pull #1718

github

web-flow
Merge 363e59409 into e8c755cdb
Pull Request #1718: Sigrok based RDM sniffer

7823 of 18064 branches covered (43.31%)

0 of 57 new or added lines in 2 files covered. (0.0%)

22403 of 49160 relevant lines covered (45.57%)

54.24 hits per line

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

0.0
/libs/sniffer/DMXSignalProcessor.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
 * DMXSignalProcessor.cpp
17
 * Process a stream of bits and decode into DMX frames.
18
 * Copyright (C) 2013 Simon Newton
19
 *
20
 * See E1.11 for the details including timing. It generally goes something
21
 * like:
22
 *  Mark (Idle) - High
23
 *  Break - Low
24
 *  Mark After Break - High
25
 *  Start bit (low)
26
 *  LSB to MSB (8)
27
 *  2 stop bits (high)
28
 *  Mark between slots (high)
29
 *
30
 * There are a number of interesting cases which we need to handle:
31
 *
32
 * Variable bit length
33
 *
34
 * Start bit vs Break.
35
 *  After the stop bits comes an optional mark time between slots, that can
36
 *  range up to 1s. When the next falling edge occurs, it could either be a
37
 *  break (indicating the previous frame is now complete) or a start bit. If a
38
 *  rising edge occurs before 35.28 (9 * 3.92) us then it was a start-bit. If
39
 *  36.72 (9 * 4.08) useconds passes and there was no rising edge it's a break.
40
 *
41
 * The implementation is based on a state machine, with a couple of tweaks.
42
 */
43

44
#include <ola/Logging.h>
45
#include <vector>
46

47
#include "libs/sniffer/DMXSignalProcessor.h"
48

49
using std::vector;
50

51
const double DMXSignalProcessor::MIN_BREAK_TIME = 88.0;
52
const double DMXSignalProcessor::MIN_MAB_TIME = 8.0;
53
const double DMXSignalProcessor::MAX_MAB_TIME = 1000000.0;
54
const double DMXSignalProcessor::MIN_BIT_TIME = 3.75;
55
const double DMXSignalProcessor::MAX_BIT_TIME = 4.08;
56
const double DMXSignalProcessor::MIN_LAST_BIT_TIME = 2.64;
57
const double DMXSignalProcessor::MAX_MARK_BETWEEN_SLOTS = 1000000.0;
58

59
/**
60
 * Create a new DMXSignalProcessor which runs the specified callback when a new
61
 * frame is received.
62
 */
63
DMXSignalProcessor::DMXSignalProcessor(DataCallback *callback,
×
64
                                       unsigned int sample_rate)
×
65
    : m_callback(callback),
×
66
      m_sample_rate(sample_rate),
×
67
      m_microseconds_per_tick(1000000.0 / sample_rate),
×
68
      m_state(IDLE),
×
69
      m_ticks(0),
×
70
      m_may_be_in_break(false),
×
71
      m_ticks_in_break(0) {
×
NEW
72
  OLA_DEBUG << "Microseconds per tick " << m_microseconds_per_tick;
×
73
  if (m_sample_rate % DMX_BITRATE) {
×
74
    OLA_WARN << "Sample rate is not a multiple of " << DMX_BITRATE;
×
75
  }
76
}
×
77

78
/*
79
 * Process the data stream. We pass in a uint8_t array rather than a bool
80
 * array, since it's the same size anyway. The mask is used to indicate how to
81
 * interpret the data.
82
 * @param ptr the data stream to process
83
 * @param size the number of samples in the stream
84
 * @param mask the value to be AND'ed with each sample to determine if the
85
 *   signal is high or low.
86
 * @param width the width (in bytes) of each sample within the stream
87
 */
88
void DMXSignalProcessor::Process(uint8_t *ptr, unsigned int size,
×
89
                                 uint8_t mask, unsigned int width) {
NEW
90
  for (unsigned int i = 0 ; i < size; i += width) {
×
91
    ProcessSample(ptr[i] & mask);
×
92
  }
93
}
×
94

95
/**
96
 * Process one bit of data through the state machine.
97
 */
98
void DMXSignalProcessor::ProcessSample(bool bit) {
×
99
  if (m_may_be_in_break && !bit) {
×
100
    // if we may be in a break, keep track of the time since the falling edge.
101
    m_ticks_in_break++;
×
102
  }
103

104
  switch (m_state) {
×
105
    case UNDEFINED:
×
106
      if (bit) {
×
107
        SetState(IDLE);
×
108
      }
109
      break;
110
    case IDLE:
×
111
      if (bit) {
×
112
        m_ticks++;
×
113
      } else {
114
        SetState(BREAK);
×
115
      }
116
      break;
117
    case BREAK:
×
118
      if (bit) {
×
119
        if (DurationExceeds(MIN_BREAK_TIME)) {
×
120
          SetState(MAB);
×
121
        } else {
122
          OLA_WARN << "Break too short, was " << TicksAsMicroSeconds()
×
NEW
123
                   << " us (" << m_ticks << " ticks)";
×
124
          SetState(IDLE);
×
125
        }
126
      } else {
127
        m_ticks++;
×
128
      }
129
      break;
130
    case MAB:
×
131
      if (bit) {
×
132
        m_ticks++;
×
133
        if (DurationExceeds(MAX_MAB_TIME)) {
×
134
          SetState(IDLE, m_ticks);
×
135
        }
136
      } else {
137
        if (DurationExceeds(MIN_MAB_TIME)) {
×
138
          // OLA_INFO << "In start bit!";
139
          SetState(START_BIT);
×
140
        } else {
NEW
141
          OLA_WARN << "Mark too short, was " << TicksAsMicroSeconds() << "us ("
×
NEW
142
                   << m_ticks << " ticks)";
×
143
          SetState(UNDEFINED);
×
144
        }
145
      }
146
      break;
147
    case START_BIT:
×
148
    case BIT_1:
×
149
    case BIT_2:
×
150
    case BIT_3:
×
151
    case BIT_4:
×
152
    case BIT_5:
×
153
    case BIT_6:
×
154
    case BIT_7:
×
155
    case BIT_8:
×
156
      ProcessBit(bit);
×
157
      break;
×
158
    case STOP_BITS:
×
159
      m_ticks++;
×
160
      if (bit) {
×
161
        if (DurationExceeds(2 * MIN_BIT_TIME)) {
×
162
          AppendDataByte();
×
163
          SetState(MARK_BETWEEN_SLOTS);
×
164
        }
165
      } else {
166
        if (m_may_be_in_break) {
×
167
          HandleFrame();
×
168
          SetState(BREAK, m_ticks_in_break);
×
169
        } else {
170
          OLA_WARN << "Saw a low during a stop bit";
×
171
          SetState(UNDEFINED);
×
172
        }
173
      }
174
      break;
175
    case MARK_BETWEEN_SLOTS:
×
176
      // Wait for the falling edge, this could signal the next start bit, or a
177
      // new break.
178
      m_ticks++;
×
179
      if (bit) {
×
180
        if (DurationExceeds(MAX_MARK_BETWEEN_SLOTS)) {
×
181
          // ok, that was the end of the frame.
182
          HandleFrame();
×
183
          SetState(IDLE);
×
184
        }
185
      } else {
186
        m_may_be_in_break = true;
×
187
        // Assume it's a start bit for now, but flag that we may be in a break.
188
        SetState(START_BIT);
×
189
      }
190
      break;
191
    default:
192
      break;
193
  }
194
}
×
195

196
/**
197
 * Process a sample that makes up a bit of data.
198
 */
199
void DMXSignalProcessor::ProcessBit(bool bit) {
×
200
  if (bit) {
×
201
    // a high at this stage means this definitely isn't a break.
202
    m_may_be_in_break = false;
×
203
  }
204

205
  bool current_bit = SetBitIfNotDefined(bit);
×
206

207
  /*
208
  OLA_INFO << "ticks: " << m_ticks << ", current bit " << current_bit
209
           << ", our bit " << bit;
210
  */
211
  m_ticks++;
×
212
  if (bit == current_bit) {
×
213
    if (DurationExceeds(MAX_BIT_TIME)) {
×
214
      SetState(static_cast<State>(m_state + 1));
×
215
    }
216
  } else {
217
    // Because we force a transition into the next state (bit) after
218
    // MAX_BIT_TIME. The last bit may appear to be too short. This math is as
219
    // follows:
220
    //  min time for 9 bits = 9 * 3.92 = 35.28
221
    //  max time for 8 bits = 8 * 4.08 = 32.64
222
    //  difference = 2.64
223
    if ((m_state == BIT_8 && DurationExceeds(MIN_LAST_BIT_TIME)) ||
×
224
        DurationExceeds(MIN_BIT_TIME)) {
×
225
      SetState(static_cast<State>(m_state + 1));
×
226
    } else {
227
      OLA_WARN << "Bit " << m_state << " was too short, was "
×
NEW
228
               << TicksAsMicroSeconds() << "us (" << m_ticks << " ticks)";
×
229
      SetState(UNDEFINED);
×
230
    }
231
  }
232
}
×
233

234
/**
235
 * This is where we accumulate the bit values, before packing them into a byte.
236
 * This method does a couple of things:
237
 *  If there is no known value for the bit, it sets one.
238
 *  Return the value of the bit.
239
 */
240
bool DMXSignalProcessor::SetBitIfNotDefined(bool bit) {
×
241
  if (m_state == START_BIT) {
×
242
    return false;
243
  }
244

245
  int offset = m_state - BIT_1;
×
246
  if (!m_bits_defined[offset]) {
×
247
    // OLA_INFO << "Set bit " << offset << " to " << bit;
248
    m_current_byte.push_back(bit);
×
249
    m_bits_defined[offset] = true;
×
250
  }
251
  return m_current_byte[offset];
×
252
}
253

254
/**
255
 * Pack the 8 bit values into a byte, and append it to the vector of bytes.
256
 */
257
void DMXSignalProcessor::AppendDataByte() {
×
258
  uint8_t byte = 0;
×
259
  for (unsigned int i = 0; i < 8; i++) {
×
260
    // LSB first
261
    byte |= (m_current_byte[i] << i);
×
262
  }
263
  OLA_INFO << "Byte " << m_dmx_data.size() << " is "
×
264
           << static_cast<int>(byte) << " ( 0x" << std::hex
×
265
           << static_cast<int>(byte) << " )";
×
266
  m_dmx_data.push_back(byte);
×
267
  m_bits_defined.assign(8, false);
×
268
  m_current_byte.clear();
×
269
}
×
270

271
/**
272
 * Called when we know the previous frame is complete. This invokes the
273
 * callback if there is one, and resets the vector.
274
 */
275
void DMXSignalProcessor::HandleFrame() {
×
276
  // OLA_INFO << "--------------- END OF FRAME ------------------";
277
  OLA_INFO << "Got frame of size " << m_dmx_data.size();
×
278
  if (m_callback && !m_dmx_data.empty()) {
×
279
    m_callback->Run(&m_dmx_data[0], m_dmx_data.size());
×
280
  }
281
  m_dmx_data.clear();
×
282
}
×
283

284
/**
285
 * Used to transition between states
286
 */
287
void DMXSignalProcessor::SetState(State state, unsigned int ticks) {
×
288
  OLA_INFO << "Transition to " << state << ", prev duration was "
×
NEW
289
           << TicksAsMicroSeconds() << " us (" << m_ticks << " ticks)";
×
290
  m_state = state;
×
291
  m_ticks = ticks;
×
292
  if (state == UNDEFINED) {
×
293
    // if we have a partial frame, we should send that up the stack
294
    HandleFrame();
×
295
  } else if (state == MAB) {
×
296
    m_dmx_data.clear();
×
297
  } else if (state == START_BIT) {
×
298
    // The reset should be done in AppendDataByte but do it again to be safe.
299
    m_bits_defined.assign(8, false);
×
300
    m_current_byte.clear();
×
301
  }
302
}
×
303

304
/*
305
 * Return true if the current number of ticks exceeds micro_seconds.
306
 * Due to sampling this can be wrong by +- m_microseconds_per_tick.
307
 */
308
bool DMXSignalProcessor::DurationExceeds(double micro_seconds) {
×
309
  return m_ticks * m_microseconds_per_tick >= micro_seconds;
×
310
}
311

312
/*
313
 * Return the current number of ticks in microseconds.
314
 */
315
double DMXSignalProcessor::TicksAsMicroSeconds() {
×
316
  return m_ticks * m_microseconds_per_tick;
×
317
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc