Skip to content

Commit

Permalink
Merge remote-tracking branch 'openbci/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrey1994 committed Nov 4, 2019
2 parents f2809c8 + 5493824 commit 7cd7dd3
Show file tree
Hide file tree
Showing 19 changed files with 127 additions and 51 deletions.
2 changes: 1 addition & 1 deletion GanglionBLEAPI/GanglionLib/Ganglion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class Ganglion
private GattCharacteristic send_characteristic = null;
private GattCharacteristic receive_characteristic = null;
private GattCharacteristic disconnect_characteristic = null;
private TimeSpan timeout = TimeSpan.FromSeconds (5);
private TimeSpan timeout = TimeSpan.FromSeconds (6);
private Queue<BoardData> data_queue = new Queue<BoardData> ();

public int open_ganglion ()
Expand Down
Binary file modified GanglionBLEAPI/Win32/Release/GanglionLib.dll
Binary file not shown.
Binary file modified GanglionBLEAPI/Win32/Release/GanglionLibNative32.dll
Binary file not shown.
Binary file modified GanglionBLEAPI/Win32/Release/GanglionTest.exe
Binary file not shown.
Binary file modified GanglionBLEAPI/x64/Release/GanglionLib.dll
Binary file not shown.
Binary file modified GanglionBLEAPI/x64/Release/GanglionLibNative64.dll
Binary file not shown.
Binary file modified GanglionBLEAPI/x64/Release/GanglionTest.exe
Binary file not shown.
10 changes: 6 additions & 4 deletions docs/AskHelp.rst
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
Ask Help
=========

Report Issues, Feature Requests, Contact Info
-----------------------------------------------

- Preferred: create issues or feature requests in our `GitHub Page <https://github.com/Andrey1994/brainflow>`_
- `Andrey\'s email <a1994ndrey@gmail.com>`_
- For questions about OpenBCI boards use `OpenBCI Forum <https://openbci.com/forum/>`_ and `OpenBCI Docs <https://docs.openbci.com/docs/Welcome>`_
- For questions about BrainFlow integration to other apps and frameworks use `Andrey\'s email <a1994ndrey@gmail.com>`_

Contributors
-------------

- Andrey Parfenov (original author, maintainer)
- Daniel Lasry
- OpenBCI
.. ghcontributors:: Andrey1994/brainflow
8 changes: 4 additions & 4 deletions docs/BrainFlowDev.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ CI and tests

If you want to commit to core module of brainflow project please check that all tests are passed, you can enable `Travis CI <https://travis-ci.com/>`_ and `AppVeyour <https://ci.appveyor.com>`_ for your fork of BrainFlow to run tests automatically, or check CI status directly in your PR

Also you can run integration tests manually for any board even if you dont have real hardware, see :ref:`brainflow_emulator_link` for details
Also you can run integration tests manually for any board even if you dont have real hardware, check BrainFlow Emulator page for details.

Pull Requests
--------------

Just try to briefly explain a goal of this PR

Instructions to add new boards to brainflow
Instructions to add new boards to BrainFlow
---------------------------------------------

- add new object creation to board_controller.cpp
Expand All @@ -44,6 +44,6 @@ Instructions to add new boards to brainflow
- add information about your board to brainflow_boards.json
- add new files to CmakeLists.txt

You've just written Python, Java, C#, R, C++ ... SDKs for your board!
**You've just written Python, Java, C#, R, C++ ... SDKs for your board! Also now you can use your new board with applications and frameworks which use BrainFlow API.**

To enable automation testing you should develop a simple emulator for your new board and add tests to .travis.yml and appveyour.yml, also make sure that all current tests are passed and feel free to send PR
To enable automation testing you should develop a simple emulator for your new board and add tests to .travis.yml and appveyour.yml, also make sure that all current tests are passed and feel free to send PR.
2 changes: 2 additions & 0 deletions docs/DataFormatDesc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ Exact format for this array is board specific but to keep API uniform we have me
get_timestamp_channel (board_id)
# and so on
**For some boards like Cyton, Ganglion and others we can not separate EMG, EEG, EDA and ECG and in this case we return exactly the same array for all these methods but for some boards EMG and EEG channels differs**

