Last active
February 15, 2019 21:32
-
-
Save pavel-a/4674b612df0b3460c548d441194b723b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Detect the proper COM ports for paired BT devices by name prefix | |
// pavel_a@fastmail.fm 12-feb-2019 | |
#include "pch.h" | |
#define _CRT_SECURE_NO_WARNINGS 1 | |
#include <vector> | |
#include <tuple> | |
#include <string> | |
#include <process.h> | |
#include <iostream> | |
using string = std::string; | |
const char * const sGUID_BTHdev = "{e0cbf06c-cd8b-4647-bb8a-263b43f0f974}"; | |
const char * const sGUID_Port = "{4d36e978-e325-11ce-bfc1-08002be10318}"; | |
#define dbgprintf printf | |
/// Find all matching BTH devices and their COM ports | |
/// Returns list of tuples (COMname, BTdevname) | |
std::vector<std::tuple<string, string > > | |
find_bth_ports(string BT_name_prefix) | |
{ | |
// Match BTH device with its COM port by "Contained ID". | |
// The matching port is always the "outgoing" one. The other one is "incoming". | |
// Parse output of | |
// reg query HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\BTHENUM ... | |
// No unicode. Assume a normal ENU system, no weird characters in the spew. | |
const char *cmd = | |
R"(reg.exe query HKLM\SYSTEM\CurrentControlSet\Enum\BTHENUM /s /t REG_SZ)"; | |
std::vector<std::tuple<string, string > > resv; // result | |
std::vector<std::tuple<string, string > > BTH_devs; | |
std::vector<std::tuple<string, string > > Ports; | |
string curr_reg_base_subkey; // current subkey of either D or P type | |
char state = 'N'; // N=Neutral, D=device, P=port, X=unknown | |
string name, container_id, COM_port_name; | |
char buf[300]; | |
auto get_val = [&buf]() { | |
// Get the reg. value in the spew line as string: | |
//search for REG_xxx<space*>, return all after it, can contain spaces | |
char *p = strstr(buf, "REG_"); | |
if (!p) return string(""); | |
p = strchr(p, ' '); | |
if (!p) return string(""); | |
while (*p == ' ') p++; | |
return string(p); | |
}; | |
FILE *p = _popen(cmd, "rt"); | |
if (!p) { | |
dbgprintf("Error reading registry!"); | |
return resv; | |
} | |
while (fgets(buf, sizeof(buf)-1, p)) { | |
buf[sizeof(buf) - 1] = 0; | |
size_t sl = strlen(buf); | |
//remove trailing \n | |
if (buf[sl - 1] == '\n') { | |
buf[--sl] = 0; | |
} | |
else { | |
dbgprintf("Line too long\n"); | |
} | |
// get rid of tabs | |
for(;;) { | |
char *q = strchr(buf, '\t'); | |
if (!q) break; | |
*q = ' '; | |
} | |
if (0 == _strnicmp(buf, "End of...", 6)) { | |
// Last line of the spew; fake location to flush the last found node. | |
strcpy(buf, R"(HKEY_LOCAL_MACHINE\SYSTEM\end)"); | |
} | |
if (0 == strncmp(buf, R"(HKEY_LOCAL_MACHINE\SYSTEM)", 24)) { | |
// KEY/SUBKEY line: | |
switch (state) { | |
case 'N': | |
// Keep the last one, go on. | |
curr_reg_base_subkey = std::string(buf); | |
continue; | |
default: | |
if (0 != strncmp(buf, curr_reg_base_subkey.c_str(), curr_reg_base_subkey.length())) | |
{ | |
// This subtree ended and new one starts. Save the current node | |
if (state == 'P') { | |
// printf("Port found... FN=%s PN=%s\n", name.c_str(), COM_port_name.c_str()); | |
Ports.push_back({ container_id, COM_port_name }); | |
} | |
else if (state == 'D') { | |
if (0 == name.compare(0, BT_name_prefix.length(), BT_name_prefix)) { | |
//printf("BTH dev found... FN=%s\n", name.c_str()); | |
BTH_devs.push_back({ container_id, name }); | |
} | |
} | |
// and start new (unknown type) node at this subkey: | |
state = 'N'; | |
name = ""; | |
curr_reg_base_subkey = buf; | |
} | |
} | |
continue; | |
} // subkey line | |
if (!strstr(buf, " REG_")) { | |
continue; // ignore other non-value lines | |
} | |
if (state == 'N') { | |
state = 'X'; // unknown type | |
//printf("Node start->\n%s\n", curr_reg_base_subkey.c_str()); | |
} | |
// Look for interesting properties: | |
if (strstr(buf, "ClassGUID")) { | |
string cls = get_val(); | |
char const * pcls = cls.c_str(); | |
if (0 == _stricmp(pcls, sGUID_BTHdev)) { | |
state = 'D'; | |
} | |
else if (0 == _stricmp(pcls, sGUID_Port)) { | |
state = 'P'; | |
} | |
continue; | |
} | |
if (strstr(buf, "ContainerID")) { | |
container_id = get_val(); | |
continue; | |
} | |
if (strstr(buf, "FriendlyName")) { | |
name = get_val(); | |
continue; | |
} | |
if (strstr(buf, "PortName")) { | |
COM_port_name = get_val(); | |
continue; | |
} | |
} // while | |
fclose(p); | |
//printf("Found ports: %d adapters: %d\n", Ports.size(), BTH_devs.size()); | |
// Now select all ports that have matching container id with any BTH device: | |
for (auto &port : Ports) { | |
string port_cid = std::get<0>(port); | |
for (auto adp : BTH_devs) { | |
if (port_cid == std::get<0>(adp)) { | |
//printf("match\n"); | |
// Return tuple (COM_port_name, adapter_Name) | |
resv.push_back({ std::get<1>(port), std::get<1>(adp) }); | |
} | |
} | |
} | |
return resv; | |
} | |
int main(int argc, char *argv[]) | |
{ | |
char const *adp_prefix = "RNBT"; | |
if (argc >= 2) { | |
adp_prefix = argv[1]; | |
} | |
std::cout << "Looking up COM ports for paired/bonded BTH adapters with names like " << adp_prefix << " ...\n"; | |
auto result = find_bth_ports(adp_prefix); | |
if (!result.size()) { | |
std::cout << "No COM ports found for BTH devices with prefix '" << adp_prefix << "'\n"; | |
} | |
for (auto &res : result) { | |
string COM_name, adapter_name; | |
std::tie(COM_name, adapter_name) = res; | |
std::cout << "COM port name: " << COM_name << " for adapter: " << adapter_name << std::endl; | |
} | |
return result.size() ? 0 : 1; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Detection of "outgoing" com port of RN42 bluetooth module. Formatted as standalone program.
Seems to work on win10 1803-09.