1
1
//
2
2
// This file is part of Dire Wolf, an amateur radio packet TNC.
3
3
//
4
- // Copyright (C) 2017 John Langner, WB2OSZ
4
+ // Copyright (C) 2017,2019 John Langner, WB2OSZ
5
5
//
6
6
// Parts of this were adapted from "hamlib" which contains the notice:
7
7
//
31
31
* Description:
32
32
*
33
33
* There is an incresing demand for using the GPIO pins of USB audio devices for PTT.
34
- * We have a couple commercial products:
34
+ * We have a few commercial products:
35
35
*
36
- * DMK URI http://www.dmkeng.com/URI_Order_Page.htm
37
- * RB-USB RIM http://www.repeater-builder.com/products/usb-rim-lite.html
36
+ * DMK URI http://www.dmkeng.com/URI_Order_Page.htm
37
+ * RB-USB RIM http://www.repeater-builder.com/products/usb-rim-lite.html
38
+ * RA-35 http://www.masterscommunications.com/products/radio-adapter/ra35.html
38
39
*
39
40
* and homebrew projects which are all very similar.
40
41
*
47
48
*
48
49
* Soundmodem and hamlib paved the way but didn't get too far.
49
50
* Dire Wolf 1.3 added HAMLIB support (Linux only) which theoretically allows this in a
50
- * roundabout way. This is documented in the User Guide, section called,
51
+ * painful roundabout way. This is documented in the User Guide, section called,
51
52
* "Hamlib PTT Example 2: Use GPIO of USB audio adapter. (e.g. DMK URI)"
52
53
*
53
54
* It's rather involved and the explantion doesn't cover the case of multiple
@@ -215,7 +216,7 @@ static int cm108_write (char *name, int iomask, int iodata);
215
216
216
217
// Used to process regular expression matching results.
217
218
218
- static void inline substr_se (char * dest , const char * src , int start , int endp1 )
219
+ static void substr_se (char * dest , const char * src , int start , int endp1 )
219
220
{
220
221
int len = endp1 - start ;
221
222
@@ -234,14 +235,19 @@ static void inline substr_se (char *dest, const char *src, int start, int endp1)
234
235
*/
235
236
236
237
struct thing_s {
237
- int vid ; // vendor id
238
- int pid ; // product id
238
+ int vid ; // vendor id, displayed as four hexadecimal digits.
239
+ int pid ; // product id, displayed as four hexadecimal digits.
240
+ char card_number [8 ]; // Number. e.g. 2 for plughw:2,0
241
+ char card_name [32 ]; // Name, assigned by system (e.g. Device_1) or by udev rule.
239
242
char product [32 ]; // product name (e.g. manufacturer, model)
240
243
char devnode_sound [22 ]; // e.g. /dev/snd/pcmC0D0p
241
244
char plughw [72 ]; // Above in more familiar format e.g. plughw:0,0
242
245
// Oversized to silence a compiler warning.
246
+ char plughw2 [72 ]; // With name rather than number.
247
+ char devpath [128 ]; // Kernel dev path. Does not include /sys mount point.
243
248
char devnode_hidraw [17 ]; // e.g. /dev/hidraw3
244
249
char devnode_usb [25 ]; // e.g. /dev/bus/usb/001/012
250
+ // This is what we use to match up audio and HID.
245
251
};
246
252
247
253
int cm108_inventory (struct thing_s * things , int max_things );
@@ -251,13 +257,12 @@ int cm108_inventory (struct thing_s *things, int max_things);
251
257
*
252
258
* Name: main
253
259
*
254
- * Purpose: Test program to list USB audio and HID devices.
255
- *
256
- * sudo apt-get install libudev-dev
257
- * gcc -DCM108_MAIN textcolor.c -l udev
260
+ * Purpose: Useful utility to list USB audio and HID devices.
258
261
*
259
262
*------------------------------------------------------------------*/
260
263
264
+ //#define EXTRA 1
265
+
261
266
#define MAXX_THINGS 60
262
267
263
268
#ifdef CM108_MAIN
@@ -270,36 +275,40 @@ int main (void)
270
275
271
276
text_color_init (0 ); // Turn off text color.
272
277
278
+ // Take inventory of USB Audio adapters and other HID devices.
279
+
273
280
num_things = cm108_inventory (things , MAXX_THINGS );
274
281
275
- dw_printf (" VID PID %-*s %-*s %-*s %-*s"
282
+ dw_printf (" VID PID %-*s %-*s %-*s %-*s %-*s "
276
283
#if EXTRA
277
284
" %-*s"
278
285
#endif
279
286
"\n" , (int )sizeof (things [0 ].product ), "Product" ,
280
287
(int )sizeof (things [0 ].devnode_sound ), "Sound" ,
281
- (int )sizeof (things [0 ].plughw ), "ADEVICE" ,
288
+ (int )sizeof (things [0 ].plughw )/5 , "ADEVICE" ,
289
+ (int )sizeof (things [0 ].plughw2 )/4 , "ADEVICE" ,
282
290
(int )sizeof (things [0 ].devnode_hidraw ), "HID [ptt]"
283
291
#if EXTRA
284
292
, (int )sizeof (things [0 ].devnode_usb ), "USB"
285
293
#endif
286
294
);
287
295
288
- dw_printf (" --- --- %-*s %-*s %-*s %-*s"
296
+ dw_printf (" --- --- %-*s %-*s %-*s %-*s %-*s "
289
297
#if EXTRA
290
298
" %-*s"
291
299
#endif
292
300
"\n" , (int )sizeof (things [0 ].product ), "-------" ,
293
301
(int )sizeof (things [0 ].devnode_sound ), "-----" ,
294
- (int )sizeof (things [0 ].plughw ), "-------" ,
302
+ (int )sizeof (things [0 ].plughw )/5 , "-------" ,
303
+ (int )sizeof (things [0 ].plughw2 )/4 , "-------" ,
295
304
(int )sizeof (things [0 ].devnode_hidraw ), "---------"
296
305
#if EXTRA
297
306
, (int )sizeof (things [0 ].devnode_usb ), "---"
298
307
#endif
299
308
);
300
309
for (i = 0 ; i < num_things ; i ++ ) {
301
310
302
- dw_printf ("%2s %04x %04x %-*s %-*s %-*s %-*s"
311
+ dw_printf ("%2s %04x %04x %-*s %-*s %-*s %-*s %-*s "
303
312
#if EXTRA
304
313
" %-*s"
305
314
#endif
@@ -308,13 +317,61 @@ int main (void)
308
317
things [i ].vid , things [i ].pid ,
309
318
(int )sizeof (things [i ].product ), things [i ].product ,
310
319
(int )sizeof (things [i ].devnode_sound ), things [i ].devnode_sound ,
311
- (int )sizeof (things [0 ].plughw ), things [i ].plughw ,
320
+ (int )sizeof (things [0 ].plughw )/5 , things [i ].plughw ,
321
+ (int )sizeof (things [0 ].plughw2 )/4 , things [i ].plughw2 ,
312
322
(int )sizeof (things [i ].devnode_hidraw ), things [i ].devnode_hidraw
313
323
#if EXTRA
314
324
, (int )sizeof (things [i ].devnode_usb ), things [i ].devnode_usb
315
325
#endif
316
326
);
327
+ //dw_printf (" %-*s\n", (int)sizeof(things[i].devpath), things[i].devpath);
328
+ }
329
+
330
+ static const char * suggested_names [] = {"Fred" , "Wilma" , "Pebbles" , "Dino" , "Barney" , "Betty" , "Bamm_Bamm" };
331
+ int iname = 0 ;
332
+
333
+ // From example in https://alsa.opensrc.org/Udev
334
+
335
+ dw_printf ("\n" );
336
+ dw_printf ("** = Can use Audio Adapter GPIO for PTT.\n" );
337
+ dw_printf ("\n" );
338
+ dw_printf ("Notice that each USB Audio adapter is assigned a number and a name. These are not predictable so you could\n" );
339
+ dw_printf ("end up using the wrong adapter after adding or removing other USB devices or after rebooting. You can assign a\n" );
340
+ dw_printf ("name to each USB adapter so you can refer to the same one each time. This can be based on any characteristics\n" );
341
+ dw_printf ("that makes them unique such as product id or serial number. Unfortunately these devices don't have unique serial\n" );
342
+ dw_printf ("numbers so how can we tell them apart? A name can also be assigned based on the physical USB socket.\n" );
343
+ dw_printf ("Create a file like \"/etc/udev/rules.d/85-my-usb-audio.rules\" with the following contents and then reboot.\n" );
344
+ dw_printf ("\n" );
345
+ dw_printf ("SUBSYSTEM!=\"sound\", GOTO=\"my_usb_audio_end\"\n" );
346
+ dw_printf ("ACTION!=\"add\", GOTO=\"my_usb_audio_end\"\n" );
347
+
348
+ // Consider only the 'devnode' paths that end with "card" and a number.
349
+ // Replace the number with a question mark.
350
+
351
+ regex_t devpath_re ;
352
+ char emsg [100 ];
353
+ // Drop any "/sys" at the beginning.
354
+ int e = regcomp (& devpath_re , "(/devices/.+/card)[0-9]$" , REG_EXTENDED );
355
+ if (e ) {
356
+ regerror (e , & devpath_re , emsg , sizeof (emsg ));
357
+ text_color_set (DW_COLOR_ERROR );
358
+ dw_printf ("INTERNAL ERROR: %s:%d: %s\n" , __FILE__ , __LINE__ , emsg );
359
+ return (-1 );
360
+ }
361
+
362
+ for (i = 0 ; i < num_things ; i ++ ) {
363
+ if (i == 0 || strcmp (things [i ].devpath ,things [i - 1 ].devpath ) != 0 ) {
364
+ regmatch_t devpath_match [2 ];
365
+ if (regexec (& devpath_re , things [i ].devpath , 2 , devpath_match , 0 ) == 0 ) {
366
+ char without_number [256 ];
367
+ substr_se (without_number , things [i ].devpath , devpath_match [1 ].rm_so , devpath_match [1 ].rm_eo );
368
+ dw_printf ("DEVPATH==\"%s?\", ATTR{id}=\"%s\"\n" , without_number , suggested_names [iname ]);
369
+ if (iname < 6 ) iname ++ ;
370
+ }
371
+ }
317
372
}
373
+ dw_printf ("LABEL=\"my_usb_audio_end\"\n" );
374
+ dw_printf ("\n" );
318
375
319
376
return (0 );
320
377
}
@@ -349,6 +406,10 @@ int cm108_inventory (struct thing_s *things, int max_things)
349
406
struct udev_device * dev ;
350
407
struct udev_device * parentdev ;
351
408
409
+ char const * pattrs_id = NULL ;
410
+ char const * pattrs_number = NULL ;
411
+ char card_devpath [128 ] = "" ;
412
+
352
413
int num_things = 0 ;
353
414
memset (things , 0 , sizeof (struct thing_s ) * max_things );
354
415
@@ -368,11 +429,21 @@ int cm108_inventory (struct thing_s *things, int max_things)
368
429
udev_enumerate_scan_devices (enumerate );
369
430
devices = udev_enumerate_get_list_entry (enumerate );
370
431
udev_list_entry_foreach (dev_list_entry , devices ) {
371
- const char * path ;
372
- path = udev_list_entry_get_name (dev_list_entry );
432
+ const char * path = udev_list_entry_get_name (dev_list_entry );
373
433
dev = udev_device_new_from_syspath (udev , path );
374
434
char const * devnode = udev_device_get_devnode (dev );
375
- if (devnode != NULL ) {
435
+
436
+ if (devnode == NULL ) {
437
+ // I'm not happy with this but couldn't figure out how
438
+ // to get attributes from one level up from the pcmC?D?? node.
439
+ strlcpy (card_devpath , path , sizeof (card_devpath ));
440
+ pattrs_id = udev_device_get_sysattr_value (dev ,"id" );
441
+ pattrs_number = udev_device_get_sysattr_value (dev ,"number" );
442
+ //dw_printf (" >card_devpath = %s\n", card_devpath);
443
+ //dw_printf (" >>pattrs_id = %s\n", pattrs_id);
444
+ //dw_printf (" >>pattrs_number = %s\n", pattrs_number);
445
+ }
446
+ else {
376
447
parentdev = udev_device_get_parent_with_subsystem_devtype ( dev , "usb" , "usb_device" );
377
448
if (parentdev != NULL ) {
378
449
char const * p ;
@@ -387,9 +458,12 @@ int cm108_inventory (struct thing_s *things, int max_things)
387
458
if (num_things < max_things ) {
388
459
things [num_things ].vid = vid ;
389
460
things [num_things ].pid = pid ;
461
+ SAFE_STRCPY (things [num_things ].card_name , pattrs_id );
462
+ SAFE_STRCPY (things [num_things ].card_number , pattrs_number );
390
463
SAFE_STRCPY (things [num_things ].product , udev_device_get_sysattr_value (parentdev ,"product" ));
391
464
SAFE_STRCPY (things [num_things ].devnode_sound , devnode );
392
465
SAFE_STRCPY (things [num_things ].devnode_usb , udev_device_get_devnode (parentdev ));
466
+ strlcpy (things [num_things ].devpath , card_devpath , sizeof (things [num_things ].devpath ));
393
467
num_things ++ ;
394
468
}
395
469
udev_device_unref (parentdev );
@@ -414,8 +488,7 @@ int cm108_inventory (struct thing_s *things, int max_things)
414
488
udev_enumerate_scan_devices (enumerate );
415
489
devices = udev_enumerate_get_list_entry (enumerate );
416
490
udev_list_entry_foreach (dev_list_entry , devices ) {
417
- const char * path ;
418
- path = udev_list_entry_get_name (dev_list_entry );
491
+ const char * path = udev_list_entry_get_name (dev_list_entry );
419
492
dev = udev_device_new_from_syspath (udev , path );
420
493
char const * devnode = udev_device_get_devnode (dev );
421
494
if (devnode != NULL ) {
@@ -448,6 +521,7 @@ int cm108_inventory (struct thing_s *things, int max_things)
448
521
SAFE_STRCPY (things [num_things ].product , udev_device_get_sysattr_value (parentdev ,"product" ));
449
522
SAFE_STRCPY (things [num_things ].devnode_hidraw , devnode );
450
523
SAFE_STRCPY (things [num_things ].devnode_usb , usb );
524
+ SAFE_STRCPY (things [num_things ].devpath , udev_device_get_devpath (dev ));
451
525
num_things ++ ;
452
526
}
453
527
udev_device_unref (parentdev );
@@ -461,6 +535,8 @@ int cm108_inventory (struct thing_s *things, int max_things)
461
535
* Seeing the form /dev/snd/pcmC4D0p will be confusing to many because we
462
536
* would generally something like plughw:4,0 for in the direwolf configuration file.
463
537
* Construct the more familiar form.
538
+ * Previously we only used the numeric form. In version 1.6, the name is listed as well
539
+ * and we describe how to assign names based on the physical USB socket for repeatability.
464
540
*/
465
541
int i ;
466
542
regex_t pcm_re ;
@@ -481,6 +557,7 @@ int cm108_inventory (struct thing_s *things, int max_things)
481
557
substr_se (c , things [i ].devnode_sound , match [1 ].rm_so , match [1 ].rm_eo );
482
558
substr_se (d , things [i ].devnode_sound , match [2 ].rm_so , match [2 ].rm_eo );
483
559
snprintf (things [i ].plughw , sizeof (things [i ].plughw ), "plughw:%s,%s" , c , d );
560
+ snprintf (things [i ].plughw2 , sizeof (things [i ].plughw ), "plughw:%s,%s" , things [i ].card_name , d );
484
561
}
485
562
}
486
563
@@ -493,11 +570,16 @@ int cm108_inventory (struct thing_s *things, int max_things)
493
570
*
494
571
* Name: cm108_find_ptt
495
572
*
496
- * Purpose: Try to find /dev/hidraw corresponding to plughw:n,n
573
+ * Purpose: Try to find /dev/hidraw corresponding to a USB audio "card."
497
574
*
498
575
* Inputs: output_audio_device
499
- * - Device name used in the ADEVICE configuration.
500
- * This would generally be something like plughw:1,0
576
+ * - Used in the ADEVICE configuration.
577
+ * This can take many forms such as:
578
+ * surround41:CARD=Fred,DEV=0
579
+ * surround41:Fred,0
580
+ * surround41:Fred
581
+ * plughw:2,3
582
+ * In our case we just need to extract the card number or name.
501
583
*
502
584
* ptt_device_size - Size of result area to avoid buffer overflow.
503
585
*
@@ -514,15 +596,46 @@ void cm108_find_ptt (char *output_audio_device, char *ptt_device, int ptt_devic
514
596
int num_things ;
515
597
int i ;
516
598
599
+ //dw_printf ("DEBUG: cm108_find_ptt('%s')\n", output_audio_device);
600
+
517
601
strlcpy (ptt_device , "" , ptt_device_size );
518
602
num_things = cm108_inventory (things , MAXX_THINGS );
519
603
520
- for (i = 0 ; i < num_things ; i ++ ) {
604
+ regex_t sound_re ;
605
+ char emsg [100 ];
606
+ int e = regcomp (& sound_re , ".+:(CARD=)?([A-Za-z0-9_]+)(,.*)?" , REG_EXTENDED );
607
+ if (e ) {
608
+ regerror (e , & sound_re , emsg , sizeof (emsg ));
609
+ text_color_set (DW_COLOR_ERROR );
610
+ dw_printf ("INTERNAL ERROR: %s:%d: %s\n" , __FILE__ , __LINE__ , emsg );
611
+ return ;
612
+ }
521
613
522
- if (GOOD_DEVICE (things [i ].vid ,things [i ].pid ) ) {
523
- if (strcmp (output_audio_device , things [i ].plughw ) == 0 ) {
524
- strlcpy (ptt_device , things [i ].devnode_hidraw , ptt_device_size );
614
+ char num_or_name [64 ];
615
+ strcpy (num_or_name , "" );
616
+ regmatch_t sound_match [4 ];
617
+ if (regexec (& sound_re , output_audio_device , 4 , sound_match , 0 ) == 0 ) {
618
+ substr_se (num_or_name , output_audio_device , sound_match [2 ].rm_so , sound_match [2 ].rm_eo );
619
+ //dw_printf ("DEBUG: Got '%s' from '%s'\n", num_or_name, output_audio_device);
620
+ }
621
+ if (strlen (num_or_name ) == 0 ) {
622
+ text_color_set (DW_COLOR_ERROR );
623
+ dw_printf ("Could not extract card number or name from %s\n" , output_audio_device );
624
+ dw_printf ("Can't automatically find matching HID for PTT.\n" );
625
+ return ;
626
+ }
627
+
628
+ for (i = 0 ; i < num_things ; i ++ ) {
629
+ //dw_printf ("DEBUG: i=%d, card_name='%s', card_number='%s'\n", i, things[i].card_name, things[i].card_number);
630
+ if (strcmp (num_or_name ,things [i ].card_name ) == 0 || strcmp (num_or_name ,things [i ].card_number ) == 0 ) {
631
+ //dw_printf ("DEBUG: success! returning '%s'\n", things[i].devnode_hidraw);
632
+ strlcpy (ptt_device , things [i ].devnode_hidraw , ptt_device_size );
633
+ if ( ! GOOD_DEVICE (things [i ].vid ,things [i ].pid ) ) {
634
+ text_color_set (DW_COLOR_ERROR );
635
+ dw_printf ("Warning: USB audio card %s (%s) is not a device known to work with GPIO PTT.\n" ,
636
+ things [i ].card_number , things [i ].card_name );
525
637
}
638
+ return ;
526
639
}
527
640
}
528
641
0 commit comments