Using methods above you can write completely board agnostic code and switch boards using single parameter! Even if you have only one board using these methods you can easily switch to Synthetic board for development and run code without real hardware.

Special channels for Cyton Based boards
Expand Down
6 changes: 2 additions & 4 deletions docs/SupportedBoards.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,12 @@ To choose this board in BoardShim class please specify:

Supported platforms:

- Windows >= 10
- Linux
- MacOS
- Only Windows 10, older versions of Windows dont support Bluetooth Low Energy API

Additional configuration:

- If you use python bindings installed from PYPI on Windows you may need to install `redist_x64 <https://aka.ms/vs/16/release/vc_redist.x64.exe>`_ or `redist_x86 <https://aka.ms/vs/16/release/vc_redist.x86.exe>`_ (but more likely you have it preinstalled)
- For Linux/MacOS you need to use dongle, for Windows dongle is not required if you have Bluetooth on your laptop
- It works without dongle if your PC has Bluetooth, if not - use dongle

Board Spec:

Expand Down
4 changes: 2 additions & 2 deletions docs/UserAPI.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
User API
==========

BrainFlow User API has no classes for all supported boardm instead there are only two classes:
BrainFlow User API has no classes for all supported boards instead there are only two classes:
- BoardShim to read data from a board
- DataFilter to perform signal processing

Expand Down Expand Up @@ -109,7 +109,7 @@ R API Reference
R binding is a wrapper on top of Python binding which is implemented using `reticulate <https://rstudio.github.io/reticulate/>`_.
There are a few methods which allows you to create python objects and call their methods.

But reticulate translates numpy arrays to R arrays in a tricky way, and it prevents us to implement signal processing in R, so you for R you have to perform signal processing by yourself.
But reticulate translates numpy arrays to R arrays in a tricky way, and it prevents us to implement signal processing in R, so for R you have to perform signal processing by yourself.

Check R sample to see how to use it.

Expand Down
3 changes: 2 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@
# ones.
extensions = [
'sphinx.ext.autodoc',
'breathe'
'breathe',
'sphinxcontrib.ghcontributors'
]

# Breathe and Doxygen setup
Expand Down
1 change: 1 addition & 0 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ breathe==4.13.0
sphinx==2.0.1
guzzle_sphinx_theme
brainflow
sphinxcontrib-ghcontributors
4 changes: 2 additions & 2 deletions emulator/brainflow_emulator/novaxr_tcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,12 @@ def run (self):
raise TestFailureError ('failed to establish connection')

while self.keep_alive:
if self.package_num % 256 == 0:
if self.package_num % 255 == 0:
self.package_num = 0
# dirtiest hack ever but it doesnt work otherwise. seems like recv ignores timeout and it means that we send only 1 package
if self.package_num == 0:
try:
msg = self.conn.recv (128)
msg = self.conn.recv (1)
if msg:
logging.info ('received %s' % (msg))
if msg == Message.start_stream.value:
Expand Down
13 changes: 12 additions & 1 deletion src/board_controller/board_controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,18 @@ int prepare_session (int board_id, char *json_brainflow_input_params)
{
Board::board_logger->error (
"Board with id {} and the same config already exists", board_id);
return PORT_ALREADY_OPEN_ERROR;
return ANOTHER_BOARD_IS_CREATED_ERROR;
}
// for ganglion we load dll at runtime this dll has its own global state
// so we dont support multiple ganglions
for (auto iter = boards.begin (); iter != boards.end (); iter++)
{
auto key = iter->first;
if (key.first == GANGLION_BOARD)
{
Board::board_logger->error ("Only one Ganglion board is allowed");
return ANOTHER_BOARD_IS_CREATED_ERROR;
}
}

std::shared_ptr<Board> board = NULL;
Expand Down
60 changes: 39 additions & 21 deletions src/board_controller/openbci/ganglion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -415,38 +415,56 @@ int Ganglion::call_init ()

