Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

USB detection #2338

Open
JonathanWoodward opened this issue Nov 1, 2021 · 5 comments
Open

USB detection #2338

JonathanWoodward opened this issue Nov 1, 2021 · 5 comments
Labels
feature-request Feature or Enhancement

Comments

@JonathanWoodward
Copy link

JonathanWoodward commented Nov 1, 2021

@reconbot @GazHank

💥 Proposal

What feature you'd like to see

I would like the ability to detect USB serial ports being added and removed from the computer. Like most USB to serial devices such as FTDI you can set personalised USB VID's and PID's which are used to identify peoples bespoke hardware.

Motivation

To enable people to event driven detect their devices upon being plugged in to the system, open a serial port and start processing data.

Pitch

The concept would you you can initialise the monitoring of a custom VID and PID and it would return the data relevant to opening a serial port. In some instances it might not be a COM1 or ttyUSB0 but a FDTI USB driver.

Device *device = cDevice_create(uv_loop, impl);
device->add(device, VID1, PID1, added_device, removed_device);
device->add(device, VID1, PID2, added_device, removed_device);
device->search(device);

void added_device(Device *device, DeviceNode *node, const char *dev_name)
{
// open serial port
}

void removed_device(Device *device, DeviceNode *node, const char *dev_name)
{
// removal of resources
}
`

Linux can achieve this functionality using libudev:
`
static void Device_findUdevDevices(Device *self, struct udev *udev)
{
    const char *path;
    struct udev_list_entry *devices, *dev_list_entry;
    struct udev_device *dev;
    struct udev_enumerate *enumerate = udev_enumerate_new(udev);
    udev_enumerate_add_match_subsystem(enumerate, "tty");
    udev_enumerate_scan_devices(enumerate);
    devices = udev_enumerate_get_list_entry(enumerate);
	udev_list_entry_foreach(dev_list_entry, devices) {
		path = udev_list_entry_get_name(dev_list_entry);
		dev = udev_device_new_from_syspath(udev, path);	
		if(dev) {
			Device_udevProcessDevice(self, dev, "add");
			udev_device_unref(dev);
		}	
	}
    udev_enumerate_unref(enumerate);
}

static void Device_watcher_cb(uv_poll_t* watcher, int status, int revents)
{
    Device *self = (Device *)watcher->data;
    struct udev_monitor *udev_monitor = (struct udev_monitor *)self->udev_monitor;
    struct udev_device* dev = udev_monitor_receive_device(udev_monitor);
    cDevice_udevProcessDevice(self, dev, udev_device_get_action(dev));
    udev_device_unref(dev);
}
`

Windows is a bit more of a pain:

You need to create a discovery thread 
`
thread = CreateThread(NULL, 0, Device_win_discoveryThread, self, 0, NULL);

static DWORD WINAPI cDevice_win_discoveryThread(void *thread_data)
{
        Device *self = (Device *)thread_data;

	// Initialize and register the window class
	WNDCLASSEX wndClass;
	wndClass.cbSize = sizeof(WNDCLASSEX);
	wndClass.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
	wndClass.hInstance = (HINSTANCE)GetModuleHandle(NULL);
	wndClass.lpfnWndProc = (WNDPROC)Device_win_trampoline;
	wndClass.cbClsExtra = 0;
	wndClass.cbWndExtra = 0;
	wndClass.hIcon = LoadIcon(0, IDI_APPLICATION);
	wndClass.hbrBackground = NULL; 
	wndClass.hCursor = LoadCursor(0, IDC_ARROW);
	wndClass.lpszClassName = TEXT("cDeviceClass");
	wndClass.lpszMenuName = NULL;
	wndClass.hIconSm = wndClass.hIcon;

	RegisterClassEx(&wndClass);

	// Create window
	HWND hWnd = CreateWindowEx(WS_EX_CLIENTEDGE | WS_EX_APPWINDOW, 
		wndClass.lpszClassName, 
		TEXT("DeviceFinder"), 
		WS_OVERLAPPEDWINDOW, 
		0, 0, 
		0, 0, 
		NULL, NULL, 
		(HINSTANCE)GetModuleHandle(NULL),
               self
	);
	self->hwnd = (void *)hWnd;

	ShowWindow(hWnd, SW_HIDE);
	UpdateWindow(hWnd);

	// Start a timer
	self->timer_count = 0;
	SetTimer(hWnd, TIMER_ID, TIMER_INTERVAL, NULL);

	// Message pump loops until the window is destroyed
	MSG msg;
	int retVal;
	while (0 != (retVal = GetMessage(&msg, NULL, 0, 0)))
	{
		if (retVal == -1) break;
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	KillTimer(hWnd, TIMER_ID);
	self->hwnd = NULL;
	return 1;
}

On Darwin I have not yet worked on the solution. If the project goes ahead I am happy to contribute to it.

Regards
J
cDevice.zip

@reconbot
Copy link
Member

reconbot commented Nov 1, 2021

This is a very common request for ports. Listing ports over and over is a poor solution when we can have usb auto detection. The complixity is around cross platform support and which subsystem handles the port. Old com port or tty interfaces don't have this concept so we'd have to look at the usb interfaces and correlate between them. We do have some code that does this in windows (which seems to use old registry entries), but not so much in linux or osx iirc.

@GazHank GazHank added the feature-request Feature or Enhancement label Nov 1, 2021
@GazHank
Copy link
Contributor

GazHank commented Nov 1, 2021

On the Darwin point I wonder if the new driverkit will make this any easier

@currentoor
Copy link

This package does it correctly emit events when I plug in my device but for some reason, I had to add a timeout otherwise serialport wouldn't be able to connect.

https://github.com/MadLittleMods/node-usb-detection

@JonathanWoodward
Copy link
Author

Yes, this is what I did on my version.

static int cDevice_win_proc(cDevice* self, HWND hWnd, unsigned int message, unsigned int wParam, LPARAM lParam) { LRESULT lRet = 1; switch (message) { case WM_CREATE: cDevice_win_registerDeviceToHwnd(WceusbshGUID, hWnd, &self->hDeviceNotify); break; case WM_DEVICECHANGE: { self->timer_count = TIMER_INTERVAL * 2; break; } case WM_CLOSE: if (self->hDeviceNotify != NULL) { UnregisterDeviceNotification((HDEVNOTIFY)self->hDeviceNotify); } DestroyWindow(hWnd); break; case WM_DESTROY: PostQuitMessage(0); break; case WM_TIMER: if(wParam == TIMER_ID) { if(self->timer_count > 0) { if(self->timer_count > TIMER_INTERVAL) { self->timer_count -= TIMER_INTERVAL; } else { self->timer_count = 0; uv_async_send(&win_async_cDevice); //cDevice_win_scanAllDevices(self); } } } break; default: lRet = DefWindowProc(hWnd, message, wParam, lParam); break; } return lRet; }

Thanks for pointing out that lib, I don't think it was public when I started my project 2 years ago.

@JonathanWoodward
Copy link
Author

The other thing I found is you have to detect the serial port from the Windows ports class as followed:

if (SetupDiClassGuidsFromNameW(L"Ports", &pGuids, 1, &portGuids)) { cDevice_win_compare(self, &pGuids, portGuids, 1); }

Then you can match "USB\VID_%04X&PID_%04X" on the usbid:

if (!SetupDiEnumDeviceInfo(hDevInfo, index, &devInfo)) { break; } CM_Get_Device_IDW(devInfo.DevInst, scratch, 256, 0); wcstombs_s(NULL, usbId, 256, scratch, 256);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature-request Feature or Enhancement
Development

No branches or pull requests

4 participants