Skip to content

Commit

Permalink
Merge pull request #42 from areaDetector/add-open-by-index-option
Browse files Browse the repository at this point in the history
Add open by index option
  • Loading branch information
jwlodek committed Feb 9, 2023
2 parents 0aa7bbf + b23154a commit 072123b
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 24 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,15 @@ Product: (null)
UVC Compliance: 0
```

You may need to run the program as `sudo` if camera serial number is locked behind root access.
You may need to run the program as `root` if camera serial number is locked behind root access.

Next, in the st.cmd file in the `iocs/uvcIOC/iocBoot/iocUVC` directory, locate the ADUVCConfig function call. There are two options for this function, one where the serial number is passed and product ID is set 0, and one where the productID is passed and the serial number is an empty string "". Simply uncomment the way you wish to connect to the device, and place the serial or productID in the appropriate parameter spot. From here the driver IOC is ready to be started with:
Next, in the st.cmd file in the `iocs/uvcIOC/iocBoot/iocUVC` directory, locate the ADUVCConfig function call. There are three options for this function, one where the serial number is used, one where the product ID is used, and one where the UVC device index is used. Simply uncomment the way you wish to connect to the device, and pass the appropriate values in for the serial, productID, and deviceIndex parameters. If available, the serial number connection mode is recommended, since it guarantees unique access to the device. The product ID mode is the second best option, with deviceIndex only being a recommended operating mode if you have identical devices with no serial numbers running on the same machine (in which case the product IDs wouldn't be unique). From here the driver IOC is ready to be started with:

```
./st.cmd
```

Again, note that if usb devices are locked behind root privelages you may need to run the IOC with `sudo`, or adjust `udev` permissions/rules.
Again, note that if usb devices are locked behind root privelages you may need to run the IOC as `root`, or adjust `udev` permissions/rules. To simplify this process, there is a python3 script in the support directory - it will run the `uvc_locater` command, and then use the output vendor/product IDs to generate udev rules for each camera. This file will then be installed under `/etc/udev/rules.d/###-usbcams.rules`.

Further documentation, including CSS screenshots and usage information, is available at the driver's [website](https://jwlodek.github.io/ADUVC).

Expand Down
12 changes: 12 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@ ADUVC requires libusb, libuvc, epics-base, epics-modules, ADCore, and ADSupport.
Release Notes
=============

R1-7 (27-January-2023)
----
* Features Added
* New option to connect to device by device index - needed for connecting to identical devices with no serial number
* Add printing of HEX value of product and vendor IDs to `uvc_locater` - useful for generating udev rules
* New flag for `uvc_locater` to print more easily machine readable output
* New simple utility script for generating udev rule files from output of `uvc_locater`

* Fixes
* Minor bug with continuous acquisition mode fixed
* Avoid "Auto Adjust" as default behavior


R1-6 (1-December-2020)
----
Expand Down
21 changes: 12 additions & 9 deletions iocs/uvcIOC/iocBoot/iocADUVC/st.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ epicsEnvSet("QSIZE", "20")
epicsEnvSet("XSIZE", "640")
# The maximim image height; used for column profiles in the NDPluginStats plugin
epicsEnvSet("YSIZE", "480")
# The framerate at which the stream will operate
epicsEnvSet("FRAMERATE", "30");
# The maximum number of time seried points in the NDPluginStats plugin
epicsEnvSet("NCHANS", "2048")
# The maximum number of frames buffered in the NDPluginCircularBuff plugin
Expand All @@ -35,23 +33,28 @@ epicsEnvSet("EPICS_CA_MAX_ARRAY_BYTES", 20000000)
# *
# * @params: portName -> port for NDArray recieved from camera
# * @params: serial -> serial number of device to connect to
# * @params: productID -> id number for device to connect to
# * @params: framerate -> framerate at which camera should operate
# * @params: productID -> Product id number for device to connect to
# * @params: deviceIndex -> Index of camera in device list, starting with 0
# * @params: xsize -> width of image
# * @params: ysize -> height of image
# * @params: maxBuffers -> max buffer size for NDArrays
# * @params: maxMemory -> maximum memory allocated for driver
# * @params: priority -> what thread priority this driver will execute with
# * @params: stackSize -> size of the driver on the stack
# */
# ADUVCConfig(const char* portName, const char* serial, int productID, int deviceIndex, int xsize, int ysize, int maxBuffers, size_t maxMemory, int priority, int stackSize)