int Ganglion::call_open ()
{
int res = GanglionLibNative::CustomExitCodesNative::STATUS_OK;
if (this->use_mac_addr)
int res = GanglionLibNative::CustomExitCodesNative::GENERAL_ERROR;
#ifdef _WIN32
int num_attempts = 2;
#else
int num_attempts = 1;
#endif
// its bad but we need to make it as reliable as possible, once per ~200 runs it fails to
// open ganglion and I can not fix it in C# code(wo restart) so lets restart it here,
// bigger delay is better than failure!
for (int i = 0; i < num_attempts; i++)
{
safe_logger (spdlog::level::info, "search for {}", this->params.mac_address.c_str ());
DLLFunc func = this->dll_loader->get_address ("open_ganglion_mac_addr_native");
if (func == NULL)
safe_logger (spdlog::level::debug, "trying to open ganglion {}/{}", i, num_attempts);
if (this->use_mac_addr)
{
safe_logger (spdlog::level::err,
"failed to get function address for open_ganglion_mac_addr_native");
return GENERAL_ERROR;
safe_logger (spdlog::level::info, "search for {}", this->params.mac_address.c_str ());
DLLFunc func = this->dll_loader->get_address ("open_ganglion_mac_addr_native");
if (func == NULL)
{
safe_logger (spdlog::level::err,
"failed to get function address for open_ganglion_mac_addr_native");
return GENERAL_ERROR;
}
res = (func) (const_cast<char *> (params.mac_address.c_str ()));
}
res = (func) (const_cast<char *> (params.mac_address.c_str ()));
}
else
{
safe_logger (
spdlog::level::info, "mac address is not specified, try to find ganglion without it");
DLLFunc func = this->dll_loader->get_address ("open_ganglion_native");
if (func == NULL)
else
{
safe_logger (spdlog::level::info,
"mac address is not specified, try to find ganglion without it");
DLLFunc func = this->dll_loader->get_address ("open_ganglion_native");
if (func == NULL)
{
safe_logger (
spdlog::level::err, "failed to get function address for open_ganglion_native");
return GENERAL_ERROR;
}
res = (func) (NULL);
}
if (res == GanglionLibNative::CustomExitCodesNative::STATUS_OK)
{
safe_logger (
spdlog::level::err, "failed to get function address for open_ganglion_native");
return GENERAL_ERROR;
safe_logger (spdlog::level::info, "Ganglion is opened and paired");
break;
}
res = (func) (NULL);
// sanity check, e.g. device was found but services were not found
call_close ();
}
if (res != GanglionLibNative::CustomExitCodesNative::STATUS_OK)
{
safe_logger (spdlog::level::err, "failed to Open Ganglion Device {}", res);
return GENERAL_ERROR;
}
safe_logger (spdlog::level::info, "Found Ganglion Device");
return STATUS_OK;
}

Expand Down
59 changes: 50 additions & 9 deletions src/board_controller/openbci/novaxr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
#include "novaxr.h"
#include "openbci_helpers.h"

#ifndef _WIN32
#include <errno.h>
#endif


NovaXR::NovaXR (struct BrainFlowInputParams params) : Board ((int)NOVAXR_BOARD, params)
{
this->socket = NULL;
Expand Down Expand Up @@ -34,16 +39,22 @@ int NovaXR::prepare_session ()
safe_logger (spdlog::level::err, "ip address or ip protocol is empty");
return INVALID_ARGUMENTS_ERROR;
}
int port = 2390;
if (params.ip_port != 0)
{
safe_logger (spdlog::level::warn, "use port {} instead default", params.ip_port);
port = params.ip_port;
}
if (params.ip_protocol == (int)IpProtocolType::UDP)
{
socket = new SocketClient (params.ip_address.c_str (), 2390, (int)SocketType::UDP);
socket = new SocketClient (params.ip_address.c_str (), port, (int)SocketType::UDP);
}
else
{
socket = new SocketClient (params.ip_address.c_str (), 2390, (int)SocketType::TCP);
socket = new SocketClient (params.ip_address.c_str (), port, (int)SocketType::TCP);
}
int res = socket->connect ();
if (res != 0)
if (res != (int)SocketReturnCodes::STATUS_OK)
{
safe_logger (spdlog::level::err, "failed to init socket: {}", res);
return GENERAL_ERROR;
Expand All @@ -64,6 +75,14 @@ int NovaXR::config_board (char *config)
res = socket->send (config, len);
if (len != res)
{
if (res == -1)
{
#ifdef _WIN32
safe_logger (spdlog::level::err, "WSAGetLastError is {}", WSAGetLastError ());
#else
safe_logger (spdlog::level::err, "errno {} message {}", errno, strerror (errno));
#endif
}
safe_logger (spdlog::level::err, "Failed to config a board");
return BOARD_WRITE_ERROR;
}
Expand All @@ -90,8 +109,17 @@ int NovaXR::start_stream (int buffer_size)
}

