File: usbdevice.hh

package info (click to toggle)
qdmr 0.13.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky
  • size: 22,420 kB
  • sloc: cpp: 95,929; xml: 10,749; python: 1,108; makefile: 78; sh: 9
file content (254 lines) | stat: -rw-r--r-- 9,815 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
/** @defgroup detect Device detection and enumeration.
 * This module collects classes and functions to discover and select interfaces to connected radios.
 *
 * With an increasing number of supported devices, the issue arises, that auto detection of
 * radios may fail or may even harmful. Some manufacturers simply use generic USB-serial chips
 * within the cable to talk to them over USB. These chips may also be used in other devices that may
 * react harmful to qdmrs attempts to identify them. Moreover, the assumption of the first detected
 * device with a specific VID:PID combination may not be valid. To this end, some means of
 * discovering possible radios and selecting a specific one by the user are needed. Also some radios
 * do not identify themselves before any action is performed (i.e., reading, writing the codeplug
 * or callsign db). Hence, for some devices, the user must specify the type of the radio. The latter
 * concerns the Kydera CDR-300UV, Retevis RT73 and similar devices.
 *
 * This module specifies the classes and functions to discover possible radios and to address each
 * one uniquely. They allow for an implementation of a semi-automatic device detection. That is,
 * for the majority of radios, if a single matching VID:PID combination is found, it can be assumed
 * that this device is a radio and it can then be identified by sending commands to it. Some radios,
 * however, like the Kydera CDR-300UV, cannot be identified before the actual codeplug read or
 * write operation. They simply do not provide a command for identification.
 *
 * For a save semi-automatic detection the following steps are performed:
 * @dotfile autodetect.dot "Semi-automatic radio detection"
 *
 *   -# Search for all USB devices with known VID:PID combinations. This can be done using the
 *      @c USBDeviceDescriptor::detect method. It returns a list of matching device descriptors
 *      found. If only one device is found, one may continue with that one if it is safe to assume
 *      that the device detected is a DMR radio. The latter is not true for radios using generic
 *      USB CDC-ACM chips, as other serial devices may be connected. If several devices are found or
 *      it is not save to assume a DMR radio, the user must select a device.
 *   -# Once the USB device is selected, one needs to identify the connected radio. Unfortunately,
 *      not all radios can be identified easily by simply sending a command to it. To this end,
 *      one first needs to check if the selected device is identifiable. That is, if the protocol
 *      provides commands to identify the connected radio. The @c USBDeviceInfo provides this
 *      information. If a device is not identifiable, the user must specify the specific connected
 *      radio. This can be done by obtaining all known radios matching the selected USB device
 *      (VID:PID) by calling RadioInfo::allRadios, passing the @c USBDeviceDescriptor.
 *
 * @section detectExample A example for AnyTone devices
 * This example tries to detect an AnyTone device and reads the binary codeplug from it. Once the
 * codeplug is read, it is decoded into its generic device independent representation (@c Config).
 *
 * @code
 * #include "libdmrconf/usbdevice.hh"
 * #include "libdmrconf/anytone_radio.hh"
 *
 * int main(void)
 * {
 *   // First, search matching devices (only AnyTones)
 *   // to find all supported devices, call @c USBDescriptor::detect();
 *   QList<USBDeviceDescriptor> devices = AnytoneInterface::detect();
 *   if (1 != devices.count()) {
 *     // Either none or more than one device found...
 *     return -1;
 *   }
 *
 *   // A place to put error messages
 *   ErrorStack err;
 *   // Dedetect the specific radio and get radio descriptor.
 *   // To detect any radio based on the selected descriptor, call @c Radio::detect().
 *   Radio *radio = AnytoneRadio::detect(devices.first(), RadioInfo(), err);
 *   if (nullptr == radio) {
 *     // There went something wrong, check err.
 *     return -1;
 *   }
 *
 *   // Read codeplug from device blocking.
 *   if (! radio->startDownload(true, err)) {
 *     // Some read error, check err.
 *     delete radio;
 *     return -1;
 *   }
 *
 *   // Decode codeplug into generic representation
 *   Config genericCodeplug;
 *   if (! radio->codeplug().decode(&genericCodeplug, err)) {
 *     // Some decoding error, check err.
 *     delete radio;
 *     return -1;
 *   }
 *
 *   // Do whatever you like with the codeplug.
 *
 *   return 0;
 * }
 * @endcode
 *
 * @ingroup rif */

#ifndef USBDEVICE_HH
#define USBDEVICE_HH

#include <inttypes.h>
#include <QVariant>

/** Combines the USB bus and device number, to address a USB device uniquely.
 *
 * @ingroup detect */
struct USBDeviceHandle {
  uint8_t bus;         ///< Holds the bus number.
  uint8_t device;      ///< Holds the device address.
  uint32_t locationId; ///< On MacOS, holds the location ID.

