• 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

0.0
/plugins/ftdidmx/FtdiWidget.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
 * FtdiWidget.cpp
17
 * This class is based on QLCFTDI class from
18
 *
19
 * Q Light Controller
20
 * qlcftdi-libftdi.cpp
21
 *
22
 * Copyright (C) Heikki Junnila
23
 *
24
 * Only standard CPP conversion was changed and function name changed
25
 * to follow OLA coding standards.
26
 *
27
 * by Rui Barreiros
28
 *
29
 * Additional modifications to enable support for multiple outputs and
30
 * additional device ids did change the original structure.
31
 *
32
 * by E.S. Rosenberg a.k.a. Keeper of the Keys 5774/2014
33
 */
34

35
#if HAVE_CONFIG_H
36
#include <config.h>
37
#endif  // HAVE_CONFIG_H
38

39
#ifdef HAVE_LIBFTDI1
40
#include <libftdi1/ftdi.h>
41
#else
42
#include <ftdi.h>
43
#endif  // HAVE_LIBFTDI1
44

45
#include <assert.h>
46
#include <strings.h>
47

48
#include <string>
49
#include <algorithm>
50
#include <vector>
51

52
#include "ola/Logging.h"
53
#include "ola/BaseTypes.h"
54
#include "ola/StringUtils.h"
55
#include "ola/Constants.h"
56
#include "plugins/ftdidmx/FtdiWidget.h"
57

