How to detect USB device disconnect under Linux/Qt/C++ - linux

I'm writing a system (X-Platform Windows/Linux) that talks to a custom device using an FTDI USB chip. I use their D2XX driver for device open/close/read/write. So far, so good.
I need to know when the device is disconnected so the program can respond gracefully. At present, under Windows the application receives a sudden unexpected close. Under Linux, when the device is disconnected, there is a sgementation fault.
I have found informaiton under Windows about listening for the WM_DEVICECHANGE message. However, I have not found how to detect this event under Windows. There is information for the device driver level interacting with the kernel. However, I can't figure out how to do this at an application level. The FTDI driver does not offer any such service.
The system is written using the Qt framework with C++. The device driver is FTDI's D2XX driver.
Can anyone point me in the right direction?
Thanks so much in advance!
Judy

You'll probably want to use HAL (freedesktop.org's Hardware Abstraction Layer).
In the future you will probably want to use DeviceKit. It is a project fix the many problems with HAL. It hasn't been adopted by all major distros yet though (I think just Fedora), so you probably don't want to use it right now.
Edit: As Jeach said, you can use udev also. I wouldn't suggest this, as it is much lower level, and harder to program, but if latency is very important, this might be the best option.

Although what I'm about to tell you won't directly answer your question, it may give you a hint as to your next move.
I use udev rules configured in '/etc/udev/rules.d/' which run various scripts. When a USB device gets connected/disconnected I run a script which sends a HUP signal to my binary. Since my requirements can handle a bit of lag it works perfectly fine for me.
But my point is that maybe there is a udev library you can link to and register events programmatically (instead of scripts).
Hope it helps... good luck!

I recently had a project which involved reading via an FTDI chip. I also tried using libftdi but found out that it is much simpler to use /dev/ttyUSB* for reading and writing. This way, you can use QFile('/dev/ttyUSB*') to write and read. You can also check if the device actually exists and it won't segfault. Of course, this is not a very 'Platform independent' way. To get a platform independent method, you can use a Serial library for Qt.