// start streaming
if (socket->send ("b", 1) != 1)
int res = socket->send ("b", 1);
if (res != 1)
{
if (res == -1)
{
#ifdef _WIN32
safe_logger (spdlog::level::err, "WSAGetLastError is {}", WSAGetLastError ());
#else
safe_logger (spdlog::level::err, "errno {} message {}", errno, strerror (errno));
#endif
}
safe_logger (spdlog::level::err, "Failed to send a command to board");
return BOARD_WRITE_ERROR;
}
Expand Down Expand Up @@ -132,8 +160,17 @@ int NovaXR::stop_stream ()
is_streaming = false;
streaming_thread.join ();
this->state = SYNC_TIMEOUT_ERROR;
if (socket->send ("s", 1) != 1)
int res = socket->send ("s", 1);
if (res != 1)
{
if (res == -1)
{
#ifdef _WIN32
safe_logger (spdlog::level::err, "WSAGetLastError is {}", WSAGetLastError ());
#else
safe_logger (spdlog::level::err, "errno {} message {}", errno, strerror (errno));
#endif
}
safe_logger (spdlog::level::err, "Failed to send a command to board");
return BOARD_WRITE_ERROR;
}
Expand Down Expand Up @@ -200,6 +237,14 @@ void NovaXR::read_thread ()
while (keep_alive)
{
res = socket->recv (b, 72);
if (res == -1)
{
#ifdef _WIN32
safe_logger (spdlog::level::err, "WSAGetLastError is {}", WSAGetLastError ());
#else
safe_logger (spdlog::level::err, "errno {} message {}", errno, strerror (errno));
#endif
}
if (res != 72)
{
safe_logger (spdlog::level::trace, "unable to read 72 bytes, read {}", res);
Expand All @@ -210,10 +255,6 @@ void NovaXR::read_thread ()
// inform main thread that everything is ok and first package was received
if (this->state != STATUS_OK)
{
for (int i = 0; i < 72; i++)
{
safe_logger (spdlog::level::trace, "byte {} val {}", i, b[i]);
}
{
std::lock_guard<std::mutex> lk (this->m);
this->state = STATUS_OK;
Expand Down
6 changes: 4 additions & 2 deletions src/utils/socket_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,14 @@ int SocketClient::connect ()
}

// ensure that library will not hang in blocking recv/send call
DWORD timeout = 3000;
DWORD timeout = 5000;
setsockopt (connect_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof (timeout));
setsockopt (connect_socket, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof (timeout));
if (socket_type == (int)SocketType::TCP)
{
DWORD value = 1;
setsockopt (connect_socket, IPPROTO_TCP, TCP_NODELAY, (char *)&value, sizeof (value));
setsockopt (connect_socket, SOL_SOCKET, SO_KEEPALIVE, (char *)&value, sizeof (value));
if (::connect (connect_socket, (sockaddr *)&socket_addr, sizeof (socket_addr)) ==
SOCKET_ERROR)
{
Expand Down Expand Up @@ -285,7 +286,7 @@ int SocketClient::connect ()

// ensure that library will not hang in blocking recv/send call
struct timeval tv;
tv.tv_sec = 3;
tv.tv_sec = 5;
tv.tv_usec = 0;
setsockopt (connect_socket, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof (tv));
setsockopt (connect_socket, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof (tv));
Expand All @@ -294,6 +295,7 @@ int SocketClient::connect ()
{
int value = 1;
setsockopt (connect_socket, IPPROTO_TCP, TCP_NODELAY, &value, sizeof (value));
setsockopt (connect_socket, SOL_SOCKET, SO_KEEPALIVE, &value, sizeof (value));
if (::connect (connect_socket, (sockaddr *)&socket_addr, sizeof (socket_addr)) == -1)
{
return (int)SocketReturnCodes::CONNECT_ERROR;
Expand Down

0 comments on commit 7cd7dd3

Please sign in to comment.