58
namespace ola {
59
namespace plugin {
60
namespace ftdidmx {
61

62
using std::string;
63
using std::vector;
64

65
const uint16_t FtdiWidgetInfo::FTDI_VID = 0x0403;
66
const uint16_t FtdiWidgetInfo::FT232_PID = 0x6001;
67
const uint16_t FtdiWidgetInfo::FT4232_PID = 0x6011;
68

69
FtdiWidget::FtdiWidget(const string& serial,
×
70
                       const string& name,
71
                       uint32_t id,
72
                       const uint16_t vid,
73
                       const uint16_t pid)
×
74
    : m_serial(serial),
×
75
      m_name(name),
×
76
      m_id(id),
×
77
      m_vid(vid),
×
78
      m_pid(pid) {}
×
79

80
FtdiWidget::~FtdiWidget() {}
×
81

82
/**
83
 * @brief Get the number of physical interfaces our widget has to offer.
84
 *
85
 * This does not deal with product names being named in a different way.
86
 *
87
 * Originally I had hoped to use ftdi_context::type however, it only gets set
88
 * properly after the device has been opened.
89
 */
90
int FtdiWidget::GetInterfaceCount() {
×
91
  std::string tmp = m_name;
×
92
  ToLower(&tmp);
×
93
  if (std::string::npos != tmp.find("plus4")) {
×
94
    return 4;
95
  } else  if (std::string::npos != tmp.find("plus2")) {
×
96
    return 2;
97
  } else {
98
    return 1;
×
99
  }
100
}
×
101

102
bool FtdiWidget::m_missing_serial = false;
103

104
/**
105
 * @brief Build a list of all attached ftdi devices
106
 */
107
void FtdiWidget::Widgets(vector<FtdiWidgetInfo> *widgets) {
×
108
  int i = -1;
×
109
  widgets->clear();
×
110
  struct ftdi_context *ftdi = ftdi_new();
×
111
  if (!ftdi) {
×
112
    OLA_WARN << "Failed to allocate FTDI context";
×
113
    return;
×
114
  }
115

116
  vector<uint16_t> pids;
×
117
  pids.push_back(FtdiWidgetInfo::FT232_PID);
×
118
  pids.push_back(FtdiWidgetInfo::FT4232_PID);
×
119
  const uint16_t vid = FtdiWidgetInfo::FTDI_VID;
×
120

121
  for (vector<uint16_t>::iterator current_pid = pids.begin();
×
122
       current_pid != pids.end();
×
123
       ++current_pid) {
×
124
    struct ftdi_device_list* list = NULL;
×
125

126
    int devices_found = ftdi_usb_find_all(ftdi, &list, vid, *current_pid);
×
127

128
    if (devices_found < 0) {
×
129
      OLA_WARN << "Failed to get FTDI devices: "
×
130
               << ftdi_get_error_string(ftdi)
×
131
               << " with PID: " << *current_pid;
×
132
    } else {
133
      OLA_INFO << "Found " << devices_found << " FTDI devices with PID: "
×
134
               << *current_pid << ".";
×
135

136
      ftdi_device_list* current_device = list;
×
137

138
      while (current_device != NULL) {
×
139
#if HAVE_LIBFTDI1
140
        struct libusb_device *dev = current_device->dev;
141
#else
142
        struct usb_device *dev = current_device->dev;
×
143
#endif  // HAVE_LIBFTDI1
144
        current_device = current_device->next;
×
145
        i++;
×
146

147
        if (!dev) {
×
148
          OLA_WARN << "Device returned from ftdi_usb_find_all was NULL";
×
149
          continue;
×
150
        }
151

152
        char serial[256];
×
153
        char name[256];
×
154
        char vendor[256];
×
155

156
        int r = ftdi_usb_get_strings(ftdi, dev,
×
157
                                     vendor, sizeof(vendor),
158
                                     name, sizeof(name),
159
                                     serial, sizeof(serial));
160

161
        if (r < 0 &&
×
162
            r != FtdiWidget::libftdi_ftdi_usb_get_strings_get_serial_failed) {
×
163
          OLA_WARN << "Unable to fetch string information from USB device: "
×
164
                   << ftdi_get_error_string(ftdi);
×
165
          continue;
×
166
        }
167

168
        string v = string(vendor);
×
169
        string sname = string(name);
×
170
        string sserial = string(serial);
×
171
        if (sserial == "?" ||
×
172
            r == FtdiWidget::libftdi_ftdi_usb_get_strings_get_serial_failed) {
173
          // This means there wasn't a serial number
174
          sserial.clear();
×
175
        }
176

177
        if (r == FtdiWidget::libftdi_ftdi_usb_get_strings_get_serial_failed) {
×
178
          if (FtdiWidget::m_missing_serial) {
×
179
            OLA_WARN << "Failed to read serial number or serial number empty. "
×
180
                     << "We can only support one device without a serial "
181
                     << "number.";
×
182
            continue;
×
183
          } else {
184
            OLA_WARN << "Failed to read serial number for " << sname;
×
185
            FtdiWidget::m_missing_serial = true;
×
186
          }
187
        }
188

189
        OLA_INFO << "Found FTDI device. Vendor: '" << v << "', Name: '"
×
190
                 << sname << "', Serial: '" << sserial << "'";
×
191
        ToUpper(&v);
×
192
        if (std::string::npos != v.find("FTDI") ||
×
193
            std::string::npos != v.find("JMS") ||
×
194
            std::string::npos != v.find("KMTRONIC") ||
×
195
            std::string::npos != v.find("KWMATIK") ||
×
196
            std::string::npos != v.find("NORTLE") ||
×
197
            std::string::npos != v.find("WWW.SOH.CZ")) {
×
198
          widgets->push_back(FtdiWidgetInfo(sname,
×
199
                                            sserial,
200
                                            i,
201
                                            vid,
202
                                            *current_pid));
×
203
        } else {
204
          OLA_INFO << "Unknown FTDI device with vendor string: '" << v << "'";
×
205
        }
206
      }  // while (list != NULL)
×
207
      OLA_DEBUG << "Freeing list";
×
208
      ftdi_list_free(&list);
×
209
    }
210
  }
211
  ftdi_free(ftdi);
×
212
}
×
213

214
FtdiInterface::FtdiInterface(const FtdiWidget * parent,
×
215
                             const ftdi_interface interface)
×
216
      : m_parent(parent),
×
217
        m_interface(interface) {
×
218
  memset(&m_handle, '\0', sizeof(struct ftdi_context));
×
219
  ftdi_init(&m_handle);
×
220
}
×
221

222

223
FtdiInterface::~FtdiInterface() {
×
224
  if (IsOpen()) {
×
225
    Close();
×
226
  }
227
  ftdi_deinit(&m_handle);
×
228
}
×
229

230
bool FtdiInterface::SetInterface() {
×
231
  OLA_INFO << "Setting interface to: " << m_interface;
×
232
  if (ftdi_set_interface(&m_handle, m_interface) < 0) {
×
233
    OLA_WARN << m_parent->Description() << " "
×
234
             << ftdi_get_error_string(&m_handle);
×
235
    return false;
×
236
  } else {
237
    return true;
238
  }
239
}
240

241
bool FtdiInterface::Open() {
×
242
  if (m_parent->Serial().empty()) {
×
243
    OLA_WARN << m_parent->Name() << " has no serial number, which might cause "
×
244
             << "issues with multiple devices";
×
245
    if (ftdi_usb_open(&m_handle, m_parent->Vid(), m_parent->Pid()) < 0) {
×
246
      OLA_WARN << m_parent->Description() << " "
×
247
               << ftdi_get_error_string(&m_handle);
×
248
      return false;
×
249
    } else {
250
      return true;
251
    }
252
  } else {
253
    OLA_DEBUG << "Opening FTDI device " << m_parent->Name() << ", serial: "
×
254
              << m_parent->Serial() << ", interface: " << m_interface;
×
255

256
    if (ftdi_usb_open_desc(&m_handle, m_parent->Vid(), m_parent->Pid(),
×
257
                           m_parent->Name().c_str(),
×
258
                           m_parent->Serial().c_str()) < 0) {
×
259
      OLA_WARN << m_parent->Description() << " "
×
260
               << ftdi_get_error_string(&m_handle);
×
261
      return false;
×
262
    } else {
263
      return true;
264
    }
265
  }
266
}
267

268
bool FtdiInterface::Close() {
×
269
  if (ftdi_usb_close(&m_handle) < 0) {
×
270
    OLA_WARN << m_parent->Description() << " "
×
271
             << ftdi_get_error_string(&m_handle);
×
272
    return false;
×
273
  } else {
274
    return true;
275
  }
276
}
277

278
bool FtdiInterface::IsOpen() const {
×
279
  return (m_handle.usb_dev != NULL);
×
280
}
281

282
bool FtdiInterface::Reset() {
×
283
  if (ftdi_usb_reset(&m_handle) < 0) {
×
284
    OLA_WARN << m_parent->Description() << " "
×
285
             << ftdi_get_error_string(&m_handle);
×
286
    return false;
×
287
  } else {
288
    return true;
289
  }
290
}
291

292
bool FtdiInterface::SetLineProperties() {
×
293
  if ((ftdi_set_line_property(&m_handle, BITS_8, STOP_BIT_2, NONE) < 0)) {
×
294
    OLA_WARN << m_parent->Description() << " "
×
295
             << ftdi_get_error_string(&m_handle);
×
296
    return false;
×
297
  } else {
298
    return true;
299
  }
300
}
301

302
bool FtdiInterface::SetBaudRate(int speed) {
×
303
  if (ftdi_set_baudrate(&m_handle, speed) < 0) {
×
304
    OLA_WARN << "Error setting " << m_parent->Description() << " to baud rate "
×
305
             << "of " << speed << " - " << ftdi_get_error_string(&m_handle);
×
306
    return false;
×
307
  } else {
308
    return true;
309
  }
310
}
311

312
bool FtdiInterface::SetFlowControl() {
×
313
  if (ftdi_setflowctrl(&m_handle, SIO_DISABLE_FLOW_CTRL) < 0) {
×
314
    OLA_WARN << m_parent->Description() << " "
×
315
             << ftdi_get_error_string(&m_handle);
×
316
    return false;
×
317
  } else {
318
    return true;
319
  }
320
}
321

322
bool FtdiInterface::ClearRts() {
×
323
  if (ftdi_setrts(&m_handle, 0) < 0) {
×
324
    OLA_WARN << m_parent->Description() << " "
×
325
             << ftdi_get_error_string(&m_handle);
×
326
    return false;
×
327
  } else {
328
    return true;
329
  }
330
}
331

332
bool FtdiInterface::PurgeBuffers() {
×
333
  if (ftdi_usb_purge_buffers(&m_handle) < 0) {
×
334
    OLA_WARN << m_parent->Description() << " "
×
335
             << ftdi_get_error_string(&m_handle);
×
336
    return false;
×
337
  } else {
338
    return true;
339
  }
340
}
341

342
bool FtdiInterface::SetBreak(bool on) {
×
343
  if (ftdi_set_line_property2(&m_handle, BITS_8, STOP_BIT_2, NONE,
×
344
                              (on ? BREAK_ON : BREAK_OFF)) < 0) {
345
    OLA_WARN << m_parent->Description() << " "
×
346
             << ftdi_get_error_string(&m_handle);
×
347
    return false;
×
348
  } else {
349
    return true;
350
  }
351
}
352

353
bool FtdiInterface::Write(const ola::DmxBuffer& data) {
×
354
  unsigned char buffer[DMX_UNIVERSE_SIZE + 1];
×
355
  unsigned int length = DMX_UNIVERSE_SIZE;
×
356
  buffer[0] = DMX512_START_CODE;
×
357

358
  data.Get(buffer + 1, &length);
×
359

360
  if (ftdi_write_data(&m_handle, buffer, length + 1) < 0) {
×
361
    OLA_WARN << m_parent->Description() << " "
×
362
             << ftdi_get_error_string(&m_handle);
×
363
    return false;
×
364
  } else {
365
    return true;
366
  }
367
}
368

369
bool FtdiInterface::Read(unsigned char *buff, int size) {
×
370
  int read = ftdi_read_data(&m_handle, buff, size);
×
371
  if (read <= 0) {
×
372
    OLA_WARN << m_parent->Description() << " "
×
373
             << ftdi_get_error_string(&m_handle);
×
374
    return false;
×
375
  } else {
376
    return true;
377
  }
378
}
379

380
bool FtdiInterface::SetupOutput() {
×
381
  // Setup the widget
382
  if (!SetInterface()) {
×
383
    OLA_WARN << "Error setting the device interface.";
×
384
    return false;
×
385
  }
386

387
  if (!Open()) {
×
388
    OLA_WARN << "Error Opening widget";
×
389
    return false;
×
390
  }
391

392
  if (!Reset()) {
×
393
    OLA_WARN << "Error Resetting widget";
×
394
    return false;
×
395
  }
396

397
  if (!SetBaudRate()) {
×
398
    OLA_WARN << "Error Setting baudrate";
×
399
    return false;
×
400
  }
401

402
  if (!SetLineProperties()) {
×
403
    OLA_WARN << "Error setting line properties";
×
404
    return false;
×
405
  }
406

407
  if (!SetFlowControl()) {
×
408
    OLA_WARN << "Error setting flow control";
×
409
    return false;
×
410
  }
411

412
  if (!PurgeBuffers()) {
×
413
    OLA_WARN << "Error purging buffers";
×
414
    return false;
×
415
  }
416

417
  if (!ClearRts()) {
×
418
    OLA_WARN << "Error clearing rts";
×
419
    return false;
×
420
  }
421

422
  return true;
423
}
424

425
}  // namespace ftdidmx
426
}  // namespace plugin
427
}  // 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