You obviously have to write different implementations for the different operating systems unless you want to create a thread to continuously run:
FT_ListDevices(&numDevs, nullptr, FT_LIST_NUMBER_ONLY);
and enumerate the devices if numDevs changed compared to previous checks.
If you are like me and don't really like to do that sort of continuous polling on your USB devices then you will have to target your specific operating system.
Here's a link to some sample code from FTDI:
http://www.ftdichip.com/Support/SoftwareExamples/CodeExamples/VC.htm
Example 7 shows how to detect the USB insertion and removal on windows:
http://www.ftdichip.com/Support/Documents/AppNotes/AN_152_Detecting_USB_%20Device_Insertion_and_Removal.pdf
On Linux I can personally recommend using udev.
This code is for enumerating the devices:
#include <sys/types.h>
#include <dirent.h>
#include <cstdlib>
#include <libudev.h>
#include <fcntl.h>
struct udev *udev = udev_new();
if (!udev) {
cout << "Can't create udev" <<endl;
}
struct udev_enumerate *enumerate = udev_enumerate_new(udev);
udev_enumerate_add_match_subsystem(enumerate, "usb");
udev_enumerate_scan_devices(enumerate);
struct udev_list_entry *dev_list_entry, *devices = udev_enumerate_get_list_entry(enumerate);
struct udev_device *dev;
udev_list_entry_foreach(dev_list_entry, devices) {
const char *path;
path = udev_list_entry_get_name(dev_list_entry);
dev = udev_device_new_from_syspath(udev, path);
if( udev_device_get_devnode(dev) != nullptr ){
string vendor = (std::string) udev_device_get_sysattr_value(dev, "idVendor");
string product = (std::string) udev_device_get_sysattr_value(dev, "idProduct");
string description = (std::string)udev_device_get_sysattr_value(dev, "product");
cout << vendor << product << description << endl;
}
udev_device_unref(dev);
}
udev_enumerate_unref(enumerate);
This code I put in a separate thread that waits to receive an insertion or a removal event
struct udev_device *dev;
struct udev_monitor *mon = udev_monitor_new_from_netlink(udev, "udev");
udev_monitor_filter_add_match_subsystem_devtype(mon, "usb", NULL);
udev_monitor_enable_receiving(mon);
int fd = udev_monitor_get_fd(mon);
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1){
debugError("Can't get flags for fd");
}
flags &= ~O_NONBLOCK;
fcntl(fd, F_SETFL, flags);
fd_set fds;
FD_ZERO(&fds);
FD_SET(fd, &fds);
while( _running ){
cout << "waiting for udev" << endl;
dev = udev_monitor_receive_device(mon);
if (dev && udev_device_get_devnode(dev) != nullptr ) {
string action = (std::string)udev_device_get_action(dev);
if( action == "add" ){
cout << "do something with your device... " << endl;
} else {
string path = (std::string)udev_device_get_devnode(dev);
for( auto device : _DevicesList ){
if( device.getPath() == path ){
_DevicesList.erase(iter);
cout << "Erased Device from list" << endl;
break;
}
}
}
udev_device_unref(dev);
}
}
udev_monitor_unref(mon);
some of the functions and variables are obviously not defined when you copy/paste this code.
I keep a list of detected devices to check the path and other information like the location ID of the inserted device. I need the location ID later to FT_OpenEx via FT_OPEN_BY_LOCATION. To get the location id I read the contents of the following files:
string getFileContent(string file ){
string content = "";
ifstream readfile( file );
if( readfile.is_open() ){
getline(readfile, content );
readfile.close();
}
return content;
}
string usbdirectory = "/sys/bus/usb/devices";
string dev1content = getFileContent(usbdirectory+"/usb"+udev_device_get_sysattr_value(dev, "busnum" )+"/dev");
int dev1num = std::atoi(dev1content.substr(dev1content.find_first_of(":")+1).c_str());
string dev2content = (std::string)udev_device_get_sysattr_value(dev, "dev");
int dev2num = std::atoi(dev2content.substr(dev2content.find_first_of(":")+1).c_str());
int locationid = dev1num+dev2num+257;
I can't guarantee that the locationid is correct but it seemed to work for me until now.

Don't forget that you have two problems here :
Detecting device insertion / removal
Properly terminating your application.
The first problem has been adressed by Zifre.
But the second problem remains : your Linux app should not be segfaulting when the device is removed, and I think the two problems are unrelated : if the device is removed in the middle of a write or read system call, then those system call will return with an error before you get any notification, and this should not segfault your app.

Related

Talking to a power supply via mac os x using a usb-serial converter

I am trying to talk to a power supply from my mac OS X Yosemite. The code works fine on a linux machine, but when I try it on my mac, it does not work. I am using a usb-serial converter and have downloaded the PL-2303 driver. The driver shows up in my /dev folder as cu.usbserial and tty.usbserial.
The part of my code that fails:
fd = initserial("/dev/cu.usbserial");
int initserial(char port[])
{
struct termios shimtermios;
int fd;
if((fd=open(port,O_RDWR)) < 0) {
perror("Opening the RS-232 port failed for initserial\n");
exit(-1);
}
if (tcgetattr(fd, &shimtermios) < 0) {
perror("couldn't get terminal attributes\n");
exit(-2);
}
shimtermios.c_iflag=012005;
shimtermios.c_oflag=014004;
shimtermios.c_cflag=03206276;
shimtermios.c_lflag=05060;
cfsetospeed(&shimtermios,B19200);
if (tcsetattr(fd, TCSAFLUSH, &shimtermios) < 0) {
perror("couldn't set terminal attributes\n");
exit(-3);
}
return (fd);
}
I get the following error message
couldn't set terminal attributes
: Invalid argument
Please let me know if you have any experience with this linux/unix issue. Thank you so much!
See the termios man page for the valid masks that can be used for c_iflag, c_oflag, c_cflag, and c_lflag). You should OR the mask constants together rather than hard-coding the values.
Also from experience, FTDI USB-serial converters tend to work better on OS X than pl2303. OS X has a built-in driver for these (AppleUSBFTDI).

