Closed helarsen closed 3 years ago
Yes, currently the script only returns one camera: https://github.com/LJMUAstroecology/flirpy/blob/master/find_cameras/find_cameras/find_cameras.cpp
Just have it print out every id
rather than just the first. Feel free to submit a pull request! Note that while it's easy to do this, it is not trivial to associate camera IDs with their correct serial ports (and this should be done properly so that you can query each camera for internal info/control settings).
This is a duplicate issue and therefore I'm closing this one: https://github.com/LJMUAstroecology/flirpy/issues/24
thanks. And sorry I should have seen #24. I will investigate if I can come up with a solution. The enum com devices is easy - done that already. The cross linking to video device is the hard part.
Yeah, I would suggest just returning a vector of IDs and then printing them comma separated (or something similar). Unfortunately I no longer have multiple Bosons to test with otherwise I'd have a go - though I can update the code and if you're willing to test it we can work together on it. The difficult bit is then matching the serials, but I think that should be possible.
I am not much familier with this workflow. The code below is tested and will return a list of COM ports and the Boson serial number. The list could also be returned as [["COM35", 12456], ["COM8",23456]] to keep items as a camera property. Anyways - this code does not break much ground. I would be happy to assist and can test with at least 2 Bosons. henning
@classmethod def get_serial_device_list(self): """ Attempts to find the serial ports that has a Boson connection and returns a list of COM port and serial number Returns
list of string serial port name list of int Boson serial number """ device_list = list_ports.comports() device_list = sorted(device_list, key=lambda dev: dev.device) # in case os uses random order. VID = 0x09CB PID = 0x4007 ports = [ device.device for device in device_list if device.vid == VID and device.pid == PID] sns = [ int(device.serial_number) for device in device_list if device.vid == VID and device.pid == PID] return ports, sns
So if you can extract the serial number from DirectShow as well, then that should give you the correct device. Could you check whether the serial number you can get from the device matches any of the properties listed by Windows (any unique matching parameter would do actually)? You can check this by going to Device Manager and checking the Details tab for the camera there (you should be able to see e.g. the PID, VID and friendly name, etc).
One device seen by device manager:
Second device seen by device manager:
So there is a common value seen in both views. One can get this value via COM listing in python (code above with minor modification): for COM8: location = '1-1.2:x.2' or hwid='USB VID:PID=09CB:4007 SER=85786 LOCATION=1-1.2:x.2' for COM35 location = '1-1.3.2:x.2' hwid='USB VID:PID=09CB:4007 SER=53112 LOCATION=1-1.3.2:x.2'
Although a different format it is clearly the same parameter and thus a link between the two views. BUT How to get the video location information programmatically?
You should be able to query that using the cpp script, in the same way we check for the "friendly name" e.g.
hr = pPropBag->Read(L"Location information", &var, 0);
and then convert it to a string and return it with the id.
Unfortunately pPropBag interface calls
hr = pPropBag->Read(L"Location Information", &var, 0); hr = pPropBag->Read(L"Location information", &var, 0); hr = pPropBag->Read(L"location", &var, 0); hr = pPropBag->Read(L"Location", &var, 0);
does not return any value to var. But
hr = pPropBag->Read(L"DevicePath", &var, 0);
returns
var= BSTR = 0x01203734 L"\\?\usb#vid_09cb&pid_4007&mi_00#d&2b4821fa&0&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global"
and for the second camera
var= BSTR = 0x01203734 L"\\?\usb#vid_09cb&pid_4007&mi_00#3&26014a8a&0&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global"
As seen slightly different but does not correlate well to what serial port enumeration gives. So back to the guesswork - I wish I could find documentation for this horrible pPropBag interface
Yeah, I've not been able to find a way of enumerating valid keys for an IPropertyBag. This might be one for Stack Overflow to answer. I'm not super familiar with COM programming.
https://docs.microsoft.com/en-us/windows/win32/directshow/selecting-a-capture-device says
so basically there are only Firendlyname and devicePath to read that way. DevicePath is unique (example is in my previous post) but it does not help here due to lack of visibility via the serial port.
Tried to switch the device into ramp mode and hoped that way to generate an image which is easy to identify. This has for the moment stranded on the fact that the test ramps I get are not fixed patterns and are not reproducible which makes it hard to use. I am however confident that it can be done that way. Problem with test ramp is that it is (also) poorly documented and there are a surprisingly many types of ramps not only patterns but also the location they are at. Does anyone reading here have experience with setting test ramps?
This example compiles fine for me on Windows 10 and does print out the location correctly. https://stackoverflow.com/questions/3438366/setupdigetdeviceproperty-usage-example
We should be able to do it by matching up the device paths, which is a bit messy but I can't see another obvious way.
Here is a complete example. I split the Device Path to get the unique ID for the camera, and then query the device list to get the location string. You should then be able to match that up to the COM port in PySerial.
// find_cameras.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <windows.h>
#include <dshow.h>
#include <string>
#include <vector>
#include <iostream>
#include <sstream>
#include <cctype>
#include <algorithm>
#pragma comment(lib, "strmiids")
#include <devguid.h> // for GUID_DEVCLASS_CDROM etc
#include <setupapi.h>
#include <cfgmgr32.h> // for MAX_DEVICE_ID_LEN, CM_Get_Parent and CM_Get_Device_ID
#define INITGUID
#include <tchar.h>
#include <stdio.h>
//#include "c:\WinDDK\7600.16385.1\inc\api\devpkey.h"
// include DEVPKEY_Device_BusReportedDeviceDesc from WinDDK\7600.16385.1\inc\api\devpropdef.h
#ifdef DEFINE_DEVPROPKEY
#undef DEFINE_DEVPROPKEY
#endif
#ifdef INITGUID
#define DEFINE_DEVPROPKEY(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8, pid) EXTERN_C const DEVPROPKEY DECLSPEC_SELECTANY name = { { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }, pid }
#else
#define DEFINE_DEVPROPKEY(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8, pid) EXTERN_C const DEVPROPKEY name
#endif // INITGUID
// include DEVPKEY_Device_BusReportedDeviceDesc from WinDDK\7600.16385.1\inc\api\devpkey.h
DEFINE_DEVPROPKEY(DEVPKEY_Device_BusReportedDeviceDesc, 0x540b947e, 0x8b40, 0x45bc, 0xa8, 0xa2, 0x6a, 0x0b, 0x89, 0x4c, 0xbd, 0xa2, 4); // DEVPROP_TYPE_STRING
DEFINE_DEVPROPKEY(DEVPKEY_Device_ContainerId, 0x8c7ed206, 0x3f8a, 0x4827, 0xb3, 0xab, 0xae, 0x9e, 0x1f, 0xae, 0xfc, 0x6c, 2); // DEVPROP_TYPE_GUID
DEFINE_DEVPROPKEY(DEVPKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING
DEFINE_DEVPROPKEY(DEVPKEY_DeviceDisplay_Category, 0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57, 0x5a); // DEVPROP_TYPE_STRING_LIST
DEFINE_DEVPROPKEY(DEVPKEY_Device_LocationInfo, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 15); // DEVPROP_TYPE_STRING
DEFINE_DEVPROPKEY(DEVPKEY_Device_Manufacturer, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 13); // DEVPROP_TYPE_STRING
DEFINE_DEVPROPKEY(DEVPKEY_Device_SecuritySDS, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 26); // DEVPROP_TYPE_SECURITY_DESCRIPTOR_STRING
#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
#pragma comment (lib, "setupapi.lib")
typedef BOOL(WINAPI* FN_SetupDiGetDevicePropertyW)(
__in HDEVINFO DeviceInfoSet,
__in PSP_DEVINFO_DATA DeviceInfoData,
__in const DEVPROPKEY* PropertyKey,
__out DEVPROPTYPE* PropertyType,
__out_opt PBYTE PropertyBuffer,
__in DWORD PropertyBufferSize,
__out_opt PDWORD RequiredSize,
__in DWORD Flags
);
// List all USB devices with some additional information
void ListDeviceLocation(CONST GUID* pClassGuid, LPCTSTR pszEnumerator, std::wstring devicePath)
{
unsigned i;
DWORD dwSize;
DEVPROPTYPE ulPropertyType;
CONFIGRET status;
HDEVINFO hDevInfo;
SP_DEVINFO_DATA DeviceInfoData;
const static LPCTSTR arPrefix[3] = { TEXT("VID_"), TEXT("PID_"), TEXT("MI_") };
TCHAR szDeviceInstanceID[MAX_DEVICE_ID_LEN];
WCHAR szBuffer[4096];
FN_SetupDiGetDevicePropertyW fn_SetupDiGetDevicePropertyW = (FN_SetupDiGetDevicePropertyW)
GetProcAddress(GetModuleHandle(TEXT("Setupapi.dll")), "SetupDiGetDevicePropertyW");
// List all connected USB devices
hDevInfo = SetupDiGetClassDevs(&GUID_DEVCLASS_CAMERA, pszEnumerator, NULL, DIGCF_PRESENT);
if (hDevInfo == INVALID_HANDLE_VALUE) {
std::cout << "Invalid handle";
return;
}
// Find the ones that are driverless
for (i = 0; ; i++) {
DeviceInfoData.cbSize = sizeof(DeviceInfoData);
if (!SetupDiEnumDeviceInfo(hDevInfo, i, &DeviceInfoData)) {
break;
}
status = CM_Get_Device_ID(DeviceInfoData.DevInst, szDeviceInstanceID, MAX_PATH, 0);
if (status != CR_SUCCESS) {
std::cout << "Failed to get Device ID";
continue;
}
// Check for correct device
std::size_t found = std::wstring(szDeviceInstanceID).find(devicePath);
if (found == std::string::npos) {
continue;
}
// Retreive the device description as reported by the device itself
// On Vista and earlier, we can use only SPDRP_DEVICEDESC
// On Windows 7, the information we want ("Bus reported device description") is
// accessed through DEVPKEY_Device_BusReportedDeviceDesc
if (fn_SetupDiGetDevicePropertyW && fn_SetupDiGetDevicePropertyW(hDevInfo, &DeviceInfoData, &DEVPKEY_Device_BusReportedDeviceDesc,
&ulPropertyType, (BYTE*)szBuffer, sizeof(szBuffer), &dwSize, 0)) {
if (fn_SetupDiGetDevicePropertyW(hDevInfo, &DeviceInfoData, &DEVPKEY_Device_LocationInfo,
&ulPropertyType, (BYTE*)szBuffer, sizeof(szBuffer), &dwSize, 0)) {
_tprintf(TEXT("Location: \"%ls\"\n"), szBuffer);
}
}
}
return;
}
inline std::wstring convert(const std::string& as)
{
// deal with trivial case of empty string
if (as.empty()) return std::wstring();
// determine required length of new string
size_t reqLength = ::MultiByteToWideChar(CP_UTF8, 0, as.c_str(), (int)as.length(), 0, 0);
// construct new string of required length
std::wstring ret(reqLength, L'\0');
// convert old string to new string
::MultiByteToWideChar(CP_UTF8, 0, as.c_str(), (int)as.length(), &ret[0], (int)ret.length());
// return new string ( compiler should optimize this away )
return ret;
}
HRESULT EnumerateDevices(REFGUID category, IEnumMoniker **ppEnum)
{
// Create the System Device Enumerator.
ICreateDevEnum *pDevEnum;
HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL,
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pDevEnum));
if (SUCCEEDED(hr))
{
// Create an enumerator for the category.
hr = pDevEnum->CreateClassEnumerator(category, ppEnum, 0);
if (hr == S_FALSE)
{
hr = VFW_E_NOT_FOUND; // The category is empty. Treat as an error.
}
pDevEnum->Release();
}
return hr;
}
std::vector<std::wstring> split(std::wstring input, wchar_t delim) {
// Vector of string to save tokens
std::vector <std::wstring> tokens;
std::wstring intermediate;
std::wstringstream stream;
stream << input;
// Tokenizing w.r.t. hash
while (std::getline(stream, intermediate, delim))
{
std::transform(intermediate.begin(), intermediate.end(), intermediate.begin(), ::toupper);
tokens.push_back(intermediate);
}
return tokens;
}
int get_id(IEnumMoniker *pEnum, std::string friendly_name)
{
IMoniker *pMoniker = NULL;
int i = 0;
while (pEnum->Next(1, &pMoniker, NULL) == S_OK)
{
IPropertyBag *pPropBag;
HRESULT hr = pMoniker->BindToStorage(0, 0, IID_PPV_ARGS(&pPropBag));
if (FAILED(hr))
{
pMoniker->Release();
continue;
}
VARIANT var;
VariantInit(&var);
// Get description or friendly name.
hr = pPropBag->Read(L"Description", &var, 0);
if (FAILED(hr))
{
hr = pPropBag->Read(L"FriendlyName", &var, 0);
}
if (SUCCEEDED(hr))
{
std::wstring str(var.bstrVal);
std::size_t found = str.find(convert(friendly_name));
if (found != std::string::npos) {
hr = pPropBag->Read(L"DevicePath", &var, 0);
if (SUCCEEDED(hr)){
auto path_tokens = split(std::wstring(var.bstrVal), L'#');
ListDeviceLocation(&GUID_DEVCLASS_CAMERA, NULL, path_tokens.at(2));
}
return i;
}
VariantClear(&var);
}
i++;
pPropBag->Release();
pMoniker->Release();
}
return -1;
}
int find_device(std::string friendly_name) {
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
int id = -1;
if (SUCCEEDED(hr))
{
IEnumMoniker *pEnum;
hr = EnumerateDevices(CLSID_VideoInputDeviceCategory, &pEnum);
if (SUCCEEDED(hr))
{
id = get_id(pEnum, friendly_name);
pEnum->Release();
}
CoUninitialize();
}
return id;
}
int main(int argc, char** argv)
{
if (argc >= 2) {
int devid = find_device(argv[1]);
std::cout << devid;
}
return 0;
}
Example output:
(base) PS C:\Users\Josh\code\flirpy\find_cameras\x64\Debug> .\find_cameras.exe "Integrated Camera"
Location: "0000.0014.0000.008.000.000.000.000.000"
0
Great work! I think its almost in the PropBag!
This is what I get looping for all flir:
and it matches what device manager show. Remains to correlate 0009.0000.0000.001.002.000.000.000.000 with location = '1-1.2:x.2' 0009.0000.0000.001.003.002.000.000.000 with location = '1-1.3.2:x.2' because the COM query gives for COM8: location = '1-1.2:x.2' or hwid='USB VID:PID=09CB:4007 SER=85786 LOCATION=1-1.2:x.2' for COM35 location = '1-1.3.2:x.2' hwid='USB VID:PID=09CB:4007 SER=53112 LOCATION=1-1.3.2:x.2'
Fortunately this can and should be done in python. Guessing that it is a matter of comparing the fields in bold and setting .000 = "" and hoping the format is more or less fixed.
Did you try removing spaces with the query string? The following works for me with an integrated camera:
hr = pPropBag->Read(L"LocationInformation", &var, 0);
no did not work in this context
Did you try removing spaces with the query string? The following works for me with an integrated camera:
hr = pPropBag->Read(L"LocationInformation", &var, 0);
no did not work in this context
Yeah I wondered if that was an easy fix, but then I realised I hadn't checked the output was actually a success and I was getting random strings every time.
For now let's assume that it's those three fixed fields. If someone else has an issue with it, then we can worry about it later!
I have implemented the algorithms to query the video and the serial port and match up giving a list of cameras:
[{"vid":1, "port":"COM3", "sn":1234}, {"vid":2, "port":"COM45", "sn":1256} ], is_race (note 1)
There remain a rather important work incorporating this into the original class. This require restructuring of the Boson class. This is a work which is best done by the main author as it affects the architecture.
One odd thing I see (I may say an asymmetry) with the current architecture is that when the class is created, only the com port is opened and not the associated video port. I think they should both be opened in the constructor so that the camera is ready for any type of operation (cap is initialized). With the new feature this should be possible to do in a consistent manner.
Also one should decide how to identify a camera to the constructor: By the vid, the port or maybe by the serial number. Again a fundamental change to the architecture.
(note 1) One note about the list of cameras: If there is a PnP event between the two type of device queries, the procedure will fail. The reason is that the vid always starts at the lowest value, so if we have vid 1 and 2 and 1 gets unplugged, the remaining will change to vid=1 To allow handling of this, the code is checking that the two queries have the same number of devices and if not the second argument returned is set to True meaning "RaceCondition"
Let me know any thoughts about further progress and suggestions.
I'll take a look at this properly shortly, but I would suggest that rather returning a flag for a race condition, just make the whole thing error and ask the user to try again. This seems like a very narrow edge case (if you pull a camera during enumeration?) and it would be simpler to just leave it as an assert somewhere.
ok I see 4 options to signal a pnp event during the execution of the two device enumerations which causes inconsistency.
I really have no præference of the 3
There is also option 5 which is to repeat the check until (for say 3 retries) the results of the enumeration are consistent?
I have several BOSON on Windows 10 system. Auto detection seems not to work. "find_cameras.exe" returns only one camera. Is that correctly observed?
If so, I would like to suggest that at more appropriate function would be to return a list of all devices.