# If searching for device by serial number, put 0 and 0 for vendor/productID
# ADUVCConfig(const char* portName, const char* serial, int productID, int framerate, int xsize, int ysize, int maxBuffers, size_t maxMemory, int priority, int stackSize)
#ADUVCConfig("$(PORT)", "10e536e9e4c4ee70", 0, $(FRAMERATE), $(XSIZE), $(YSIZE), 0, 0, 0, 0)
# If searching for device by serial number, the productID and deviceIndex arguments are ignored
#ADUVCConfig("$(PORT)", "10e536e9e4c4ee70", 0, 0, $(XSIZE), $(YSIZE), 0, 0, 0, 0)
#epicsThreadSleep(2)

# If searching for device by product ID put "" or empty string for serial number
ADUVCConfig("$(PORT)", "", 25344, $(FRAMERATE), $(XSIZE), $(YSIZE), 0, 0, 0, 0)
# If searching for device by product ID put "" or empty string for serial number. DeviceIndex argument is ignored.
#ADUVCConfig("$(PORT)", "", 25344, 0, $(XSIZE), $(YSIZE), 0, 0, 0, 0)
#epicsThreadSleep(2)

# If opening device by index, simply set a an empty string for the serial, and a 0 for productID. An index of 0 will open the first UVC
# camera detected. See the output of the uvc_locater command for index values (first -> 0, second -> 1, etc)
ADUVCConfig("$(PORT)", "", 0, 0, $(XSIZE), $(YSIZE), 0, 0, 0, 0)
epicsThreadSleep(2)

asynSetTraceIOMask($(PORT), 0, 2)
Expand Down
1 change: 1 addition & 0 deletions uvcApp/Db/ADUVC.template
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
record(ao, "$(P)$(R)UVCFramerate"){
field(PINI, "YES")
field(DTYP, "asynInt32")
field(VAL, "30")
field(OUT, "@asyn($(PORT),$(ADDR),$(TIMEOUT))UVC_FRAMERATE")
}