Remove input driver bound to the HID interface

I'm playing with some driver code for a special kind of keyboard. And this keyboard does have special modes. According to the specification those modes could only be enabled by sending and getting feature reports.
I'm using 'hid.c' file and user mode to send HID reports. But both 'hid_read' and 'hid_get_feature_report' failed with error number -1.
I already tried detaching keyboard from kernel drivers using libusb, but when I do that, 'hid_open' fails. I guess this is due to that HID interface already using by 'input' or some driver by the kernel. So I may not need to unbind kernel hidraw driver, instead I should try unbinding the keyboard ('input') driver top of 'hidraw' driver. Am I correct?
And any idea how I could do that? And how to find what are drivers using which drivers and which low level driver bind to which driver?
I found the answer to this myself.
The answer is to dig this project and find it's hid implementation on libusb.
Or you could directly receive the report.
int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length)
{
int res = -1;
int skipped_report_id = 0;
int report_number = data[0];
if (report_number == 0x0) {
/* Offset the return buffer by 1, so that the report ID
will remain in byte 0. */
data++;
length--;
skipped_report_id = 1;
}
res = libusb_control_transfer(dev->device_handle,
LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_IN,
0x01/*HID get_report*/,
(3/*HID feature*/ << 8) | report_number,
dev->interface,
(unsigned char *)data, length,
1000/*timeout millis*/);
if (res < 0)
return -1;
if (skipped_report_id)
res++;
return res;
}
I'm sorry I can't post my actual code due to some legal reasons. However the above code is from hidapi implementation.
So even you work with an old kernel , you still have the chance to make your driver working.
This answers to this question too: https://stackoverflow.com/questions/30565999/kernel-version-2-6-32-does-not-support-hidiocgfeature

How to control hard drive motor speed with C on linux?

