Related
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 am trying to fire a function every time an ESP32 node receives a certain char. I can receive the data and parse the char out of a packet structure I have made. I register all my actions in a map of type <char,callback_function>, where
typedef void (*callback_function)(void);
The idea being that I link all the chars to their respective functions. I can then parse a char out and find the pointer to the function I want to fire easily. The only downside is I have to keep a reference to a global object to access this map from within the bluetooth_callback. I've been testing this for almost 2 days now, and I've written some example code without the espressif APIs. On its own, my code seems to work fine and it runs on the ESP32 aswell. But if I let the bluetooth_callback get called after registering it with the espressif API (ie. let a real bluetooth event to fire the callback), the behavior becomes undefined. The size of the map is still correct with the number of pairs I have added to it, but all of the values and keys are null or undefined, and if I try to access them I get a Guru Meditation Error. I have been trying to solve this problem a lot, and the behaviour is very strange to me. If I call the callback manually then everything seems to work, but if I let it get called by a bluetooth event, I get undefined behavior and crashes. I'm not sure if I am doing something wrong c++ wise or if I this is an issue specific to the esp32. I could be making a dumb mistake. I have attached my Arduino script, the header and cpp files of the library I was working on, and a sample application that should show what I am trying to do. Thank you for your help.
Hardware:
Board: ESP32 Dev Module (Sparkfun ESP32 thing)
Core Installation version: 1.0.3
IDE name: Arduino IDE
Flash Frequency: 40Mhz
PSRAM enabled: no
Upload Speed: 115200
Computer OS: Windows 10
Backtrace
Guru Meditation Error: Core 0 panic'ed (LoadProhibited). Exception was unhandled.
Core 0 register dump:
PC : 0x400014e8 PS : 0x00060430 A0 : 0x800d2410 A1 : 0x3ffcf1a0
A2 : 0x000000fe A3 : 0x000000fc A4 : 0x000000ff A5 : 0x0000ff00
A6 : 0x00ff0000 A7 : 0xff000000 A8 : 0x800d2228 A9 : 0x3ffcf170
A10 : 0x3ffbed90 A11 : 0x3f40148b A12 : 0x3f40148b A13 : 0x0000ff00
A14 : 0x00ff0000 A15 : 0xff000000 SAR : 0x00000010 EXCCAUSE: 0x0000001c
EXCVADDR: 0x000000fc LBEG : 0x400014fd LEND : 0x4000150d LCOUNT : 0xfffffffb
Backtrace: 0x400014e8:0x3ffcf1a0 0x400d240d:0x3ffcf1b0 0x400d242d:0x3ffcf1d0 0x400d14a2:0x3ffcf1f0 0x400d1501:0x3ffcf210 0x400df881:0x3ffcf230 0x400d8362:0x3ffcf280 0x4008d83d:0x3ffcf2b0
Decoded:
PC: 0x400014e8
EXCVADDR: 0x000000fc
Decoding stack results
0x400d240d: Print::write(char const*) at C:\Users\Jake Booth\Documents\ArduinoData\packages\esp32\hardware\esp32\1.0.3\cores\esp32/Print.h line 66
0x400d242d: Print::print(char const*) at C:\Users\Jake Booth\Documents\ArduinoData\packages\esp32\hardware\esp32\1.0.3\cores\esp32\Print.cpp line 89
0x400d14a2: NodeESP::act_message(char*) at C:\Users\Jake Booth\Documents\Arduino\libraries\NodeESP\NodeESP.cpp line 336
0x400d1501: NodeESP::bluetooth_callback(esp_spp_cb_event_t, esp_spp_cb_param_t*) at C:\Users\Jake Booth\Documents\Arduino\libraries\NodeESP\NodeESP.cpp line 176
0x400df881: btc_spp_cb_handler at /Users/ficeto/Desktop/ESP32/ESP32/esp-idf-public/components/bt/bluedroid/btc/profile/std/spp/btc_spp.c line 152
0x400d8362: btc_task at /Users/ficeto/Desktop/ESP32/ESP32/esp-idf-public/components/bt/bluedroid/btc/core/btc_task.c line 110
0x4008d83d: vPortTaskWrapper at /Users/ficeto/Desktop/ESP32/ESP32/esp-idf-public/components/freertos/port.c line 143
I've highlighted line 336 below to show you easily which one it is.
Arduino Sketch
#include "NodeESP.h"
#define LED_PIN 5
#define TOGGLE_LED_CHAR 'l'
#define TURN_ON_LED_CHAR 'p'
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
pinMode(LED_PIN,OUTPUT);
char l = TOGGLE_LED_CHAR;
char p = TURN_ON_LED_CHAR;
esp_spp_cb_param_t ex_param;
ex_param.data_ind.data = (uint8_t*)l;
ex_param.data_ind.len = 1;
//ex_param_ptr = *ex_param;
NodeESP node_esp;
node_esp.add_func(&p, toggle_LED);
node_esp.add_func(&l, turn_on_LED);
node_esp.begin("TEST_1");
//Just calling it manually works, but when the espressif API calls it I get undefined behaviour.
// NodeESP::bluetooth_callback(ESP_SPP_DATA_IND_EVT,&ex_param);
}
void toggle_LED()
{
Serial.println("toggle_LED()");
}
void turn_on_LED()
{
Serial.println("turn_on_LED()");
}
void loop() {
// put your main code here, to run repeatedly:
while(1)
{
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}
NodeESP.h
/*
NodeESP.cpp - Library for automatically connecting to and using esp32 microcontrollers
Created by Jacob Booth 11/9/2020
*/
#ifndef NodeESP_h
#define NodeESP_h
#include "Arduino.h"
#include <ESPmDNS.h>
#include <WiFi.h>
#include <map>
#include "esp_bt_main.h"
#include "esp_bt_device.h"
#include "esp_spp_api.h"
#include "esp_gap_bt_api.h"
//---Define buffer constants--------------------------
#define RCV_BUF_SIZE 128
#define TX_BUF_SIZE 256
//---Define communication constants-------------------
#define RFCOMM_CHANNEL 1
#define RFCOMM_MAX_FRAME_SIZE 0xffff
#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! please run 'make menuconfig' to enable it
#endif
class NodeESP
{
public:
//Constructor
NodeESP();
//Begin
bool begin(const char *_name);
//Typedef
typedef void (*callback_function)(void);
//Variables
uint32_t bt_handle;
const char *_name;
std::map<char*, callback_function> funcs;
char rx_buf[RCV_BUF_SIZE];
char tx_buf[RCV_BUF_SIZE];
WiFiServer server;
WiFiClient client;
//Generic
void add_func(char* command, callback_function func);
void act_message(char* trigger);
//Bluetooth
void bluetooth_write(char *buf, int len);
bool bluetooth_spp_init();
bool bluetooth_init(const char *deviceName);
static char parse_data(uint8_t *rcvd, uint16_t len);
static void bluetooth_callback(esp_spp_cb_event_t event, esp_spp_cb_param_t *param);
String toString(const IPAddress & address);
private:
};
#endif
NodeESP.cpp
/*
NodeESP.cpp - Library for automatically connecting to and using esp32 microcontrollers
Created by Jacob Booth 11/9/2020
*/
#include "Arduino.h"
#include <ESPmDNS.h>
#include <WiFi.h>
#include <map>
#include "esp_bt_main.h"
#include "esp_bt_device.h"
#include "esp_spp_api.h"
#include "esp_gap_bt_api.h"
#include "NodeESP.h"
using namespace std;
//---State Machine Variables---
//State enum that helps the state machine track its way through
//connecting to wifi.
enum state_t {
UNDEFINED,
DISCONNECTED,
WIFI_CON,
SERVER_CON
};
enum comm_t {
s,
NONE,
WIFI,
BLUETOOTH,
q
};
//The current communication protocol we are using
comm_t cur_comm = s;
//The current state
state_t cur_state = UNDEFINED;
char tx_buf[TX_BUF_SIZE];
char rx_buf[RCV_BUF_SIZE];
//---Wifi
WiFiServer server(0);
WiFiClient client;
//---Bluetooth
uint32_t bt_handle;
void* example_obj;
//---Main Init--------------------------------------------------------
NodeESP::NodeESP()
{
this->_name = "default";
example_obj = (void*)this;
}
bool NodeESP::begin(const char *_name)
{
this->_name = _name;
//For testing set the current communication method to bluetooth
cur_comm = BLUETOOTH;
switch (cur_comm)
{
case UNDEFINED:
return false;
break;
case NONE:
return false;
break;
case WIFI:
return true;
break;
case BLUETOOTH:
if (!this->bluetooth_init(_name))
{
return false;
}
break;
}
return true;
}
//---Bluetooth Functions--------------------------------
bool NodeESP::bluetooth_init(const char *deviceName)
{
//Start Bluetooth
if (!btStart())
{
return false;
}
//Start BlueDroid (bluetooth stack)
if (esp_bluedroid_init()!= ESP_OK)
{
return false;
}
//Enable BlueDroid
if (esp_bluedroid_enable()!= ESP_OK)
{
return false;
}
//Set device name
esp_bt_dev_set_device_name(deviceName);
//Set sannable and discoverable
esp_bt_gap_set_scan_mode(ESP_BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE);
//Init spp server
if (!this->bluetooth_spp_init())
{
return false;
}
//Set send bytes to the correct callback
//send_bytes = write_bluetooth;
return true;
}
bool NodeESP::bluetooth_spp_init()
{
if (esp_spp_register_callback(&bluetooth_callback) != ESP_OK)
{
return false;
}
if (esp_spp_init(ESP_SPP_MODE_CB) != ESP_OK)
{
return false;
}
if (esp_spp_start_srv(ESP_SPP_SEC_NONE, ESP_SPP_ROLE_SLAVE, 1, "") != ESP_OK)
{
return false;
}
return true;
}
void NodeESP::bluetooth_write(char *buf, int len)
{
esp_spp_write(bt_handle, len, (uint8_t*)buf);
}
void NodeESP::bluetooth_callback(esp_spp_cb_event_t event, esp_spp_cb_param_t *param)
{
switch (event)
{
case ESP_SPP_SRV_OPEN_EVT:
{
NodeESP* self = (NodeESP*) example_obj;
self->bt_handle = param->srv_open.handle;
break;
}
case ESP_SPP_CL_INIT_EVT:
esp_bt_gap_set_scan_mode(ESP_BT_SCAN_MODE_NONE);
break;
case ESP_SPP_OPEN_EVT:
break;
case ESP_SPP_DATA_IND_EVT:
{
NodeESP* self = (NodeESP*) example_obj;
//char trig = self->parse_data(param->data_ind.data, param->data_ind.len);
self->act_message((char*)param->data_ind.data);
break;
}
case ESP_SPP_CLOSE_EVT:
esp_bt_gap_set_scan_mode(ESP_BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE);
case ESP_SPP_WRITE_EVT:
break;
case ESP_SPP_CONG_EVT:
break;
}
return;
}
//---Parsing functions-------------------------------
//TODO: Add circular buffer
char NodeESP::parse_data(uint8_t *rcvd, uint16_t len)
{
char body_buf[RCV_BUF_SIZE];
uint8_t len_buf[2];
uint8_t rcvd_bytes = 0;
uint8_t recive_stage = 0;
uint16_t calc_len = 0;
//Loop through each char recieved
for (uint16_t i = 0; i < len; i++)
{
uint8_t cur_char = rcvd[i];
rcvd_bytes++;
//If we haven't recieved the length or body
//And we recieve a start delim
if (recive_stage == 0 && cur_char == 0x7E)
{
//We are ready to read the length of the message (next 2 bytes)
recive_stage = 1;
rcvd_bytes = 0;
}
else if (recive_stage == 1)//If we are recieving the length
{
//Next two bytes get stored in the length buffer
len_buf[rcvd_bytes - 1] = cur_char;
//If we have recieved the 2 length bytes
if (rcvd_bytes == 2)
{
//Decode the length
calc_len = ((uint16_t)len_buf[0]) << 8 | (uint16_t)len_buf[1];
//Ready to recieve the body
recive_stage = 2;
rcvd_bytes = 0;
}
}
else if (recive_stage == 2)//If we are recieving the body
{
//Store the recieved bytes
body_buf[rcvd_bytes - 1] = cur_char;
if (rcvd_bytes == calc_len)
{
//Reset
rcvd_bytes = 0;
recive_stage = 0;
return(cur_char);
}
}
}
return '/';
}
void NodeESP::add_func(char* command, callback_function func)
{
Serial.println("Added new callback...");
std::pair<char*, callback_function>* new_pair = new std::pair<char*, callback_function>(command,func);
this->funcs.insert(*new_pair);
}
void NodeESP::act_message(char* trigger)
{
Serial.println("[act message]");
Serial.print("funcs.size() = ");
Serial.print(this->funcs.size()); //Always returns the correct size all of the time
Serial.println("");
for (std::map<char*, callback_function>::iterator it = this->funcs.begin();
it != this->funcs.end();
++it)
{
Serial.print("funcs[i].first = ");
Serial.print(it->first); //Line 336, accessing the first element of the iterator
Serial.println(""); //This works properly when I don't call the function through the callback.
if (trigger == it->first)
{
(it->second)();
}
}
}
String NodeESP::toString(const IPAddress & address)
{
return String(address[0]) + "." + address[1] + "." + address[2] + "." + address[3];
}
Example Program
Here is an example program that shows what I want to happen and that it works correctly.
// Example program
#include <iostream>
#include <string>
#include <map>
//Global object to access the instance from within static function
using namespace std;
class example
{
public:
example();
void begin();
typedef void (*callback_function)(void);
std::map<char, callback_function> funcs;
void add_func(char command, callback_function func);
void do_something(char trigger);
static void example_callback(char cb);
};
void* example_obj;
example::example()
{
example_obj = (void*)this;
}
void example::begin()
{
//Set up everything to do with external library, peripherals, ect...
//Register the callback with the library
//register_callback(&example_callback);
}
//Static func that does something
void example::example_callback(char cb)
{
//Have to do this because the args need to match the library's requirements
example* self = (example*) example_obj;
//Do some processing...
self->do_something(cb);
}
//Add a function pointer and its command char to the vector
void example::add_func(char command, callback_function func)
{
std::pair<char, callback_function>* new_pair = new std::pair<char, callback_function>(command,func);
this->funcs.insert(*new_pair);
}
//This function is called from the callback
void example::do_something(char trigger)
{
cout << "funcs.size() = "<< (this->funcs.size())<<"\n"; //Always returns the correct size all of the time
for (std::map<char, callback_function>::iterator it = this->funcs.begin();
it != this->funcs.end();
++it)
{
cout << "funcs[i].first = "<< (it->first)<<"\n";
if (trigger == it->first)
{
(it->second)();
}
}
}
void callback()
{
cout << "Callback Called!";
}
int main()
{
cout << "Starting program...\n";
cout << "Creating example object...\n";
example ex;
cout << "Adding some callbacks...\n";
ex.add_func('x',callback);
ex.add_func('y',callback);
ex.add_func('z',callback);
cout << "Firing callback...\n";
example::example_callback('z');
}
How do I take images with Sapera SDK and transfer the image data from the SapBuffer object to vector?
To handle images taken by camera using Sapera, you should make specialization of SapProcessing class, which is used to process buffers. Otherwise the buffer is cleared automatically after each frame, and you lose the data.
The imaging process goes as follows:
You call Grab() on the camera object to start imaging
After each frame has been taken, transfer callback is called. Here you request your SapProcessing object to process the next frame.
Run() function of your SapProcessing object is called. Here you can read data from the buffer.
After Run() function, processing callback is called.
When you have received enough frames, call Freeze() to stop imaging.
This example code takes images using default settings on the camera (monochrome 8-bit pixel format).
#include <string>
#include <vector>
#include <memory>
#include <stdexcept>
#include <iostream>
#include <iomanip>
#include <atomic>
#include "SapClassBasic.h"
// Helper function to find the camera by its serial number
SapAcqDevice getDeviceBySN(const std::string& sn)
{
char serverName[CORSERVER_MAX_STRLEN];
char serialNumberName[2048];
const int serverCount = SapManager::GetServerCount();
for (int i = 0; i < serverCount; i++) {
if (SapManager::GetResourceCount(i, SapManager::ResourceAcqDevice) != 0)
{
SapManager::GetServerName(i, serverName, sizeof(serverName));
SapAcqDevice camera(serverName);
if (!camera.Create()) {
throw std::runtime_error("Failed to create camera object.");
}
int featureCount;
if (camera.GetFeatureCount(&featureCount) && featureCount > 0)
{
if (camera.GetFeatureValue("DeviceID", serialNumberName, sizeof(serialNumberName))
&& serialNumberName == sn)
{
return camera;
}
}
camera.Destroy();
}
}
const auto errorStr = "Camera \"" + sn + "\" was not found.";
throw std::runtime_error(errorStr.c_str());
}
class SapMyProcessing : public SapProcessing
{
public:
SapMyProcessing(SapBuffer* pBuffers, SapProCallback pCallback, void* pContext);
virtual ~SapMyProcessing();
protected:
virtual BOOL Run();
};
SapMyProcessing::SapMyProcessing(SapBuffer* pBuffers, SapProCallback pCallback, void* pContext)
: SapProcessing(pBuffers, pCallback, pContext)
{}
SapMyProcessing::~SapMyProcessing()
{
if (m_bInitOK) Destroy();
}
BOOL SapMyProcessing::Run()
{
// Get the current buffer index
const int proIndex = GetIndex();
// If this is not true, buffer has overflown
SapBuffer::State state;
bool goodContent = m_pBuffers->GetState(proIndex, &state)
&& state == SapBuffer::StateFull;
if (goodContent) {
void *inAddress = nullptr;
m_pBuffers->GetAddress(proIndex, &inAddress);
int inSize = 0;
m_pBuffers->GetSpaceUsed(proIndex, &inSize);
// Width, height and pixel format are received from the camera
const int width = m_pBuffers->GetWidth();
const int height = m_pBuffers->GetHeight();
const auto format = m_pBuffers->GetFormat();
const int outSize = width * height;
// Skip unexpected pixel format or incomplete frame
goodContent = format == SapFormatMono8
&& inSize == outSize;
if (goodContent) {
// Copy data to vector
std::vector<uint8_t> outBuffer(outSize);
std::copy((uint8_t*)inAddress, (uint8_t*)(inAddress) + outSize, outBuffer.begin());
// Print the first line
for (int i = 0; i < width; i++) {
std::cout << std::hex << int(outBuffer[i]);
}
std::cout << std::endl << std::endl;
}
}
return TRUE;
}
// Information to pass to callbacks
struct TransferContext {
std::atomic_int frameGrabCount = 0, frameProcessingCount = 0;
std::shared_ptr<SapMyProcessing> processing;
};
void transferCallback(SapXferCallbackInfo *info)
{
auto context = (TransferContext*)info->GetContext();
context->frameGrabCount++;
if (!info->IsTrash()) {
// Execute Run() for this frame
context->processing->ExecuteNext();
}
}
// Processing callback is called after Run()
void processingCallback(SapProCallbackInfo* info)
{
auto context = (TransferContext*)info->GetContext();
// Processing has finished
context->frameProcessingCount++;
}
// The main imaging function
void grab(const std::string& serialNumber)
{
// Number of frames to receive from the camera
const int maxFrameCount = 10;
TransferContext context;
auto camera = getDeviceBySN(serialNumber);
std::unique_ptr<SapBuffer> buffer
= std::make_unique<SapBufferWithTrash>(maxFrameCount, &camera);
std::unique_ptr<SapTransfer> transfer
= std::make_unique<SapAcqDeviceToBuf>(&camera, buffer.get(), transferCallback, &context);
context.processing = std::make_shared<SapMyProcessing>(buffer.get(), processingCallback, &context);
auto cleanup = [&]() {
if (context.processing) context.processing->Destroy();
if (transfer) transfer->Destroy();
if (buffer) buffer->Destroy();
camera.Destroy();
};
try {
if (!buffer->Create()) {
throw std::runtime_error("Failed to create buffer object.");
}
if (!transfer->Create()) {
throw std::runtime_error("Failed to create transfer object.");
}
if (!context.processing->Create()) {
throw std::runtime_error("Failed to create processing object.");
}
transfer->SetAutoEmpty(false);
context.processing->SetAutoEmpty(true);
context.processing->Init();
transfer->Grab();
// Wait for the camera to grab all frames
while (context.frameGrabCount < maxFrameCount);
transfer->Freeze();
if (!transfer->Wait(5000)) {
throw std::runtime_error("Failed to stop grab.");
}
// Wait for processing to complete
while (context.frameProcessingCount < maxFrameCount);
cleanup();
}
catch (...) {
cleanup();
throw;
}
}
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
I write a program to capture the packets using Library "Winpcap". ( How to complie WinPcap with VS2010? Please see this link: http://www.rhyous.com/2011/11/12/how-to-compile-winpcap-with-visual-studio-2010/)
I want to capture the packet(using pcap_loop()) and at the same time I process the packet in the same function void D. The code int x = 1; in function void D is only an easy example which represents some functions that can process the packet. The thread t calls void D. The thread tt calls the function startCap() which captures the packet.
I want to emphasize my question: I debug the program and it stops at the position of pcap_loop() in thread tt until pcap_loop() finishes. I set the parameter of pcap_loop() to make it to run without end because I need continious capture of packet. The result is that the program will not go to the next step int x = 1;. I want to run int x = 1; as thread tt runs. In a word, I wish that the both threads t and tt run at the same time. But the program runs only on thread tt without jumping out and run on thread t.
PS 1: int x = 1; keeps staying better in void D.
PS 2: There's no erros in compiling and debuging. But the program falls into the function pcap_loop which is in the thread tt.
Did I make my question clear?
The whole code:
Data.h
#ifndef DATA_H
#define DATA_H
#include <pcap/pcap.h>
#include <vector>
using namespace std;
struct PcapDevice
{
string name;
string description;
};
class Data
{
public:
Data();
~Data();
bool findDevices(vector<PcapDevice> &deviceList );
bool openDevices(const char * device);
void processPacket();
void startCap();
private:
pcap_t* inData;
char errbuf[256];
bool isCapturing;
pcap_if_t * m_pDevs;
};
#endif // DATA_H
Data.cpp
#include "Data.h"
#include <iostream>
// define pcap callback
void capture_callback_handler(unsigned char *userData, const struct pcap_pkthdr* pkthdr, const unsigned char * packet)
{
((Data*) userData)->processPacket();
}
Data::Data()
{
memset(errbuf,0,PCAP_ERRBUF_SIZE);
isCapturing = false;
inData = NULL;
m_pDevs = NULL;
}
Data::~Data()
{
}
// process the packet
void Data::processPacket()
{
return ;
}
// find local adapter
bool Data::findDevices(vector<PcapDevice> &deviceList )
{
m_pDevs = NULL;
int res = pcap_findalldevs(&m_pDevs, errbuf);
if(0 == res)
{
pcap_if_t * pIter = m_pDevs;
while(pIter != NULL)
{
PcapDevice device;
device.description = pIter->description;
device.name = pIter->name;
deviceList.push_back(device);
pIter = pIter->next;
}
return true;
}
else
{
printf("PCAP: no devices found\n");
}
return false;
}
// open the adapter
bool Data::openDevices(const char *device)
{
if ( (inData = pcap_open_live(device, 8192, 1, 512, errbuf)) == NULL)
{
return false;
}
return true;
}
// start the process of capturing the packet
void Data::startCap()
{
if ( inData == NULL ){
fprintf(stderr, "ERROR: no source set\n" );
return;
}
// free the list of device(adapter)
pcap_freealldevs(m_pDevs);
Data* data = this;
isCapturing = true;
// capture in the loop
if ( pcap_loop(inData, -1, capture_callback_handler, (unsigned char *) data) == -1)
{
fprintf(stderr, "ERROR: %s\n", pcap_geterr(inData) );
isCapturing = false;
}
}
main.cpp
#include <WinSock2.h>
#include <Windows.h>
#include <time.h>
#include "Data.h"
#include <boost\thread\thread.hpp>
#include <boost\function.hpp>
struct parameter
{
Data* pData;
};
void D(void* pParam)
{
// init the parameter
parameter* pUserParams = (parameter*)pParam;
boost::function<void()> f;
// the capture thread will be started
f = boost::bind(&Data::startCap, pUserParams->pData);
boost::thread tt(f);
tt.join();
// I want to work on the packet at the same time, the code "int x=1" is only an easy example
// and it represents a series of functions that can process the packet. I want to run those function as the thread tt runs.
int x = 1;
}
void main()
{
Data oData;
parameter pPara ;
pPara.pData = &oData;
std::vector<PcapDevice> DevList;
oData.findDevices(DevList);
int num = DevList.size()-1;
oData.openDevices(DevList[num].name.c_str());
boost::thread t(D,(void*)&pPara);
t.join();
}
Calling tt.join() will wait until the thread finishes (that is, startCap() returns) before executing the next statement.
You can simply put your int x = 1; before the join(); however, the thread may have not have even started at that point. If you want to ensure the thread is running, or up to a certain point before processing int x = 1; you can use a condition_variable:
The condition_variable class is a synchronization primitive that can be used to block a thread, or multiple threads at the same time, until:
a notification is received from another thread
void Data::startCap(std::condition_variable& cv)
{
if ( inData == NULL ){
fprintf(stderr, "ERROR: no source set\n" );
return;
}
// free the list of device(adapter)
pcap_freealldevs(m_pDevs);
Data* data = this;
isCapturing = true;
// Notify others that we are ready to begin capturing packets
cv.notify_one();
// capture in the loop
if ( pcap_loop(inData, -1, capture_callback_handler, (unsigned char *) data) == -1)
{
fprintf(stderr, "ERROR: %s\n", pcap_geterr(inData) );
isCapturing = false;
}
}
void D(void* pParam)
{
// init the parameter
parameter* pUserParams = (parameter*)pParam;
// Create conditional_variable
std::conditional_variable cv;
// Pass the conditional_variable by reference to the thread
boost::thread tt(&Data::startCap, pUserParams->pData, std::ref(cv));
// Wait until the thread notifies us it's ready:
cv.wait();
// Process packets etc.
int x = 1;
// Wait for the thread to finish
tt.join();
}
Now D() will start the thread tt and then wait until startCaps has reached a certain point (where it calls notify_one()) before we continuing doing things.
I am using boost library to develop a asynchronous udp communication. A data received at the receiver side is being precessed by another thread. Then my problem is when I read the received data in another thread rather than the receiver thread it self it gives a modified data or updated data which is not the data that is supposed to be.
My code is working on unsigned character buffer array at sender side and receiver side. The reason is I need consider unsigned character buffer as a packet of data
e.g buffer[2] = Engine_start_ID
/* global buffer to store the incomming data
unsigned char received_buffer[200];
/*
global buffer accessed by another thread
which contains copy the received_buffer
*/
unsigned char read_hmi_buffer[200];
boost::mutex hmi_buffer_copy_mutex;
void udpComm::start_async_receive() {
udp_socket.async_receive_from(
boost::asio::buffer(received_buffer, max_length), remote_endpoint,
boost::bind(&udpComm::handle_receive_from, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
/* the data received is stored in the unsigned char received_buffer data buffer*/
void udpComm::handle_receive_from(const boost::system::error_code& error,
size_t bytes_recvd) {
if (!error && bytes_recvd > 0) {
received_bytes = bytes_recvd;
hmi_buffer_copy_mutex.lock();
memcpy(&read_hmi_buffer[0], &received_buffer[0], received_bytes);
hmi_buffer_copy_mutex.unlock();
/*data received here is correct 'cus i printed in the console
checked it
*/
cout<<(int)read_hmi_buffer[2]<<endl;
}
start_async_receive();
}
/* io_service is running in a thread
*/
void udpComm::run_io_service() {
udp_io_service.run();
usleep(1000000);
}
The above code is the asynchronous udp communication running a thread
/* My second thread function is */
void thread_write_to_datalink()
{ hmi_buffer_copy_mutex.lock();
/* here is my problem begins*/
cout<<(int)read_hmi_buffer[2]<<endl;
hmi_buffer_copy_mutex.unlock();
/* all data are already changed */
serial.write_to_serial(read_hmi_buffer, 6);
}
/* threads from my main function
are as below */
int main() {
receive_from_hmi.start_async_receive();
boost::thread thread_receive_from_hmi(&udpComm::run_io_service,
&receive_from_hmi);
boost::thread thread_serial(&thread_write_to_datalink);
thread_serial.join();
thread_receive_from_hmi.join();
return 0;
}
/* The Serial_manager class contains functions for writting and reading from serial port*/
#include <iostream>
#include <boost/thread.hpp>
#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
using namespace boost::asio;
class Serial_manager {
public:
Serial_manager(boost::asio::io_service &serial_io_service,char *dev_name);
void open_serial_port();
void write_to_serial(void *data, int size);
size_t read_from_serial(void *data, int size);
void handle_serial_exception(std::exception &ex);
virtual ~Serial_manager();
void setDeviceName(char* deviceName);
protected:
io_service &port_io_service;
serial_port datalink_serial_port;
bool serial_port_open;
char *device_name;
};
void Serial_manager::setDeviceName(char* deviceName) {
device_name = deviceName;
}
Serial_manager::Serial_manager(boost::asio::io_service &serial_io_service,char *dev_name):
port_io_service(serial_io_service),
datalink_serial_port(serial_io_service) {
device_name = dev_name;
serial_port_open = false;
open_serial_port();
}
void Serial_manager::open_serial_port() {
bool temp_port_status = false;
bool serial_port_msg_printed = false;
do {
try {
datalink_serial_port.open(device_name);
temp_port_status = true;
} catch (std::exception &ex) {
if (!serial_port_msg_printed) {
std::cout << "Exception-check the serial port device "
<< ex.what() << std::endl;
serial_port_msg_printed = true;
}
datalink_serial_port.close();
temp_port_status = false;
}
} while (!temp_port_status);
serial_port_open = temp_port_status;
std::cout <<std::endl <<"serial port device opened successfully"<<std::endl;
datalink_serial_port.set_option(serial_port_base::baud_rate(115200));
datalink_serial_port.set_option(
serial_port_base::flow_control(
serial_port_base::flow_control::none));
datalink_serial_port.set_option(
serial_port_base::parity(serial_port_base::parity::none));
datalink_serial_port.set_option(
serial_port_base::stop_bits(serial_port_base::stop_bits::one));
datalink_serial_port.set_option(serial_port_base::character_size(8));
}
void Serial_manager::write_to_serial(void *data, int size) {
boost::asio::write(datalink_serial_port, boost::asio::buffer(data, size));
}
size_t Serial_manager::read_from_serial(void *data, int size) {
return boost::asio::read(datalink_serial_port, boost::asio::buffer(data, size));
}
void Serial_manager::handle_serial_exception(std::exception& ex) {
std::cout << "Exception-- " << ex.what() << std::endl;
std::cout << "Cannot access data-link, check the serial connection"
<< std::endl;
datalink_serial_port.close();
open_serial_port();
}
Serial_manager::~Serial_manager() {
// TODO Auto-generated destructor stub
}
I think my area of problem is about thread synchronization and notification and I will be happy if you help me. You should not worry about the sender it is works perfectly as I already checked it the data is received at the receiver thread. I hope you understand my question.
Edit: Here is the modification.My whole idea here is to develop a simulation for the Manual flight control so according my design i have client application that sends commands through
udp communication. At the receiver side intended to use 3 threads. one thread receives input from sticks i.e void start_hotas() the second thread is a thread that receives commands from sender(client): void udpComm::run_io_service() and 3rd is the void thread_write_to_datalink().
/* a thread that listens for input from sticks*/
void start_hotas() {
Hotas_manager hotasobj;
__s16 event_value; /* value */
__u8 event_number; /* axis/button number */
while (1) {
hotasobj.readData_from_hotas();
event_number = hotasobj.getJoystickEvent().number;
event_value = hotasobj.getJoystickEvent().value;
if (hotasobj.isAxisPressed()) {
if (event_number == 0) {
aileron = (float) event_value / 32767;
} else if (event_number == 1) {
elevator = -(float) event_value / 32767;
} else if (event_number == 2) {
rudder = (float) event_value / 32767;
} else if (event_number == 3) {
brake_left = (float) (32767 - event_value) / 65534;
} else if (event_number == 4) {
} else if (event_number == 6) {
} else if (event_number == 10) {
} else if (event_number == 11) {
} else if (event_number == 12) {
}
} else if (hotasobj.isButtonPressed()) {
}
usleep(1000);
}
}
/*
* Hotas.h
*
* Created on: Jan 31, 2013
* Author: metec
*/
#define JOY_DEV "/dev/input/js0"
#include <iostream>
#include <boost/thread.hpp>
#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <linux/joystick.h>
bool message_printed = false;
bool message2_printed = false;
class Hotas_manager {
public:
Hotas_manager();
virtual ~Hotas_manager();
void open_hotas_device();
/*
*
* read from hotas input
* used to the updated event data and status of the joystick from the
* the file.
*
*/
void readData_from_hotas();
js_event getJoystickEvent() {
return joystick_event;
}
int getNumOfAxis() {
return num_of_axis;
}
int getNumOfButtons() {
return num_of_buttons;
}
bool isAxisPressed() {
return axis_pressed;
}
bool isButtonPressed() {
return button_pressed;
}
int* getAxis() {
return axis;
}
char* getButton() {
return button;
}
private:
int fd;
js_event joystick_event;
bool hotas_connected;
int num_of_axis;
int num_of_buttons;
int version;
char devName[80];
/*
* the the variables below indicates
* the state of the joystick.
*/
int axis[30];
char button[30];
bool button_pressed;
bool axis_pressed;
};
Hotas_manager::Hotas_manager() {
// TODO Auto-generated constructor stub
hotas_connected = false;
open_hotas_device();
std::cout << "joystick device detected" << std::endl;
}
Hotas_manager::~Hotas_manager() {
// TODO Auto-generated destructor stub
}
void Hotas_manager::open_hotas_device() {
bool file_open_error_printed = false;
while (!hotas_connected) {
if ((fd = open(JOY_DEV, O_RDONLY)) > 0) {
ioctl(fd, JSIOCGAXES, num_of_axis);
ioctl(fd, JSIOCGBUTTONS, num_of_buttons);
ioctl(fd, JSIOCGVERSION, version);
ioctl(fd, JSIOCGNAME(80), devName);
/*
* NON BLOCKING MODE
*/
ioctl(fd, F_SETFL, O_NONBLOCK);
hotas_connected = true;
} else {
if (!file_open_error_printed) {
std::cout << "hotas device not detected. check "
"whether it is "
"plugged" << std::endl;
file_open_error_printed = true;
}
close(fd);
hotas_connected = false;
}
}
}
void Hotas_manager::readData_from_hotas() {
int result;
result = read(fd, &joystick_event, sizeof(struct js_event));
if (result > 0) {
switch (joystick_event.type & ~JS_EVENT_INIT) {
case JS_EVENT_AXIS:
axis[joystick_event.number] = joystick_event.value;
axis_pressed = true;
button_pressed = false;
break;
case JS_EVENT_BUTTON:
button[joystick_event.number] = joystick_event.value;
button_pressed = true;
axis_pressed = false;
break;
}
message2_printed = false;
message_printed = false;
} else {
if (!message_printed) {
std::cout << "problem in reading the stick file" << std::endl;
message_printed = true;
}
hotas_connected = false;
open_hotas_device();
if (!message2_printed) {
std::cout << "stick re-connected" << std::endl;
message2_printed = true;
}
}
}
I updated the main function to run 3 threads .
int main() {
boost::asio::io_service receive_from_hmi_io;
udpComm receive_from_hmi(receive_from_hmi_io, 6012);
receive_from_hmi.setRemoteEndpoint("127.0.0.1", 6011);
receive_from_hmi.start_async_receive();
boost::thread thread_receive_from_hmi(&udpComm::run_io_service,
&receive_from_hmi);
boost::thread thread_serial(&thread_write_to_datalink);
boost::thread thread_hotas(&start_hotas);
thread_hotas.join();
thread_serial.join();
thread_receive_from_hmi.join();
return 0;
}
The void thread_write_to_datalink() also writes the data come from the hotas_manager(joysticks).
void thread_write_to_datalink() {
/*
* boost serial communication
*/
boost::asio::io_service serial_port_io;
Serial_manager serial(serial_port_io, (char*) "/dev/ttyUSB0");
cout << "aileron " << "throttle " << "elevator " << endl;
while (1) {
// commands from udp communication
serial.write_to_serial(read_hmi_buffer, 6);
// data come from joystick inputs
//cout << aileron<<" "<<throttle<<" "<<elevator<< endl;
memcpy(&buffer_manual_flightcontrol[4], &aileron, 4);
memcpy(&buffer_manual_flightcontrol[8], &throttle, 4);
memcpy(&buffer_manual_flightcontrol[12], &elevator, 4);
unsigned char temp;
try {
serial.write_to_serial(buffer_manual_flightcontrol, 32);
//serial.write_to_serial(buffer_manual_flightcontrol, 32);
} catch (std::exception& exp) {
serial.handle_serial_exception(exp);
}
try {
serial.write_to_serial(buffer_payloadcontrol, 20);
} catch (std::exception& exp) {
serial.handle_serial_exception(exp);
}
usleep(100000);
}
}
My question is how better can I design to synchronize these 3 threads. If your answer says you do not need to use 3 threads I need you to tell me how.
Let's back up a little bit from multi-threading, your program mixes synchronous and asynchronous operations. You don't need to do this, as it will only cause confusion. You can asynchronously write the buffer read from the UDP socket to the serial port. This can all be achieved with a single thread running the io_service, eliminating any concurrency concerns.
You will need to add buffer management to keep the data read from the socket in scope for the lifetime of the async_write for the serial port, study the async UDP server as an example. Also study the documentation, specifically the requirements for buffer lifetime in async_write
buffers
One or more buffers containing the data to be written.
Although the buffers object may be copied as necessary, ownership of
the underlying memory blocks is retained by the caller, which must
guarantee that they remain valid until the handler is called.
Once you have completed that design, then you can move to more advanced techniques such as a thread pool or multiple io_services.
You need to make your access to read_hmi_buffer synchronized.
Therefore you need a mutex (std::mutex, pthread_mutex_t, or the windows equivalent), to lock onto whenever a piece of code read or write in that buffer.
See this question for a few explanations on the concept and links to other tutorials.