  /** Empty constructor. */
  USBDeviceHandle();
  /** Constructor from bus and device number. */
  USBDeviceHandle(uint8_t busno, uint8_t deviceno, uint32_t locid=0);

  /** Compares only wrt bus and device number. */
  bool operator==(const USBDeviceHandle &other);
};
Q_DECLARE_METATYPE(USBDeviceHandle)

/** Generic information about a possible radio interface.
 *
 * This class combines the USB vendor, product ID and some meta information about the interface.
 * In particular if it is safe to access the device without user ineraction and if the protocol
 * implements means for identifying the specific radio.
 *
 * @ingroup detect */
class USBDeviceInfo
{
public:
  /** Possible interface types. */
  enum class Class {
    None,       ///< Class for invalid interface info.
    Serial,     ///< Serial port interface class.
    DFU,        ///< DFU interface class.
    HID,        ///< HID (human-interface device) interface class.
    C7K         ///< Raw USB access to C7000 devices.
  };

public:
  /** Empty constructor. */
  USBDeviceInfo();
  /** Constructor from class, VID and PID. */
  USBDeviceInfo(Class cls, uint16_t vid, uint16_t pid, bool save=true);
  /** Destructor. */
  virtual ~USBDeviceInfo();

  /** Copy constructor. */
  USBDeviceInfo(const USBDeviceInfo &other);
  /** Assignment. */
  USBDeviceInfo &operator =(const USBDeviceInfo &other);

  /** Comparison. */
  bool operator ==(const USBDeviceInfo &other) const;
  /** Comparison. */
  bool operator !=(const USBDeviceInfo &other) const;

  /** Returns @c true if the interface info is valid. */
  bool isValid() const;

  /** Returns the interface class. */
  Class interfaceClass() const;

  /** Returns @c true, if a vendor ID is set. */
  bool hasVendorID() const;
  /** Returns the vendor ID or 0 if not set. */
  uint16_t vendorId() const;
  /** Returns @c true, if a product ID is set. */
  bool hasProductID() const;
  /** Returns the product ID or 0 if not set. */
  uint16_t productId() const;

  /** Returns a brief human readable description of the interface. */
  QString description() const;

  /** Returns a more extensive human readable description of the interface. */
  QString longDescription() const;

  /** Returns @c true if it is safe to send commands to this device without user approval.
   * This is true for radios which use somewhat unique VID:PIDs. Radios with generic USB-serial
   * chips are not save, as other devices may use the same chip and sending data to these devices
   * may be harmful. */
  bool isSave() const;

protected:
  /** The class of the interface. */
  Class _class;
  /** The USB vid. */
  uint16_t _vid;
  /** The USB pid. */
  uint16_t _pid;
  /** If @c true, it is safe to send commands to the device without user approval. */
  bool _save;
};


/** Base class for all radio interface descriptors representing a unique interface to a
 * connected radio.
 *
 * This class extends the @c USBDeviceInfo by some information to identify a USB uniquely. This is
 * either the bus and device number or the path to the serial port.
 *
 * @ingroup detect */
class USBDeviceDescriptor: public USBDeviceInfo
{
protected:
  /** Hidden constructor from info and path string. */
  USBDeviceDescriptor(const USBDeviceInfo &info, const QString &device);
  /** Hidden constructor from info and USB device address. */
  USBDeviceDescriptor(const USBDeviceInfo &info, const USBDeviceHandle &device);

public:
  /** Empty constructor. */
  USBDeviceDescriptor();
  /** Copy constructor. */
  USBDeviceDescriptor(const USBDeviceDescriptor &other);

  /** Assignment */
  USBDeviceDescriptor &operator =(const USBDeviceDescriptor &other);

  /** Returns @c true if the descriptor is still valid. That is, if the described device is still
   * connected. */
  bool isValid() const;

  /** Returns a human readable description of the device. */
  QString description() const;

  /** Returns the device information identifying the interface uniquely. */
  const QVariant &device() const;
  /** Returns a unique string representation of the device information. */
  QString deviceHandle() const;

public:
  /** Searches for all connected radios (may contain false positives). */
  static QList<USBDeviceDescriptor> detect(bool saveOnly=true);

protected:
  /** Checks a serial port. */
  bool validSerial() const;
  /** Checks a raw USB device. */
  bool validRawUSB() const;

protected:
  /** Holds some information to identify the radio interface uniquely. */
  QVariant _device;
};


namespace std {
template <> struct hash<USBDeviceInfo>
{
  // seed is optional
  inline size_t operator()(const USBDeviceInfo &key, size_t seed = 0) const {
    return qHash((unsigned)key.interfaceClass(),
                 qHash(key.vendorId(),
                       qHash(key.productId(),
                             qHash(key.isSave(), seed))));
  }
};
}

#endif // USBDEVICE_HH