I've got an old HDD with which I planned to fiddle around a little. First thing I'm trying to do is spinning the motor with different speeds.
Questions are:
Is there a general way to do this or does it depend on the HDD model?
Where do I find a list of commands, that I can send to the HDD controller to control the speed of the motor?
I actually found a function, that apparently spins down the motor, here it is:
/* spin-down a disk */
static void spindown_disk(const char *name)
{
struct sg_io_hdr io_hdr;
unsigned char sense_buf[255];
char dev_name[100];
int fd;
dprintf("spindown: %s\n", name);
/* fabricate SCSI IO request */
memset(&io_hdr, 0x00, sizeof(io_hdr));
io_hdr.interface_id = 'S';
io_hdr.dxfer_direction = SG_DXFER_NONE;
/* SCSI stop unit command */
io_hdr.cmdp = (unsigned char *) "\x1b\x00\x00\x00\x00\x00";
io_hdr.cmd_len = 6;
io_hdr.sbp = sense_buf;
io_hdr.mx_sb_len = (unsigned char) sizeof(sense_buf);
/* open disk device (kernel 2.4 will probably need "sg" names here) */
snprintf(dev_name, sizeof(dev_name), "/dev/%s", name);
if ((fd = open(dev_name, O_RDONLY)) < 0) {
perror(dev_name);
return;
}
/* execute SCSI request */
if (ioctl(fd, SG_IO, &io_hdr) < 0) {
char buf[100];
snprintf(buf, sizeof(buf), "ioctl on %s:", name);
perror(buf);
} else if (io_hdr.masked_status != 0) {
fprintf(stderr, "error: SCSI command failed with status 0x%02x\n",
io_hdr.masked_status);
if (io_hdr.masked_status == CHECK_CONDITION) {
phex(sense_buf, io_hdr.sb_len_wr, "sense buffer:\n");
}
}
close(fd);
}
Though I don't really understand where the actual command is sent to the controller, nor do I know how to control the speed, I don't see any rpm specifications.
You cannot control a harddisk's rotational speed, and that is a good thing. If you could, you would inevitably destroy data.
The heads float in what is commonly called "air bearing".
This is, in easy words, a spring mechanism pressing the head onto the disks's surface with a well-defined force and an air cussion from airflow due to the disk's rotation being in equilibrium at the disk's operational speed. When the disk is shut down, another spring mechanisms quickly pulls the heads out of the way into a kind of "parking position".
If you could run the drive at arbitrary speeds, the heads would scratch on the surface. Not good!
As to where the actual command is being sent in above snippet, it is the ioctl call in the line following /* execute SCSI request */.
If you are interested in playing with your old harddisk (be aware that you'll quite likely break it!), have a look at the hdparm tool and its source code. hdparm lets you tweak dozens of parameters such as power save modes, caching, or acustic management... pretty much everything that disk drives support.
In the tool's source code, you'll find a quite complete list of device commands, too.

Linux - How to program for a touchscreen outside of X Windows

I have a small TFT with touch control connected to a Raspberry Pi. The touchscreen works well within X windows.
I would like to be able to use the touchscreen outside of X windows.
Something simple, like two buttons on the screen.
I have experience with C and writing to the framebuffer using SDL. Or directly to memory.
I have no idea how to detect the input of the touchscreen and I am hoping some one could point me in the right direction.
I see the touchscreen as /dev/input/event0
It seems that you are just seeing a regular event device. What have you done so far? You might try for example Using the Input Subsystem article on Linux Journal.
What you should try at first should probably be:
/* how many bytes were read */
size_t rb;
/* the events (up to 64 at once) */
struct input_event ev[64];
rb=read(fd,ev,sizeof(struct input_event)*64);
if (rb < (int) sizeof(struct input_event)) {
perror("evtest: short read");
exit (1);
}
for (yalv = 0;
yalv < (int) (rb / sizeof(struct input_event));
yalv++)
{
//if (EV_KEY == ev[yalv].type)
printf("%ld.%06ld ",
ev[yalv].time.tv_sec,
ev[yalv].time.tv_usec,
printf("type %d code %d value %d\n",
ev[yalv].type,
ev[yalv].code, ev[yalv].value);
}
Then you should pay attention, what event types are being emitted, and then work with them further.

How to read from a Linux serial port

I am working on robot which has to control using wireless serial communication. The robot is running on a microcontroller (by burning a .hex file). I want to control it using my Linux (Ubuntu) PC. I am new to serial port programming. I am able to send the data, but I am not able to read data.
A few piece of code which is running over at the microcontroller:
Function to send data:
void TxData(unsigned char tx_data)
{
SBUF = tx_data; // Transmit data that is passed to this function
while(TI == 0) // Wait while data is being transmitted
;
}
I am sending data through an array of characters data_array[i]:
for (i=4; i<=6; i++)
{
TxData(data_array[i]);
RI = 0; // Clear receive interrupt. Must be cleared by the user.
TI = 0; // Clear transmit interrupt. Must be cleared by the user.
}
Now the piece of code from the C program running on Linux...
while (flag == 0) {
int res = read(fd, buf, 255);
buf[res] = 0; /* Set end of string, so we can printf */
printf(":%s:%d\n", buf, res);
if (buf[0] == '\0')
flag = 1;
}
It prints out value of res = 0.
Actually I want to read data character-by-character to perform calculations and take further decision. Is there another way of doing this?
Note: Is there good study material (code) for serial port programming on Linux?
How can I read from the Linux serial port...
This is a good guide: Serial Programming Guide for POSIX Operating Systems
The read call may return with no data and errno set to EAGAIN. You need to check the return value and loop around to read again if you're expecting data to arrive.
First, take a look at /proc/tty/driver/serial to see that everything is set up correctly (i.e., you see the signals you should see). Then, have a look at the manual page for termios(3), you may be interested in the VMIN and VTIME explanation.

Resources