I'm using pjsip high-level api PJSUA for doing multiple calls at one time. My sip operator support up to 3 outgoing lines for calls, so in theory it should be possible to call 3 persons at one time. The problem is, that in the operator logs i see, the message "Line limit overreached" and in pjsip, the call fails. I'm doing it like this:
Here is my class that contains methods for calling:
void Sip::InitLibrary()
{
pj::EpConfig ep_cfg;
ep_cfg.logConfig.level = 4;
ep_cfg.logConfig.consoleLevel = 4;
ep_cfg.logConfig.filename = this->log_;
ep_cfg.uaConfig.userAgent = "Ekiga";
this->ep_.libCreate();
this->ep_.libInit(ep_cfg);
}
void Sip::SetSipTransport(Sip::SipTransport transport, unsigned port)
{
TransportConfig tcfg;
tcfg.port = port;
pjsip_transport_type_e type;
switch (transport)
{
case SipTransport::TCP:
type = PJSIP_TRANSPORT_TCP;
break;
case SipTransport::UDP:
type = PJSIP_TRANSPORT_UDP;
break;
}
try {
this->transportId_ = this->ep_.transportCreate(type, tcfg);
}
catch (Error &err) {
//to log
return;
}
}
void Sip::StartLibrary()
{
this->ep_.libStart();
}
AccountInfo Sip::ConnectSipServer(string serv, string login, string pass,SipTransport transport,int port)
{
this->InitLibrary();
this->SetSipTransport(transport, port);
this->StartLibrary();
this->server_ = serv;
AccountConfig accConfig;
string uri = "<sip:" + login + "#" + serv +">";
accConfig.idUri = uri;
string regUri = "sip:" + serv;
accConfig.regConfig.registrarUri = regUri;
accConfig.sipConfig.authCreds.push_back(AuthCredInfo("digest", "*", login, 0, pass));
this->acc_ = new SipAccount();
acc_->create(accConfig, true);
{
unique_lock<mutex> locker(acc_->regLock_);
while (!acc_->regEnd_)
acc_->regDone_.wait(locker);
}
return acc_->info_;
}
bool Sip::CallNumber(string phone)
{
Call *call = new SipCall(*this->acc_);
CallOpParam prm(true);
prm.opt.audioCount = 1;
prm.opt.videoCount = 0;
call->makeCall("sip:" + phone + "#" + this->server_, prm);
SipCall *myCall = (SipCall*)call;
{
unique_lock<mutex> locker(myCall->callLock);
while (!myCall->callEnd)
myCall->callDone.wait(locker);
}
if (myCall->validPhone)
{
delete myCall;
return true;
}
delete myCall;
return false;
}
SipCall.h
SipCall(Account &acc, int call_id = PJSUA_INVALID_ID)
: Call(acc, call_id)
{
Acc = (SipAccount *)&acc;
this->callEnd = false;
this->validPhone = false;
}
~SipCall();
virtual void onCallState(OnCallStateParam &prm);
condition_variable callDone;
mutex callLock;
bool callEnd;
bool validPhone;
SipCall.cpp
void SipCall::onCallState(OnCallStateParam &prm)
{
PJ_UNUSED_ARG(prm);
CallInfo ci = getInfo();
std::cout << "*** Call: " << ci.remoteUri << " [" << ci.stateText
<< "]" << std::endl;
if (ci.state == PJSIP_INV_STATE_DISCONNECTED) {
{
unique_lock<mutex> locker(this->callLock);
this->callEnd = true;
this->callDone.notify_one();
}
}
if (ci.state == PJSIP_INV_STATE_CONFIRMED && ci.lastStatusCode == PJSIP_SC_OK)
{ //here i check that phone is valid, and just hang up the call
CallOpParam p;
this->validPhone = true;
this->hangup(p);
}
}
SipCall::~SipCall()
{
}
Since there a 3 lines avaliable im creating a three tasks to call the corresponding numbers like so:
QStringList testCalls; // initialized with numbers
struct call_result{
QString phone;
bool valid;
};
//....
QList<QFuture<call_result>> futureTestList;
while(counter < testCalls.count()) {
for(int i= 0; i < 3; i++,counter++) {
if(counter >= testCalls.count())
break;
QString number = testCalls.at(counter);
QFuture<call_result> futureResult = QtConcurrent::run(this,&sipQtThread::callNumber,number);
futureTestList.append(futureResult);
}
foreach(QFuture<call_result> future,futureTestList) {
future.waitForFinished();
call_result call = future.result();
emit phonePassed(call.phone,call.valid);
}
futureTestList.clear();
}
The correspondig task sipQtThread::callNumber is following:
sipQtThread::call_result sipQtThread::callNumber(QString phone)
{
call_result call;
pj_thread_desc initdec;
pj_thread_t* thread = 0;
pj_status_t status;
status = pj_thread_register(NULL, initdec, &thread);
if (status != PJ_SUCCESS) {
qDebug()<<"pj_thread_register faild:"<<status;
return call;
}
bool result = sip_->CallNumber(phone.toStdString());
call.phone = phone;
call.valid = result;
return call;
}
As you can see here
if (ci.state == PJSIP_INV_STATE_CONFIRMED && ci.lastStatusCode == PJSIP_SC_OK)
{ //here i check that phone is valid, and just hang up the call
CallOpParam p;
this->validPhone = true;
this->hangup(p);
}
I just check if the phone is valid, and call is starting, and the drop it. Can pjsua2 handle multiple line calls? Or even is pjsip supporting multiple outgoing lines calls? Does the thread registered by pj_thread_register needs to be somehow unregistered maybe?
Related
My program should synchronize with remote postgres database. My code:
db_handler::DB_Handler::DB_Handler(const char* host, unsigned int port, const char* db, const char* user, const char* password, int& error) {
...
this->password = reinterpret_cast<char*>(malloc(std::strlen(password) + 1));
strcpy(this->password, password);
this->connection = nullptr;
if (this->connect_to_db() != 0) {
error = 1;
}
else {
error = 0;
}
}
int db_handler::DB_Handler::connect_to_db() {
try {
std::string connection_params = "host=";
connection_params += std::string(host);
...
connection_params += " password=";
connection_params += password;
if (this->connection != nullptr) {
delete this->connection;
}
this->connection = new pqxx::connection(connection_params.c_str());
return 0;
}
catch (...) {
return 1;
}
}
int db_handler::DB_Handler::sync() {
for (int tries = 0; tries < 2; tries++) {
try {
this->connection->prepare("get_keys", "*query here*");
...
}
catch (const pqxx::broken_connection& e) {
this->connect_to_db();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
catch (...) {
return 1;
}
}
return 1;
}
And on this row this->connection->prepare("get_keys", "*query here*"); my program freezes forever until I will restart it.
Where am I wrong and how can I fix it?
Maybe I should add some timeouts? How can I do it?
The image linked above is the HTML that browser shows. Every time when I press a link, the server cannot accept the correct HTTP information from the browser. Below is my code related to communicating through HTTP.
char buf[2048];
http_handle hh(connfd, buf, 2048);
read(connfd, buf, 2048);
hh.handle_http_request(&hh);
hh.response_http_request(&hh); //the first two function works
read(connfd, buf, 2048); //this returns 0
hh.handle_http_request(&hh);
hh.response_http_request(&hh);
Below is the implementation of handle_http_requestandresponse_http_request:
void* http_handle::handle_http_request(void* arg) {
http_handle* hp = (http_handle*)arg;
hp->_handle_http_request();
return hp;
}
void http_handle::_handle_http_request() {
int i, j;
for (i = 0; this->buf[i] != ' '; i++)
method[i] = this->buf[i];
method[i] = 0;
for (j = 0, ++i; this->buf[i] != ' '; i++, j++)
url[j] = this->buf[i];
url[j] = 0;
//method stores http operations like GET and POST
//url stores the url resource in the http start line
if (!strcasecmp(method, "GET")) {
//...
}
if (!strcasecmp(method, "POST")) {
//...
}
}
void* http_handle::response_http_request(void* arg) {
http_handle* hp = (http_handle*)arg;
hp->_response_http_request();
return hp;
}
void http_handle::_response_http_request() {
if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) {
unimpelented();
return;
}
if (!strcasecmp(method, "GET")) {
if (strcmp(url, "/") == 0) {
char tmp_path[256];
http_handle::path = getcwd(tmp_path, 256);
trans_dir("src");
return;
}
std::string filepath = http_handle::path + "/" + url;
struct stat filestat;
if ((stat(filepath.c_str(), &filestat)) != 0) {
perror("_response_http_request stat error");
exit(1);
}
switch (filestat.st_mode & S_IFMT) {
case S_IFREG:
trans_file(filepath);
break;
case S_IFDIR:
trans_dir(filepath);
break;
default:
break;
}
return;
}
if (!strcasecmp(method, "POST")) { //to be implemented
return;
}
}
read() returns 0 when EOF is reached, ie when the peer has closed the TCP connection on its end.
The data you have shown is not larger than your buffer, so the first read() receives all of the data, and there is nothing left for the second read() because the server closed the connection after sending the data.
This is the main code where error happened:
void VRInputRemapping::DigitalInputConfig() {
bool orginput = this->curVRConfig->GetorgDigitalInput();
uint32_t deviceId = this->curVRConfig->GetcurDeviceId();
uint32_t buttonId = this->curVRConfig->GetcurButtonId();
printf("Digital orginput: %d device:%d butonid:%d\n", orginput, deviceId, buttonId);
vrinputemulator::DigitalInputRemapping* m_currentRemapping =new vrinputemulator::DigitalInputRemapping(true);
vrinputemulator::DigitalBinding* m_currentBinding = new vrinputemulator::DigitalBinding;
if (orginput == false) {
m_currentBinding->type = vrinputemulator::DigitalBindingType::Disabled;
memset(&m_currentBinding->data, 0, sizeof(m_currentBinding->data));
m_currentBinding->data.openvr.buttonId = buttonId;
m_currentBinding->toggleEnabled = false;
m_currentBinding->toggleDelay = 0;
m_currentBinding->autoTriggerEnabled = false;
m_currentBinding->autoTriggerFrequency = 1;
m_currentRemapping->binding = *m_currentBinding;
}
else if (orginput == true) {
m_currentBinding->type = vrinputemulator::DigitalBindingType::NoRemapping;
memset(&m_currentBinding->data, 0, sizeof(m_currentBinding->data));
m_currentBinding->data.openvr.buttonId = buttonId;
m_currentBinding->toggleEnabled = false;
m_currentBinding->toggleDelay = 0;
m_currentBinding->autoTriggerEnabled = false;
m_currentBinding->autoTriggerFrequency = 1;
m_currentRemapping->binding = *m_currentBinding;
}
printf(" void VRInputRemapping::DigitalInputConfig() m_currentRemapping->binding.type:%d device:%d buttonId:%d remapping.data.openvr.buttonId%d\n", m_currentRemapping->binding.type, deviceId, buttonId, m_currentRemapping->binding.data.openvr.buttonId);
//<--------------------Here is the function call-------------------------------------->
this->inputEmulator->setDigitalInputRemapping(deviceId, buttonId, m_currentRemapping);
//<--------------------Here is the function call-------------------------------------->
printf(" void VRInputRemapping::DigitalInputConfig() m_currentRemapping->binding.type:%d device:%d buttonId:%d remapping.data.openvr.buttonId%d\n", m_currentRemapping->binding.type, deviceId, buttonId, m_currentRemapping->binding.data.openvr.buttonId);
delete m_currentBinding;
delete m_currentRemapping;
Sleep(1);
}
This is the Function:
void VRInputEmulator::setDigitalInputRemapping(uint32_t deviceId, uint32_t buttonId, DigitalInputRemapping* remapping, bool modal) {
printf("VRInputEmulator::setDigitalInputRemapping Digital orginput: %d device:%d butonid:%d remapping->binding.data.openvr.buttonId%d\n", remapping->binding.type, deviceId, buttonId, remapping->binding.data.openvr.buttonId);
if (_ipcServerQueue) {
ipc::Request message(ipc::RequestType::InputRemapping_SetDigitalRemapping);
memset(&message.msg, 0, sizeof(message.msg));
message.msg.ir_SetDigitalRemapping.clientId = m_clientId;
message.msg.ir_SetDigitalRemapping.messageId = 0;
message.msg.ir_SetDigitalRemapping.controllerId = deviceId;
message.msg.ir_SetDigitalRemapping.buttonId = buttonId;
message.msg.ir_SetDigitalRemapping.remapData = *remapping;
if (modal) {
uint32_t messageId = _ipcRandomDist(_ipcRandomDevice);
message.msg.ir_SetDigitalRemapping.messageId = messageId;
std::promise<ipc::Reply> respPromise;
auto respFuture = respPromise.get_future();
{
std::lock_guard<std::recursive_mutex> lock(_mutex);
_ipcPromiseMap.insert({ messageId, std::move(respPromise) });
}
_ipcServerQueue->send(&message, sizeof(ipc::Request), 0);
auto resp = respFuture.get();
{
std::lock_guard<std::recursive_mutex> lock(_mutex);
_ipcPromiseMap.erase(messageId);
}
std::stringstream ss;
ss << "Error while setting digital input remapping: ";
if (resp.status == ipc::ReplyStatus::InvalidId) {
ss << "Invalid device id";
throw vrinputemulator_invalidid(ss.str(), (int)resp.status);
} else if (resp.status == ipc::ReplyStatus::NotFound) {
ss << "Device not found";
throw vrinputemulator_notfound(ss.str(), (int)resp.status);
} else if (resp.status != ipc::ReplyStatus::Ok) {
ss << "Error code " << (int)resp.status;
throw vrinputemulator_exception(ss.str(), (int)resp.status);
}
} else {
_ipcServerQueue->send(&message, sizeof(ipc::Request), 0);
}
} else {
throw vrinputemulator_connectionerror("No active connection.");
}
}
You can See the Printf I had use before and after the function call, also used in the head of the dunction,This is the output:
void VRInputRemapping::DigitalInputConfig() m_currentRemapping->binding.type:0 device:1 buttonId:32 remapping.data.openvr.buttonId: 32
VRInputEmulator::setDigitalInputRemapping Digital orginput: 0 device:1 butonid:32 remapping->binding.data.openvr.buttonId: 0
void VRInputRemapping::DigitalInputConfig() m_currentRemapping->binding.type 0 device:1 buttonId:32 remapping.data.openvr.buttonId: 32
The output in the consloe
I am using the Windows api Gatt Client BLE for C++, my goal is to connect two devices (but in this case I will try just one) and keep reading and writing data constantly without closing the device at any time. All my devices have one specific service that contains a read characteristic and a write one.
HOW TO TEST:
Use Visual studio 2017 (v141) with Windows SDK Version: 10.0.18362.0, create a new console (.exe) solution, change the Platform in Project -> Properties to Win32 and go to Project -> Properties -> C/C++ -> Command Line and add these options:
/std:c++17 /await
Then copy the following code in a file (you can copy all in the same .cpp file):
#pragma once
#include <SDKDDKVer.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <iostream>
#include <queue>
#include <map>
#include <mutex>
#include <condition_variable>
#include <string>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Web.Syndication.h>
#include "winrt/Windows.Devices.Bluetooth.h"
#include "winrt/Windows.Devices.Bluetooth.GenericAttributeProfile.h"
#include "winrt/Windows.Devices.Enumeration.h"
#include "winrt/Windows.Storage.Streams.h"
#pragma comment(lib, "windowsapp")
using namespace std;
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::Web::Syndication;
using namespace Windows::Devices::Bluetooth;
using namespace Windows::Devices::Bluetooth::GenericAttributeProfile;
using namespace Windows::Devices::Enumeration;
using namespace Windows::Storage::Streams;
#pragma region STRUCS AND ENUMS
#define LOG_ERROR(e) cout << e << endl;
union to_guid
{
uint8_t buf[16];
guid guid;
};
const uint8_t BYTE_ORDER[] = { 3, 2, 1, 0, 5, 4, 7, 6, 8, 9, 10, 11, 12, 13, 14, 15 };
guid make_guid(const wchar_t* value)
{
to_guid to_guid;
memset(&to_guid, 0, sizeof(to_guid));
int offset = 0;
for (unsigned int i = 0; i < wcslen(value); i++) {
if (value[i] >= '0' && value[i] <= '9')
{
uint8_t digit = value[i] - '0';
to_guid.buf[BYTE_ORDER[offset / 2]] += offset % 2 == 0 ? digit << 4 : digit;
offset++;
}
else if (value[i] >= 'A' && value[i] <= 'F')
{
uint8_t digit = 10 + value[i] - 'A';
to_guid.buf[BYTE_ORDER[offset / 2]] += offset % 2 == 0 ? digit << 4 : digit;
offset++;
}
else if (value[i] >= 'a' && value[i] <= 'f')
{
uint8_t digit = 10 + value[i] - 'a';
to_guid.buf[BYTE_ORDER[offset / 2]] += offset % 2 == 0 ? digit << 4 : digit;
offset++;
}
else
{
// skip char
}
}
return to_guid.guid;
}
mutex subscribeLock;
condition_variable subscribeSignal;
mutex _mutexWrite;
condition_variable signalWrite;
struct DeviceCacheEntry {
BluetoothLEDevice device = nullptr;
GattDeviceService service = nullptr;
GattCharacteristic characteristic = nullptr;
};
map<wstring, DeviceCacheEntry> cache;
struct Subscription {
GattCharacteristic::ValueChanged_revoker revoker;
};
struct BLEDeviceData {
wstring id;
wstring name;
bool isConnectable = false;
Subscription* subscription = NULL;
};
vector<BLEDeviceData> deviceList{};
mutex deviceListLock;
condition_variable deviceListSignal;
#pragma endregion
#pragma region CACHE FUNCTIONS
//Call this function to get a device from cache or async if it wasn't found
IAsyncOperation<BluetoothLEDevice> getDevice(wchar_t* deviceId) {
if (cache.count(wstring(deviceId)) && cache[wstring(deviceId)].device)
co_return cache[wstring(deviceId)].device;
BluetoothLEDevice result = co_await BluetoothLEDevice::FromIdAsync(deviceId);
if (result == nullptr) {
LOG_ERROR("Failed to connect to device.")
co_return nullptr;
}
else {
DeviceCacheEntry d;
d.device = result;
if (!cache.count(wstring(deviceId))) {
cache.insert({ wstring(deviceId), d });
}
else {
cache[wstring(deviceId)] = d;
}
co_return cache[wstring(deviceId)].device;
}
}
//Call this function to get a service from cache or async if it wasn't found
IAsyncOperation<GattDeviceService> getService(wchar_t* deviceId, wchar_t* serviceId) {
if (cache.count(wstring(deviceId)) && cache[wstring(deviceId)].service)
co_return cache[wstring(deviceId)].service;
auto device = co_await getDevice(deviceId);
if (device == nullptr)
co_return nullptr;
GattDeviceServicesResult result = co_await device.GetGattServicesForUuidAsync(make_guid(serviceId), BluetoothCacheMode::Cached);
if (result.Status() != GattCommunicationStatus::Success) {
LOG_ERROR("Failed getting services. Status: " << (int)result.Status())
co_return nullptr;
}
else if (result.Services().Size() == 0) {
LOG_ERROR("No service found with uuid")
co_return nullptr;
}
else {
if (cache.count(wstring(deviceId))) {
cache[wstring(deviceId)].service = result.Services().GetAt(0);
}
co_return cache[wstring(deviceId)].service;
}
}
//Call this function to get a characteristic from cache or async if it wasn't found
IAsyncOperation<GattCharacteristic> getCharacteristic(wchar_t* deviceId, wchar_t* serviceId, wchar_t* characteristicId) {
try {
if (cache.count(wstring(deviceId)) && cache[wstring(deviceId)].characteristic)
co_return cache[wstring(deviceId)].characteristic;
auto service = co_await getService(deviceId, serviceId);
if (service == nullptr)
co_return nullptr;
GattCharacteristicsResult result = co_await service.GetCharacteristicsForUuidAsync(make_guid(characteristicId), BluetoothCacheMode::Cached);
if (result.Status() != GattCommunicationStatus::Success) {
LOG_ERROR("Error scanning characteristics from service. Status: " << (int)result.Status())
co_return nullptr;
}
else if (result.Characteristics().Size() == 0) {
LOG_ERROR("No characteristic found with uuid")
co_return nullptr;
}
else {
if (cache.count(wstring(deviceId))) {
cache[wstring(deviceId)].characteristic = result.Characteristics().GetAt(0);
}
co_return cache[wstring(deviceId)].characteristic;
}
}
catch (...) {
LOG_ERROR("Exception while trying to get characteristic")
}
}
#pragma endregion
#pragma region SCAN DEVICES FUNCTIONS
DeviceWatcher deviceWatcher{ nullptr };
mutex deviceWatcherLock;
DeviceWatcher::Added_revoker deviceWatcherAddedRevoker;
DeviceWatcher::Updated_revoker deviceWatcherUpdatedRevoker;
DeviceWatcher::Removed_revoker deviceWatcherRemovedRevoker;
DeviceWatcher::EnumerationCompleted_revoker deviceWatcherCompletedRevoker;
struct TestBLE {
static void ScanDevices();
static void StopDeviceScan();
};
//This function would be called when a new BLE device is detected
void DeviceWatcher_Added(DeviceWatcher sender, DeviceInformation deviceInfo) {
BLEDeviceData deviceData;
deviceData.id = wstring(deviceInfo.Id().c_str());
deviceData.name = wstring(deviceInfo.Name().c_str());
if (deviceInfo.Properties().HasKey(L"System.Devices.Aep.Bluetooth.Le.IsConnectable")) {
deviceData.isConnectable = unbox_value<bool>(deviceInfo.Properties().Lookup(L"System.Devices.Aep.Bluetooth.Le.IsConnectable"));
}
deviceList.push_back(deviceData);
}
//This function would be called when an existing BLE device is updated
void DeviceWatcher_Updated(DeviceWatcher sender, DeviceInformationUpdate deviceInfoUpdate) {
wstring deviceData = wstring(deviceInfoUpdate.Id().c_str());
for (int i = 0; i < deviceList.size(); i++) {
if (deviceList[i].id == deviceData) {
if (deviceInfoUpdate.Properties().HasKey(L"System.Devices.Aep.Bluetooth.Le.IsConnectable")) {
deviceList[i].isConnectable = unbox_value<bool>(deviceInfoUpdate.Properties().Lookup(L"System.Devices.Aep.Bluetooth.Le.IsConnectable"));
}
break;
}
}
}
void DeviceWatcher_Removed(DeviceWatcher sender, DeviceInformationUpdate deviceInfoUpdate) {
}
void DeviceWatcher_EnumerationCompleted(DeviceWatcher sender, IInspectable const&) {
TestBLE::StopDeviceScan();
TestBLE::ScanDevices();
}
//Call this function to scan async all BLE devices
void TestBLE::ScanDevices() {
try {
lock_guard lock(deviceWatcherLock);
IVector<hstring> requestedProperties = single_threaded_vector<hstring>({ L"System.Devices.Aep.DeviceAddress", L"System.Devices.Aep.IsConnected", L"System.Devices.Aep.Bluetooth.Le.IsConnectable" });
hstring aqsFilter = L"(System.Devices.Aep.ProtocolId:=\"{bb7bb05e-5972-42b5-94fc-76eaa7084d49}\")"; // list Bluetooth LE devices
deviceWatcher = DeviceInformation::CreateWatcher(aqsFilter, requestedProperties, DeviceInformationKind::AssociationEndpoint);
deviceWatcherAddedRevoker = deviceWatcher.Added(auto_revoke, &DeviceWatcher_Added);
deviceWatcherUpdatedRevoker = deviceWatcher.Updated(auto_revoke, &DeviceWatcher_Updated);
deviceWatcherRemovedRevoker = deviceWatcher.Removed(auto_revoke, &DeviceWatcher_Removed);
deviceWatcherCompletedRevoker = deviceWatcher.EnumerationCompleted(auto_revoke, &DeviceWatcher_EnumerationCompleted);
deviceWatcher.Start();
}
catch (exception e) {
LOG_ERROR(e.what())
}
}
void TestBLE::StopDeviceScan() {
scoped_lock lock(deviceListLock, deviceWatcherLock);
if (deviceWatcher != nullptr) {
deviceWatcherAddedRevoker.revoke();
deviceWatcherUpdatedRevoker.revoke();
deviceWatcherRemovedRevoker.revoke();
deviceWatcherCompletedRevoker.revoke();
deviceWatcher.Stop();
deviceWatcher = nullptr;
}
deviceListSignal.notify_one();
}
#pragma endregion
#pragma region SUBSCRIBE/READ FUNCTIONS
//On this function you can read all data from the specified characteristic
void Characteristic_ValueChanged(GattCharacteristic const& characteristic, GattValueChangedEventArgs args)
{
LOG_ERROR("Read data from device: " << to_string(characteristic.Service().Device().DeviceId()) << ", data size: " << args.CharacteristicValue().Length())
}
//Function used to subscribe async to the specific device
fire_and_forget SubscribeCharacteristicAsync(wstring deviceId, wstring serviceId, wstring characteristicId, bool* result) {
try {
auto characteristic = co_await getCharacteristic(&deviceId[0], &serviceId[0], &characteristicId[0]);
if (characteristic != nullptr) {
auto status = co_await characteristic.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue::Notify);
if (status != GattCommunicationStatus::Success) {
LOG_ERROR("Error subscribing to characteristic. Status: " << (int)status)
}
else {
for (int i = 0; i < deviceList.size(); i++) {
if (deviceList[i].id == deviceId) {
deviceList[i].subscription = new Subscription();
deviceList[i].subscription->revoker = characteristic.ValueChanged(auto_revoke, &Characteristic_ValueChanged);
break;
}
}
if (result != 0)
*result = true;
}
}
}
catch (hresult_error& ex)
{
LOG_ERROR("SubscribeCharacteristicAsync error: " << to_string(ex.message().c_str()))
for (int i = 0; i < deviceList.size(); i++) {
if (deviceList[i].id == deviceId && deviceList[i].subscription) {
delete deviceList[i].subscription;
deviceList[i].subscription = NULL;
break;
}
}
}
subscribeSignal.notify_one();
}
//Call this function to subscribe to the specific device so you can read data from it
bool SubscribeCharacteristic(wstring deviceId, wstring serviceId, wstring characteristicId) {
unique_lock<mutex> lock(subscribeLock);
bool result = false;
SubscribeCharacteristicAsync(deviceId, serviceId, characteristicId, &result);
subscribeSignal.wait(lock);
return result;
}
#pragma endregion
#pragma region WRITE FUNCTIONS
//Function used to send data async to the specific device
fire_and_forget SendDataAsync(wchar_t* deviceId, wchar_t* serviceId, wchar_t* characteristicId, uint8_t * data, uint16_t size, bool* result) {
try {
auto characteristic = co_await getCharacteristic(deviceId, serviceId, characteristicId);
if (characteristic != nullptr) {
DataWriter writer;
writer.WriteBytes(array_view<uint8_t const>(data, data + size));
IBuffer buffer = writer.DetachBuffer();
auto status = co_await characteristic.WriteValueAsync(buffer, GattWriteOption::WriteWithoutResponse);
if (status != GattCommunicationStatus::Success) {
LOG_ERROR("Error writing value to characteristic. Status: " << (int)status)
}
else if (result != 0) {
LOG_ERROR("Data written succesfully")
*result = true;
}
}
}
catch (hresult_error& ex)
{
LOG_ERROR("SendDataAsync error: " << to_string(ex.message().c_str()))
for (int i = 0; i < deviceList.size(); i++) {
if (deviceList[i].id == deviceId && deviceList[i].subscription) {
delete deviceList[i].subscription;
deviceList[i].subscription = NULL;
break;
}
}
}
signalWrite.notify_one();
}
//Call this function to write data on the device
bool SendData(wchar_t* deviceId, wchar_t* serviceId, wchar_t* characteristicId, uint8_t * data, uint16_t size) {
bool result = false;
unique_lock<mutex> lock(_mutexWrite);
// copy data to stack so that caller can free its memory in non-blocking mode
SendDataAsync(deviceId, serviceId, characteristicId, data, size, &result);
signalWrite.wait(lock);
return result;
}
#pragma endregion
Finally copy this main function (it can be copied at the end of the same file):
int main() {
//The mac of the device that will be tested
wstring deviceMac = L"00:11:22:33:44:55";
//These are the serviceUUID, readCharacteristicUUID and writeCharacteristicUUID as I said previously
wstring serviceUUID = L"{47918888-5555-2222-1111-000000000000}";
wstring readUUID = L"{31a28888-5555-2222-1111-00000000cede}";
wstring writeUUID = L"{f55a8888-5555-222-1111-00000000957a}";
//I think it is the mac of the BLE USB Dongle because it is in all device id when they are enumerated
wstring otherMac = L"24:4b:fe:3a:1a:ba";
//The device Id that we are looking for
wstring deviceId = L"BluetoothLE#BluetoothLE" + otherMac;
deviceId += L"-";
deviceId += deviceMac;
//To start scanning just call this function
TestBLE::ScanDevices();
//Data to be written all the time
const uint16_t dataSize = 3;
uint8_t data [dataSize]= { 0x0, 0xff, 0xff };
//Wait time in miliseconds between each write
chrono::milliseconds waitTime = 100ms;
//It will be executed always
while (true) {
//Then every device and their info updated would be in this vector
for (int i = 0; i < deviceList.size(); i++) {
//If the device is connectable we will try to connect if we aren't subscribed yet or send information
if (deviceList[i].isConnectable) {
//We can do here the following code to know the structure of the device id (if otherMac variable is the BLE USB dongle mac or not)
//cout << to_string(deviceList[i].id) << endl;
if (!deviceList[i].subscription && deviceList[i].id == deviceId) {
SubscribeCharacteristic(deviceList[i].id, serviceUUID, readUUID);
}
else if (deviceList[i].subscription) {
SendData(&deviceId[0], &serviceUUID[0], &writeUUID[0], data, dataSize);
}
}
}
this_thread::sleep_for(waitTime);
}
}
You will need a BLE device with a service that contains a reading and a writing characteristic, set the corresponding values in the deviceMac, serviceUUID, readUUID and writeUUID variables, you can also modify the bytes that are going to be written in data and dataSize, and the time between writes in waitTime. The otherMac variable should be the mac of the BLE USB dongle device but I recommend that you check it by getting the id of the devices from deviceList inside the for loop.
When you run this code on some rare times you will get the error "Failed getting services. Status:" with result 1 (unreachable) or 3 (access denied) and in the rest of the cases it will be reading the device data correctly and after a while it will give the error "SendDataAsync error: Object has been disposed" and from there it will continue giving "SubscribeCharacteristicAsync error: Object has been disposed", so at some point it will stop being able to read data of the device. What could be the reason?
EDIT 1:
It is quite strange because with this code the data is never written correctly (the "Data written succesfully" message is not displayed) but in my completed code I have always been able to write the data, maybe the problem is still the same and it is related to the characteristic stored in the "map <wstring, DeviceCacheEntry> cache" since perhaps it is stored as a copy and when trying to access it at some point it is disposed by Windows (since it is a copy of the original that is stored in the cache) and gives the error as described in the answer to this post in the point named "UPDATE 2 - SOME WEIRDNESS"
I have a helper function for using prepared statements with mysql. All works well as long as i dont want to retrieve any results.
If i want to retrieve a result (e.g. simple long ID) im getting a warning from visual studio debugger that the stack is corrupted around my ID variable.
Header
bool RunPreparedStatement(std::string query, std::vector paramList, std::string& errbuf, int* affected_rows, bool fetchResultID = false, int32* result_id=0, bool fetchInsertID = false, int32* last_insert_id=0);
bool RunPreparedStatement(std::string sql, std::vector<MYSQL_BIND> paramList, std::string& errStr, int* affected_rows, bool fetchResultID, int32* result_id, bool fetchInsertID, int32* last_insert_id) {
char errbuf[MYSQL_ERRMSG_SIZE];
int numParams = paramList.size();
bool fetchResult = fetchResultID;
if(numParams == 0) {
errStr = "0 parameters supplied";
return false;
}
MYSQL_STMT *stmt = mysql_stmt_init(&mysql);
if (!stmt) {
errStr = "mysql_stmt_init(), out of memory";
return false;
}
if (mysql_stmt_prepare(stmt, sql.c_str(), sql.length())) {
errStr = "mysql_stmt_prepare() failed ";
errStr.append(mysql_stmt_error(stmt));
return false;
}
int param_count= mysql_stmt_param_count(stmt);
if (param_count != numParams) /* validate parameter count */{
errStr = "invalid parameter count returned by MySQL";
return false;
}
if (mysql_stmt_bind_param(stmt, ¶mList[0])) {
errStr = "mysql_stmt_bind_param() failed ";
errStr.append(mysql_stmt_error(stmt));
return false;
}
int32 id = 0;
MYSQL_BIND resultParam1;
BindValue(&resultParam1,MYSQL_TYPE_LONGLONG,&id,0,0,0);
if(fetchResult) {
/* Bind the results buffer */
if (mysql_stmt_bind_result(stmt, &resultParam1) != 0) {
errStr = "mysql_stmt_bind_result() failed ";
errStr.append(mysql_stmt_error(stmt));
return false;
}
}
if (mysql_stmt_execute(stmt)) {
errStr = "mysql_stmt_execute() failed ";
errStr.append(mysql_stmt_error(stmt));
return false;
}
if(fetchResult) {
if (mysql_stmt_store_result(stmt) != 0) {
errStr = "Could not buffer result set ";
errStr.append(mysql_stmt_error(stmt));
return false;
}
if(mysql_stmt_fetch (stmt) == 0) {
(*result_id) = id;
// OK
*affected_rows = 1;
} else {
*affected_rows = 0;
/*errStr = "no results found ";
errStr.append(mysql_stmt_error(stmt));
return false;*/
}
} else {
*affected_rows = mysql_stmt_affected_rows(stmt);
}
if(fetchInsertID) {
*last_insert_id= mysql_stmt_insert_id(stmt);
}
if(stmt != NULL) {
// Deallocate result set
mysql_stmt_free_result(stmt); /* deallocate result set */
if (mysql_stmt_close(stmt)) {
errStr = "closing the statement failed ";
errStr.append(mysql_stmt_error(stmt));
return false;
}
}
return true;
}
I call it like this
std::string errBuf;
int affected_rows = 0;
std::string sql = "SELECT count(*) FROM name WHERE (?) like name";
std::vector<MYSQL_BIND> paramList;
std::vector<MYSQL_BIND> resultParamList;
char data[1024];
safe_strncpy(data,name,sizeof(data), __FILE__, __LINE__);
MYSQL_BIND param1;
BindValue(¶m1,MYSQL_TYPE_STRING,data,strlen(data),0,0);
paramList.push_back(param1);
int32 count = 0;
bool ret = true;
if(RunPreparedStatement(sql,paramList,errBuf,&affected_rows,true,&count)){
...
}
I previously had also a resultParamList instead of simple the ID and had the same error and thought it was related to return a list of values but that wasnt it.
Im receiving the warning as soon as the RunPreparedStatement method is finished.
Trying another sample code i found the issue
int32 id = 0;
MYSQL_BIND resultParam1;
BindValue(&resultParam1,MYSQL_TYPE_LONGLONG,&id,0,0,0);
im telling that the result type is MYSQL_TYPE_LONGLONG but im only giving it a int32 (unsigned int ) instead of unsigned long long.
So either change to MYSQL_TYPE_LONG or increase var size.