Expand Down
29 changes: 24 additions & 5 deletions uvcApp/src/ADUVC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -418,11 +418,26 @@ asynStatus ADUVC::connectToDeviceUVC(){
"%s::%s Searching for UVC device with serial number: %s\n", driverName, functionName, serialNumber);
deviceStatus = uvc_find_device(pdeviceContext, &pdevice, 0, 0, this->serialNumber);
}
else{
else if(this->connectionType == 1){
asynPrint(this->pasynUserSelf, ASYN_TRACE_FLOW,
"%s::%s Searching for UVC device with Product ID: %d\n", driverName, functionName, this->productID);
deviceStatus = uvc_find_device(pdeviceContext, &pdevice, 0, this->productID, NULL);
}
else {
asynPrint(this->pasynUserSelf, ASYN_TRACE_FLOW,
"%s::%s Opening UVC device with index: %d\n", driverName, functionName, this->deviceIndex);
uvc_device_t** deviceList;
deviceStatus = uvc_get_device_list(pdeviceContext, &deviceList);
if(deviceList[this->deviceIndex] == NULL) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s::%s There are not enough UVC devices to open one with index %d!\n", driverName, functionName, this->deviceIndex);
deviceStatus = UVC_ERROR_NO_DEVICE;
}
else{
pdevice = deviceList[this->deviceIndex];
}
uvc_free_device_list(deviceList, 0);
}
if(deviceStatus<0){
reportUVCError(deviceStatus, functionName);
return asynError;
Expand Down Expand Up @@ -1550,14 +1565,14 @@ void ADUVC::report(FILE* fp, int details){
* @params[in]: portName -> port for NDArray recieved from camera
* @params[in]: serial -> serial number of device to connect to
* @params[in]: productID -> id of device used to connect if serial is unavailable
* @params[in]: framerate -> framerate at which camera should operate
* @params[in]: deviceIndex -> If serial or product ID is not used, use device index in usb device list to open
* @params[in]: maxBuffers -> max buffer size for NDArrays
* @params[in]: maxMemory -> maximum memory allocated for driver
* @params[in]: priority -> what thread priority this driver will execute with
* @params[in]: stackSize -> size of the driver on the stack
*/
ADUVC::ADUVC(const char* portName, const char* serial, int productID,
int framerate, int xsize, int ysize, int maxBuffers,
int deviceIndex, int xsize, int ysize, int maxBuffers,
size_t maxMemory, int priority, int stackSize)
: ADDriver(portName, 1, (int)NUM_UVC_PARAMS, maxBuffers,
maxMemory, asynEnumMask, asynEnumMask,
Expand Down Expand Up @@ -1594,7 +1609,7 @@ ADUVC::ADUVC(const char* portName, const char* serial, int productID,


// Set initial size and framerate params
setIntegerParam(ADUVC_Framerate, framerate);
//setIntegerParam(ADUVC_Framerate, 30);
setIntegerParam(ADSizeX, xsize);
setIntegerParam(ADSizeY, ysize);

Expand Down Expand Up @@ -1626,14 +1641,18 @@ ADUVC::ADUVC(const char* portName, const char* serial, int productID,

this->serialNumber = serial;
this->productID = productID;
this->deviceIndex = deviceIndex;

// decide if connecting with serial number or productID
if(strlen(serial) != 0){
this->connectionType = 0;
}
else{
else if(productID != 0){
this->connectionType = 1;
}
else{
this->connectionType = 2;
}

connected = connect(this->pasynUserSelf);

Expand Down
1 change: 1 addition & 0 deletions uvcApp/src/ADUVC.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ class ADUVC : ADDriver{
int connectionType;
int productID;
const char* serialNumber;
int deviceIndex;

// Checks uvc device operations status
uvc_error_t deviceStatus;
Expand Down
2 changes: 1 addition & 1 deletion uvcSupport/cameraDetector/Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
all:
g++ -I../../include -L../../lib/linux-x86_64 uvc_locater.cpp -o uvc_locater ../../lib/linux-x86_64/libuvc.a -lusb-1.0 -lpthread
gcc -I../../include -L../../lib/linux-x86_64 uvc_locater.c -o uvc_locater ../../lib/linux-x86_64/libuvc.a -lusb-1.0 -lpthread
clean:
rm uvc_locater
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,8 @@
#include <stdlib.h>
#include "libuvc/libuvc.h"
#include <stdio.h>
#include <cstddef>
#include <string.h>

using namespace std;



Expand All @@ -30,6 +28,8 @@ void print_help(){
printf("NO_ARGS -> Gets a list of all devices, and some basic information, such as serial numbers.\n");
printf("-h or --help -> View this help messag.\n");
printf("-s or --serial + SERIAL_NUMBER -> To see more detailed information about a specific camera.\n");
printf("-p or --product + PRODUCT_ID -> To see more detailed information about a specific camera.\n");
printf("-c or -- concise -> To get a more machine readable concise output. Cannot be combined with -s or -p.\n");
printf("Check the README.md file in this directory for examples of all use cases.\n");
}

Expand All @@ -41,7 +41,7 @@ void print_help(){
*
* @return: status -> 0 if success, -1 if failure
*/
int list_all(){
int list_all(int concise){
uvc_context_t* ctx;
uvc_device_t** device_list;
uvc_device_descriptor_t* desc;
Expand All @@ -59,7 +59,8 @@ int list_all(){
return status;
}

puts("UVC initialized successfully");
if(concise == 0)
printf("UVC initialized successfully\n");

//generates list of available devices
status = uvc_get_device_list(ctx, &device_list);
Expand All @@ -77,14 +78,19 @@ int list_all(){
uvc_perror(status, "uvc_get_device_descriptor");
return status;
}
if (concise == 0){
printf("-------------------------------------------------------------\n");
printf("Serial Number: %s\n", desc->serialNumber);
printf("Vendor ID: 0x%x (%d)\n", desc->idVendor, desc->idVendor);
printf("ProductID: 0x%x (%d)\n", desc->idProduct, desc->idProduct);
printf("Vendor ID: 0x%x (%d)\n", desc->idVendor, desc->idVendor);
printf("Manufacturer: %s\n", desc->manufacturer);
printf("Product: %s\n", desc->product);
printf("UVC Compliance: %d\n", desc->bcdUVC);
uvc_free_device_descriptor(desc);
}
else{
printf("Camera%d:%s,0x%x,0x%x\n", counter, desc->serialNumber, desc->idProduct, desc->idVendor);
}
counter++;
}
}
Expand Down Expand Up @@ -152,7 +158,7 @@ int list_detailed_information(const char* serialNumber, int productID){
int main(int argc, char** argv){
//if no args, list all devices
if(argc == 1){
int status = list_all();
int status = list_all(0);
return status;
}
else{
Expand All @@ -166,6 +172,10 @@ int main(int argc, char** argv){
print_help();
return 0;
}
else if(strcmp(arg,"-c")== 0 || strcmp(arg, "--concise") == 0) {
int status = list_all(1);
return 0;
}
// otherwise if -s or --serial print detailed info
else if(strcmp(arg,"-s")==0 || strcmp(arg,"--serial")==0){
const char* serialNum = *(argv+arg_num+1);
Expand Down
59 changes: 59 additions & 0 deletions uvcSupport/generate_udev_rules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env python3


import argparse
import os
from subprocess import Popen, PIPE


def get_camera_list():
cameras = set()
try:
p = Popen(['cameraDetector/uvc_locater', '-c'], stdout=PIPE, stderr=PIPE)
out, err = p.communicate()
for line in out.decode('utf-8').splitlines():
cameras.add(line.split(',')[1])
except Exception as e:
print(f'ERROR - Failed to run uvc_locater! {str(e)}')
return
return cameras


def parse_args():

parser = argparse.ArgumentParser(description='Utility for generating udev rule file for specific server')
parser.add_argument('-i', '--install', action='store_true', help='Installs the udev file into /etc/udev/rules.d/###-usbcams.rules, or replaces it if it exists.')
return parser.parse_args()


if __name__ == '__main__':
args = parse_args()
cameras = get_camera_list()

udev_file_contents = ''
for camera in cameras:
udev_file_contents += f'SUBSYSTEM=="usb", ATTRS{{idProduct}}=="{camera.split("x")[1]}", MODE="0666"\n'

print(f'Generated UDEV rules:\n\n{udev_file_contents}')

if args.install:
try:
numbers = []
for file in os.listdir('/etc/udev/rules.d'):
numbers.append(file.split('-')[0])

udev_file_num = 0
for i in range(90, 100):
if i not in numbers or i == 99:
udev_file_num = i
break

if os.path.exists(f'/etc/udev/rules.d/{udev_file_num}-usbcams.rules'):
os.remove(f'/etc/udev/rules.d/{udev_file_num}-usbcams.rules')

with open(f'/etc/udev/rules.d/{udev_file_num}-usbcams.rules', 'w') as fp:
fp.write(udev_file_contents)
print(f'Successfully generated /etc/udev/rules.d/{udev_file_num}-usbcams.rules.')
print('Restart your machine or re-trigger udevadm and re-plug your devices for the rules to apply')
except PermissionError:
print('ERROR - You do not have permission to generate and install udev files!')

0 comments on commit 072123b

Please sign in to comment.