Repository: brainflow-dev/brainflow (language: python)

Issue:
Add EmotiBit devices
https://www.emotibit.com/ 


Discussion / hints:
I will be happy to test and provide feedback.
Cool, seems there is code available here for reference or reuse: https://github.com/EmotiBit/ofxEmotiBit. 

We just need a good c++ dev now (I should really improve my skills :p). Generally it is Andrey doing the work.

I recall Andrey mentioned that the output data format of the emotibit can be changed flexibly and that this may be the primary challenge for BrainFlow integration.
I think there is some work to be done on the firmware before that.

Fix patch:
diff --git a/.github/workflows/run_android.yml b/.github/workflows/run_android.yml
index da30d09cf..8fa966c52 100644
--- a/.github/workflows/run_android.yml
+++ b/.github/workflows/run_android.yml
@@ -13,7 +13,7 @@ jobs:
         os: [ubuntu-latest]
 
     env:
-      ANDROID_NDK_VERSION: 21.4.7075529
+      ANDROID_NDK_VERSION: 25.1.8937393
 
     steps:
     - name: Clone Repository
@@ -45,7 +45,7 @@ jobs:
         for ABI in arm64-v8a armeabi-v7a x86 x86_64; do
           mkdir $GITHUB_WORKSPACE/libftdi/build-$ABI
           cd $GITHUB_WORKSPACE/libftdi/build-$ABI
-          cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${ANDROID_HOME}/ndk/${ANDROID_NDK_VERSION}/build/cmake/android.toolchain.cmake -DANDROID_ABI=$ABI -DCMAKE_BUILD_TYPE=Release -DANDROID_NATIVE_API_LEVEL=android-19 -DEXAMPLES=OFF -DFTDI_EEPROM=OFF -DLIBUSB_LIBRARIES=$GITHUB_WORKSPACE/libusb/android/libs/$ABI/libusb1.0.so -DLIBUSB_INCLUDE_DIR=$GITHUB_WORKSPACE/libusb/libusb -DCMAKE_INSTALL_PREFIX=$GITHUB_WORKSPACE/install-$ABI ..
+          cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${ANDROID_HOME}/ndk/${ANDROID_NDK_VERSION}/build/cmake/android.toolchain.cmake -DANDROID_ABI=$ABI -DCMAKE_BUILD_TYPE=Release -DANDROID_NATIVE_API_LEVEL=android-24 -DEXAMPLES=OFF -DFTDI_EEPROM=OFF -DLIBUSB_LIBRARIES=$GITHUB_WORKSPACE/libusb/android/libs/$ABI/libusb1.0.so -DLIBUSB_INCLUDE_DIR=$GITHUB_WORKSPACE/libusb/libusb -DCMAKE_INSTALL_PREFIX=$GITHUB_WORKSPACE/install-$ABI ..
           ninja
           ninja install
         done
@@ -54,7 +54,7 @@ jobs:
         for ABI in arm64-v8a armeabi-v7a x86 x86_64; do
           mkdir $GITHUB_WORKSPACE/build-$ABI
           cd $GITHUB_WORKSPACE/build-$ABI
-          cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${ANDROID_HOME}/ndk/${ANDROID_NDK_VERSION}/build/cmake/android.toolchain.cmake -DANDROID_ABI=$ABI -DCMAKE_BUILD_TYPE=Release -DANDROID_NATIVE_API_LEVEL=android-19 -DCMAKE_FIND_ROOT_PATH=$GITHUB_WORKSPACE/install-$ABI -DUSE_LIBFTDI=ON ..
+          cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${ANDROID_HOME}/ndk/${ANDROID_NDK_VERSION}/build/cmake/android.toolchain.cmake -DANDROID_ABI=$ABI -DCMAKE_BUILD_TYPE=Release -DANDROID_NATIVE_API_LEVEL=android-24 -DCMAKE_FIND_ROOT_PATH=$GITHUB_WORKSPACE/install-$ABI -DUSE_LIBFTDI=ON ..
           ninja
         done
     - name: Prepare Zip
diff --git a/cpp_package/src/board_shim.cpp b/cpp_package/src/board_shim.cpp
index 9e25b9c62..44ff1c224 100644
--- a/cpp_package/src/board_shim.cpp
+++ b/cpp_package/src/board_shim.cpp
@@ -557,6 +557,18 @@ std::vector<int> BoardShim::get_resistance_channels (int board_id, int preset)
     return std::vector<int> (channels, channels + len);
 }
 
+std::vector<int> BoardShim::get_magnetometer_channels (int board_id, int preset)
+{
+    int channels[MAX_CHANNELS];
+    int len = 0;
+    int res = ::get_magnetometer_channels (board_id, preset, channels, &len);
+    if (res != (int)BrainFlowExitCodes::STATUS_OK)
+    {
+        throw BrainFlowException ("failed to get board info", res);
+    }
+    return std::vector<int> (channels, channels + len);
+}
+
 std::string BoardShim::get_version ()
 {
     char version[64];
diff --git a/cpp_package/src/inc/board_shim.h b/cpp_package/src/inc/board_shim.h
index 790f116ae..3c7e94422 100644
--- a/cpp_package/src/inc/board_shim.h
+++ b/cpp_package/src/inc/board_shim.h
@@ -187,6 +187,13 @@ class BoardShim
      */
     static std::vector<int> get_resistance_channels (
         int board_id, int preset = (int)BrainFlowPresets::DEFAULT_PRESET);
+    /**
+     * get row indices which hold magnetometer data
+     * @param board_id board id of your device
+     * @throw BrainFlowException If this board has no such data exit code is UNSUPPORTED_BOARD_ERROR
+     */
+    static std::vector<int> get_magnetometer_channels (
+        int board_id, int preset = (int)BrainFlowPresets::DEFAULT_PRESET);
     /// release all currently prepared session
     static void release_all_sessions ();
     /// get brainflow version
diff --git a/csharp_package/brainflow/brainflow/board_controller_library.cs b/csharp_package/brainflow/brainflow/board_controller_library.cs
index 4f79b0cce..c3b483177 100644
--- a/csharp_package/brainflow/brainflow/board_controller_library.cs
+++ b/csharp_package/brainflow/brainflow/board_controller_library.cs
@@ -107,7 +107,8 @@ public enum BoardIds
         PIEEG_BOARD = 43,
         EXPLORE_4_CHAN_BOARD = 44,
         EXPLORE_8_CHAN_BOARD = 45,
-        GANGLION_NATIVE_BOARD = 46
+        GANGLION_NATIVE_BOARD = 46,
+        EMOTIBIT_BOARD = 47
     };
 
 
@@ -180,6 +181,8 @@ public static class BoardControllerLibrary64
         [DllImport ("BoardController.dll", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
         public static extern int get_resistance_channels (int board_id, int preset, int[] channels, int[] len);
         [DllImport ("BoardController.dll", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
+        public static extern int get_magnetometer_channels (int board_id, int preset, int[] channels, int[] len);
+        [DllImport ("BoardController.dll", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
         public static extern int get_exg_channels (int board_id, int preset, int[] channels, int[] len);
         [DllImport ("BoardController.dll", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
         public static extern int get_device_name (int board_id, int preset, byte[] name, int[] len);
@@ -281,6 +284,8 @@ public static class BoardControllerLibrary32
         public static extern int add_streamer (string streamer, int preset, int board_id, string input_json);
         [DllImport ("BoardController32.dll", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
         public static extern int delete_streamer (string streamer, int preset, int board_id, string input_json);
+        [DllImport ("BoardController32.dll", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
+        public static extern int get_magnetometer_channels (int board_id, int preset, int[] channels, int[] len);
     }
 
     public static class BoardControllerLibraryLinux
@@ -365,6 +370,8 @@ public static class BoardControllerLibraryLinux
         public static extern int add_streamer (string streamer, int preset, int board_id, string input_json);
         [DllImport ("libBoardController.so", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
         public static extern int delete_streamer (string streamer, int preset, int board_id, string input_json);
+        [DllImport ("libBoardController.so", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
+        public static extern int get_magnetometer_channels (int board_id, int preset, int[] channels, int[] len);
     }
 
     public static class BoardControllerLibraryMac
@@ -449,6 +456,8 @@ public static class BoardControllerLibraryMac
         public static extern int add_streamer (string streamer, int preset, int board_id, string input_json);
         [DllImport ("libBoardController.dylib", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
         public static extern int delete_streamer (string streamer, int preset, int board_id, string input_json);
+        [DllImport ("libBoardController.dylib", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
+        public static extern int get_magnetometer_channels (int board_id, int preset, int[] channels, int[] len);
     }
 
     public static class BoardControllerLibrary
@@ -1117,6 +1126,23 @@ public static int get_resistance_channels (int board_id, int preset, int[] chann
             return (int)BrainFlowExitCodes.GENERAL_ERROR;
         }
 
+        public static int get_magnetometer_channels (int board_id, int preset, int[] channels, int[] len)
+        {
+            switch (PlatformHelper.get_library_environment ())
+            {
+                case LibraryEnvironment.x64:
+                    return BoardControllerLibrary64.get_magnetometer_channels (board_id, preset, channels, len);
+                case LibraryEnvironment.x86:
+                    return BoardControllerLibrary32.get_magnetometer_channels (board_id, preset, channels, len);
+                case LibraryEnvironment.Linux:
+                    return BoardControllerLibraryLinux.get_magnetometer_channels (board_id, preset, channels, len);
+                case LibraryEnvironment.MacOS:
+                    return BoardControllerLibraryMac.get_magnetometer_channels (board_id, preset, channels, len);
+            }
+
+            return (int)BrainFlowExitCodes.GENERAL_ERROR;
+        }
+
         public static int release_all_sessions ()
         {
             switch (PlatformHelper.get_library_environment ())
diff --git a/csharp_package/brainflow/brainflow/board_shim.cs b/csharp_package/brainflow/brainflow/board_shim.cs
index 873318fc9..cca6c0dfb 100644
--- a/csharp_package/brainflow/brainflow/board_shim.cs
+++ b/csharp_package/brainflow/brainflow/board_shim.cs
@@ -580,6 +580,30 @@ public static int[] get_resistance_channels (int board_id, int preset = (int)Bra
             return result;
         }
 
+        /// <summary>
+        /// get magnetometer channels for this board
+        /// </summary>
+        /// <param name="board_id"></param>
+        /// <param name="preset">preset for device</param>
+        /// <returns>array of row nums</returns>
+        /// <exception cref="BrainFlowException">If this board has no such data exit code is UNSUPPORTED_BOARD_ERROR</exception>
+        public static int[] get_magnetometer_channels (int board_id, int preset = (int)BrainFlowPresets.DEFAULT_PRESET)
+        {
+            int[] len = new int[1];
+            int[] channels = new int[512];
+            int res = BoardControllerLibrary.get_magnetometer_channels (board_id, preset, channels, len);
+            if (res != (int)BrainFlowExitCodes.STATUS_OK)
+            {
+                throw new BrainFlowError (res);
+            }
+            int[] result = new int[len[0]];
+            for (int i = 0; i < len[0]; i++)
+            {
+                result[i] = channels[i];
+            }
+            return result;
+        }
+
         /// <summary>
         /// set log level, logger is disabled by default
         /// </summary>
diff --git a/docs/BuildBrainFlow.rst b/docs/BuildBrainFlow.rst
index 8cbeee0ea..da161d636 100644
--- a/docs/BuildBrainFlow.rst
+++ b/docs/BuildBrainFlow.rst
@@ -236,6 +236,11 @@ Now you can use BrainFlow SDK in your Android application!
 
 Note: Android Studio inline compiler may show red errors but it should be compiled fine with Gradle. To fix inline compiler you can use *File > Sync Project with Gradle Files* or click at *File > Invalidate Cache/Restart > Invalidate and Restart*
 
+Prebuild libraries for *jniLibs.zip* are complied using:
+
+- Android NDK 25.1.8937393
+- *-DANDROID_NATIVE_API_LEVEL=android-24*
+
 .. compound::
     
     For some API calls you need to provide additional permissions via manifest file of your application ::
@@ -268,13 +273,13 @@ Compilation instructions:
 
         # to prepare project(choose ABIs which you need)
         # for arm64-v8a
-        cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=E:\android-ndk-r21d-windows-x86_64\android-ndk-r21d\build\cmake\android.toolchain.cmake -DANDROID_NATIVE_API_LEVEL=android-19 -DANDROID_ABI=arm64-v8a ..
+        cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=D:\workspace\android-ndk-r25b\build\cmake\android.toolchain.cmake -DANDROID_NATIVE_API_LEVEL=android-24 -DANDROID_ABI=arm64-v8a ..
         # for armeabi-v7a
-        cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=E:\android-ndk-r21d-windows-x86_64\android-ndk-r21d\build\cmake\android.toolchain.cmake -DANDROID_NATIVE_API_LEVEL=android-19 -DANDROID_ABI=armeabi-v7a ..
+        cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=D:\workspace\android-ndk-r25b\build\cmake\android.toolchain.cmake -DANDROID_NATIVE_API_LEVEL=android-24 -DANDROID_ABI=armeabi-v7a ..
         # for x86_64
-        cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=E:\android-ndk-r21d-windows-x86_64\android-ndk-r21d\build\cmake\android.toolchain.cmake -DANDROID_NATIVE_API_LEVEL=android-19 -DANDROID_ABI=x86_64 ..
+        cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=D:\workspace\android-ndk-r25b\build\cmake\android.toolchain.cmake -DANDROID_NATIVE_API_LEVEL=android-24 -DANDROID_ABI=x86_64 ..
         # for x86
-        cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=E:\android-ndk-r21d-windows-x86_64\android-ndk-r21d\build\cmake\android.toolchain.cmake -DANDROID_NATIVE_API_LEVEL=android-19 -DANDROID_ABI=x86 ..
+        cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=D:\workspace\android-ndk-r25b\build\cmake\android.toolchain.cmake -DANDROID_NATIVE_API_LEVEL=android-24 -DANDROID_ABI=x86 ..
 
         # to build(should be run for each ABI from previous step**
         cmake --build . --target install --config Release -j 2 --parallel 2
diff --git a/docs/SupportedBoards.rst b/docs/SupportedBoards.rst
index 84e104465..48720d9c7 100644
--- a/docs/SupportedBoards.rst
+++ b/docs/SupportedBoards.rst
@@ -1203,3 +1203,40 @@ Steps to connect:
 - Enable device and Pair it with your laptop using bluetooth settings
 - Ensure that blue LED is blinking before calling :code:`board.prepare_session()`
 - If you see green LED probably you need to reboot a devce
+
+EmotiBit
+---------
+
+EmotiBit board
+~~~~~~~~~~~~~~~
+
+.. image:: https://live.staticflickr.com/65535/52519313192_7869efa2f5.jpg
+    :width: 500px
+    :height: 281px
+
+`EmotiBit Website <https://www.emotibit.com/>`_
+
+To create such board you need to specify the following board ID and fields of BrainFlowInputParams object:
+
+- :code:`BoardIds.EMOTIBIT_BOARD`
+- *optional:* :code:`ip_address`, you can provide *broadcast* ip address of the network with EmotiBit device, e.g. 192.168.178.255. If not provided BrainFlow will try to autodiscover the network and it may take a little longer.
+
+Initialization Example:
+
+.. code-block:: python
+
+    params = BrainFlowInputParams()
+    board = BoardShim(BoardIds.EMOTIBIT_BOARD, params)
+
+Supported platforms:
+
+- Windows
+- MacOS
+- Linux
+- Devices like Raspberry Pi
+
+Available :ref:`presets-label`:
+
+- :code:`BrainFlowPresets.DEFAULT_PRESET`, it contains accelerometer, gyroscope and magnetometer data
+- :code:`BrainFlowPresets.AUXILIARY_PRESET`, it contains PPG data
+- :code:`BrainFlowPresets.ANCILLARY_PRESET`, it contains EDA and temperature data
diff --git a/java_package/brainflow/src/main/java/brainflow/BoardIds.java b/java_package/brainflow/src/main/java/brainflow/BoardIds.java
index ac069b0cc..493d02664 100644
--- a/java_package/brainflow/src/main/java/brainflow/BoardIds.java
+++ b/java_package/brainflow/src/main/java/brainflow/BoardIds.java
@@ -57,7 +57,8 @@ public enum BoardIds
     PIEEG_BOARD (43),
     EXPLORE_4_CHAN_BOARD (44),
     EXPLORE_8_CHAN_BOARD (45),
-    GANGLION_NATIVE_BOARD (46);
+    GANGLION_NATIVE_BOARD (46),
+    EMOTIBIT_BOARD (47);
 
     private final int board_id;
     private static final Map<Integer, BoardIds> bi_map = new HashMap<Integer, BoardIds> ();
diff --git a/java_package/brainflow/src/main/java/brainflow/BoardShim.java b/java_package/brainflow/src/main/java/brainflow/BoardShim.java
index 4b54dfa69..c24c8152d 100644
--- a/java_package/brainflow/src/main/java/brainflow/BoardShim.java
+++ b/java_package/brainflow/src/main/java/brainflow/BoardShim.java
@@ -91,6 +91,8 @@ int get_current_board_data (int num_samples, int preset, double[] data_buf, int[
 
         int get_resistance_channels (int board_id, int preset, int[] channels, int[] len);
 
+        int get_magnetometer_channels (int board_id, int preset, int[] channels, int[] len);
+
         int get_temperature_channels (int board_id, int preset, int[] temperature_channels, int[] len);
 
         int release_all_sessions ();
@@ -852,6 +854,50 @@ public static int[] get_temperature_channels (BoardIds board_id) throws BrainFlo
         return get_temperature_channels (board_id.get_code ());
     }
 
+    /**
+     * get row indices in returned by get_board_data() 2d array which contain
+     * magnetometer data
+     */
+    public static int[] get_magnetometer_channels (int board_id, BrainFlowPresets preset) throws BrainFlowError
+    {
+        int[] len = new int[1];
+        int[] channels = new int[512];
+        int ec = instance.get_magnetometer_channels (board_id, preset.get_code (), channels, len);
+        if (ec != BrainFlowExitCode.STATUS_OK.get_code ())
+        {
+            throw new BrainFlowError ("Error in board info getter", ec);
+        }
+
+        return Arrays.copyOfRange (channels, 0, len[0]);
+    }
+
+    /**
+     * get row indices in returned by get_board_data() 2d array which contain
+     * magnetometer data
+     */
+    public static int[] get_magnetometer_channels (int board_id) throws BrainFlowError
+    {
+        return get_magnetometer_channels (board_id, BrainFlowPresets.DEFAULT_PRESET);
+    }
+
+    /**
+     * get row indices in returned by get_board_data() 2d array which contain
+     * magnetometer data
+     */
+    public static int[] get_magnetometer_channels (BoardIds board_id, BrainFlowPresets preset) throws BrainFlowError
+    {
+        return get_magnetometer_channels (board_id.get_code (), preset);
+    }
+
+    /**
+     * get row indices in returned by get_board_data() 2d array which contain
+     * magnetometer data
+     */
+    public static int[] get_magnetometer_channels (BoardIds board_id) throws BrainFlowError
+    {
+        return get_magnetometer_channels (board_id.get_code ());
+    }
+
     /**
      * get row indices in returned by get_board_data() 2d array which contain
      * resistance data
diff --git a/julia_package/brainflow/src/board_shim.jl b/julia_package/brainflow/src/board_shim.jl
index 29d9ab0c6..b95d18c96 100644
--- a/julia_package/brainflow/src/board_shim.jl
+++ b/julia_package/brainflow/src/board_shim.jl
@@ -53,6 +53,7 @@ export BrainFlowInputParams
     EXPLORE_4_CHAN_BOARD = 44
     EXPLORE_8_CHAN_BOARD = 45
     GANGLION_NATIVE_BOARD = 46
+    EMOTIBIT_BOARD = 47
 
 end
 
@@ -189,6 +190,7 @@ channel_function_names = (
     :get_other_channels,
     :get_temperature_channels,
     :get_resistance_channels,
+    :get_magnetometer_channels,
 )
 
 # generating the channels functions
diff --git a/matlab_package/brainflow/BoardIds.m b/matlab_package/brainflow/BoardIds.m
index ce1f3bb86..651d1daf6 100644
--- a/matlab_package/brainflow/BoardIds.m
+++ b/matlab_package/brainflow/BoardIds.m
@@ -51,5 +51,6 @@
         EXPLORE_4_CHAN_BOARD(44)
         EXPLORE_8_CHAN_BOARD(45)
         GANGLION_NATIVE_BOARD(46)
+        EMOTIBIT_BOARD(47)
     end
 end
\ No newline at end of file
diff --git a/matlab_package/brainflow/BoardShim.m b/matlab_package/brainflow/BoardShim.m
index 7802fac31..6e38bc556 100644
--- a/matlab_package/brainflow/BoardShim.m
+++ b/matlab_package/brainflow/BoardShim.m
@@ -329,6 +329,17 @@ function log_message(log_level, message)
             resistance_channels = data.Value(1,1:num_channels.Value) + 1;
         end
         
+        function magnetometer_channels = get_magnetometer_channels(board_id, preset)
+            % get magnetometer channels for provided board id
+            task_name = 'get_magnetometer_channels';
+            lib_name = BoardShim.load_lib();
+            num_channels = libpointer('int32Ptr', 0);
+            data = libpointer('int32Ptr', zeros(1, 512));
+            exit_code = calllib(lib_name, task_name, board_id, preset, data, num_channels);
+            BoardShim.check_ec(exit_code, task_name);
+            magnetometer_channels = data.Value(1,1:num_channels.Value) + 1;
+        end
+        
     end
 
     methods
diff --git a/python_package/brainflow/board_shim.py b/python_package/brainflow/board_shim.py
index bed81c16b..208b8505f 100644
--- a/python_package/brainflow/board_shim.py
+++ b/python_package/brainflow/board_shim.py
@@ -67,6 +67,7 @@ class BoardIds(enum.IntEnum):
     EXPLORE_4_CHAN_BOARD = 44  #:
     EXPLORE_8_CHAN_BOARD = 45  #:
     GANGLION_NATIVE_BOARD = 46  #:
+    EMOTIBIT_BOARD = 47  #:
 
 
 class IpProtocolTypes(enum.IntEnum):
@@ -516,6 +517,15 @@ def __init__(self):
             ndpointer(ctypes.c_int32)
         ]
 
+        self.get_magnetometer_channels = self.lib.get_magnetometer_channels
+        self.get_magnetometer_channels.restype = ctypes.c_int
+        self.get_magnetometer_channels.argtypes = [
+            ctypes.c_int,
+            ctypes.c_int,
+            ndpointer(ctypes.c_int32),
+            ndpointer(ctypes.c_int32)
+        ]
+
 
 class BoardShim(object):
     """BoardShim class is a primary interface to all boards
@@ -1096,6 +1106,28 @@ def get_resistance_channels(cls, board_id: int, preset: int = BrainFlowPresets.D
         result = resistance_channels.tolist()[0:num_channels[0]]
         return result
 
+    @classmethod
+    def get_magnetometer_channels(cls, board_id: int, preset: int = BrainFlowPresets.DEFAULT_PRESET) -> List[int]:
+        """get list of magnetometer channels in resulting data table for a board
+
+        :param board_id: Board Id
+        :type board_id: int
+        :param preset: preset
+        :type preset: int
+        :return: list of magnetometer channels in returned numpy array
+        :rtype: List[int]
+        :raises BrainFlowError: If this board has no such data exit code is UNSUPPORTED_BOARD_ERROR
+        """
+
+        num_channels = numpy.zeros(1).astype(numpy.int32)
+        magnetometer_channels = numpy.zeros(512).astype(numpy.int32)
+
+        res = BoardControllerDLL.get_instance().get_magnetometer_channels(board_id, preset, magnetometer_channels, num_channels)
+        if res != BrainFlowExitCodes.STATUS_OK.value:
+            raise BrainFlowError('unable to request info about this board', res)
+        result = magnetometer_channels.tolist()[0:num_channels[0]]
+        return result
+
     @classmethod
     def release_all_sessions(cls) -> None:
         """release all prepared sessions"""
diff --git a/rust_package/brainflow/src/board_shim.rs b/rust_package/brainflow/src/board_shim.rs
index ae6be9373..8c975c01f 100644
--- a/rust_package/brainflow/src/board_shim.rs
+++ b/rust_package/brainflow/src/board_shim.rs
@@ -513,6 +513,10 @@ gen_vec_fn!(
     resistance_channels,
     "Get list of resistance channels in resulting data table for a board."
 );
+gen_vec_fn!(
+    magnetometer_channels,
+    "Get list of magnetometer channels in resulting data table for a board."
+);
 
 #[cfg(test)]
 mod tests {
diff --git a/rust_package/brainflow/src/ffi/board_controller.rs b/rust_package/brainflow/src/ffi/board_controller.rs
index a6f5bbf10..88ca31bca 100644
--- a/rust_package/brainflow/src/ffi/board_controller.rs
+++ b/rust_package/brainflow/src/ffi/board_controller.rs
@@ -165,6 +165,14 @@ extern "C" {
         len: *mut ::std::os::raw::c_int,
     ) -> ::std::os::raw::c_int;
 }
+extern "C" {
+    pub fn get_magnetometer_channels(
+        board_id: ::std::os::raw::c_int,
+        preset: ::std::os::raw::c_int,
+        magnetometer_channels: *mut ::std::os::raw::c_int,
+        len: *mut ::std::os::raw::c_int,
+    ) -> ::std::os::raw::c_int;
+}
 extern "C" {
     pub fn get_device_name(
         board_id: ::std::os::raw::c_int,
diff --git a/rust_package/brainflow/src/ffi/constants.rs b/rust_package/brainflow/src/ffi/constants.rs
index 1ca838040..864047967 100644
--- a/rust_package/brainflow/src/ffi/constants.rs
+++ b/rust_package/brainflow/src/ffi/constants.rs
@@ -32,7 +32,7 @@ impl BoardIds {
     pub const FIRST: BoardIds = BoardIds::PlaybackFileBoard;
 }
 impl BoardIds {
-    pub const LAST: BoardIds = BoardIds::GanglionNativeBoard;
+    pub const LAST: BoardIds = BoardIds::EmotibitBoard;
 }
 #[repr(i32)]
 #[derive(FromPrimitive, ToPrimitive, Debug, Copy, Clone, Hash, PartialEq, Eq)]
@@ -87,6 +87,7 @@ pub enum BoardIds {
     Explore4ChanBoard = 44,
     Explore8ChanBoard = 45,
     GanglionNativeBoard = 46,
+    EmotibitBoard = 47,
 }
 #[repr(i32)]
 #[derive(FromPrimitive, ToPrimitive, Debug, Copy, Clone, Hash, PartialEq, Eq)]
diff --git a/src/board_controller/board_controller.cpp b/src/board_controller/board_controller.cpp
index 388a4ebb2..8b61ab612 100644
--- a/src/board_controller/board_controller.cpp
+++ b/src/board_controller/board_controller.cpp
@@ -31,6 +31,7 @@
 #include "cyton_daisy.h"
 #include "cyton_daisy_wifi.h"
 #include "cyton_wifi.h"
+#include "emotibit.h"
 #include "enophone.h"
 #include "explore.h"
 #include "freeeeg32.h"
@@ -252,6 +253,9 @@ int prepare_session (int board_id, const char *json_brainflow_input_params)
         case BoardIds::GANGLION_NATIVE_BOARD:
             board = std::shared_ptr<Board> (new GanglionNative (params));
             break;
+        case BoardIds::EMOTIBIT_BOARD:
+            board = std::shared_ptr<Board> (new Emotibit (params));
+            break;
         default:
             return (int)BrainFlowExitCodes::UNSUPPORTED_BOARD_ERROR;
     }
diff --git a/src/board_controller/board_info_getter.cpp b/src/board_controller/board_info_getter.cpp
index 2eb0835bd..7a87dbb51 100644
--- a/src/board_controller/board_info_getter.cpp
+++ b/src/board_controller/board_info_getter.cpp
@@ -191,6 +191,11 @@ int get_resistance_channels (int board_id, int preset, int *resistance_channels,
     return get_array_value (board_id, preset, "resistance_channels", resistance_channels, len);
 }
 
+int get_magnetometer_channels (int board_id, int preset, int *magnetometer_channels, int *len)
+{
+    return get_array_value (board_id, preset, "magnetometer_channels", magnetometer_channels, len);
+}
+
 int get_exg_channels (int board_id, int preset, int *exg_channels, int *len)
 {
     std::set<int> unique_channels;
diff --git a/src/board_controller/brainflow_boards.cpp b/src/board_controller/brainflow_boards.cpp
index f1ad0dcfd..01b9b6d70 100644
--- a/src/board_controller/brainflow_boards.cpp
+++ b/src/board_controller/brainflow_boards.cpp
@@ -805,7 +805,7 @@ BrainFlowBoards::BrainFlowBoards()
         {"num_rows", 12},
         {"accel_channels", {1, 2, 3}},
         {"gyro_channels", {4, 5, 6}},
-        {"other_channels", {7, 8, 9}}
+        {"magnetometer_channels", {7, 8, 9}}
     };
     brainflow_boards_json["boards"]["44"]["ancillary"] =
     {
@@ -842,7 +842,7 @@ BrainFlowBoards::BrainFlowBoards()
         {"num_rows", 12},
         {"accel_channels", {1, 2, 3}},
         {"gyro_channels", {4, 5, 6}},
-        {"other_channels", {7, 8, 9}}
+        {"magnetometer_channels", {7, 8, 9}}
     };
     brainflow_boards_json["boards"]["45"]["ancillary"] =
     {
@@ -871,6 +871,41 @@ BrainFlowBoards::BrainFlowBoards()
         {"accel_channels", {5, 6, 7}},
         {"resistance_channels", {8, 9, 10, 11, 12}}
     };
+    // todo add other data types and check/fix sampling rates for them
+    brainflow_boards_json["boards"]["47"]["default"] =
+    {
+        {"name", "Emotibit"},
+        {"sampling_rate", 25}, // random value for now
+        {"package_num_channel", 0},
+        {"timestamp_channel", 10},
+        {"marker_channel", 11},
+        {"num_rows", 12},
+        {"accel_channels", {1, 2, 3}},
+        {"gyro_channels", {4, 5, 6}},
+        {"magnetometer_channels", {7, 8, 9}}
+    };
+    brainflow_boards_json["boards"]["47"]["auxiliary"] =
+    {
+        {"name", "Emotibit"},
+        {"sampling_rate", 25},
+        {"package_num_channel", 0},
+        {"timestamp_channel", 4},
+        {"marker_channel", 5},
+        {"num_rows", 6},
+        {"ppg_channels", {1, 2, 3}}
+    };
+    brainflow_boards_json["boards"]["47"]["ancillary"] =
+    {
+        {"name", "Emotibit"},
+        {"sampling_rate", 15},
+        {"package_num_channel", 0},
+        {"timestamp_channel", 4},
+        {"marker_channel", 5},
+        {"num_rows", 6},
+        {"eda_channels", {1}},
+        {"temperature_channels", {2}},
+        {"other_channels", {3}}
+    };
 }
 
 BrainFlowBoards boards_struct;
\ No newline at end of file
diff --git a/src/board_controller/build.cmake b/src/board_controller/build.cmake
index b88e12461..cd0bceb88 100644
--- a/src/board_controller/build.cmake
+++ b/src/board_controller/build.cmake
@@ -36,6 +36,7 @@ SET (BOARD_CONTROLLER_SRC
     ${CMAKE_HOME_DIRECTORY}/src/utils/multicast_client.cpp
     ${CMAKE_HOME_DIRECTORY}/src/utils/multicast_server.cpp
     ${CMAKE_HOME_DIRECTORY}/src/utils/broadcast_client.cpp
+    ${CMAKE_HOME_DIRECTORY}/src/utils/broadcast_server.cpp
     ${CMAKE_HOME_DIRECTORY}/src/board_controller/openbci/galea_serial.cpp
     ${CMAKE_HOME_DIRECTORY}/src/board_controller/openbci/openbci_serial_board.cpp
     ${CMAKE_HOME_DIRECTORY}/src/board_controller/openbci/openbci_wifi_shield_board.cpp
@@ -79,6 +80,7 @@ SET (BOARD_CONTROLLER_SRC
     ${CMAKE_HOME_DIRECTORY}/src/board_controller/ble_lib_board.cpp
     ${CMAKE_HOME_DIRECTORY}/src/board_controller/muse/muse.cpp
     ${CMAKE_HOME_DIRECTORY}/src/board_controller/brainalive/brainalive.cpp
+    ${CMAKE_HOME_DIRECTORY}/src/board_controller/emotibit/emotibit.cpp
 )
 
 include (${CMAKE_HOME_DIRECTORY}/src/board_controller/ant_neuro/build.cmake)
@@ -133,6 +135,7 @@ target_include_directories (
     ${CMAKE_HOME_DIRECTORY}/third_party/SimpleBLE/simpleble/include
     ${CMAKE_HOME_DIRECTORY}/src/board_controller/brainalive/inc
     ${CMAKE_HOME_DIRECTORY}/src/board_controller/mentalab/inc
+    ${CMAKE_HOME_DIRECTORY}/src/board_controller/emotibit/inc
 )
 
 target_compile_definitions(${BOARD_CONTROLLER_NAME} PRIVATE NOMINMAX BRAINFLOW_VERSION=${BRAINFLOW_VERSION})
diff --git a/src/board_controller/emotibit/emotibit.cpp b/src/board_controller/emotibit/emotibit.cpp
new file mode 100644
index 000000000..68ed8bc6a
--- /dev/null
+++ b/src/board_controller/emotibit/emotibit.cpp
@@ -0,0 +1,847 @@
+#include "emotibit.h"
+
+#include <chrono>
+#include <sstream>
+#include <stdint.h>
+#include <string.h>
+
+#include "custom_cast.h"
+#include "json.hpp"
+#include "network_interfaces.h"
+#include "timestamp.h"
+
+#include "emotibit_defines.h"
+
+using json = nlohmann::json;
+
+
+Emotibit::Emotibit (struct BrainFlowInputParams params)
+    : Board ((int)BoardIds::EMOTIBIT_BOARD, params)
+{
+    data_socket = NULL;
+    control_socket = NULL;
+    advertise_socket_server = NULL;
+    keep_alive = false;
+    initialized = false;
+    control_port = -1;
+    data_port = -1;
+}
+
+Emotibit::~Emotibit ()
+{
+    skip_logs = true;
+    release_session ();
+}
+
+int Emotibit::prepare_session ()
+{
+    if (initialized)
+    {
+        safe_logger (spdlog::level::info, "Session is already prepared");
+        return (int)BrainFlowExitCodes::STATUS_OK;
+    }
+
+    if (params.timeout < 2)
+    {
+        params.timeout = 4;
+    }
+
+    int res = create_adv_connection ();
+    if (res == (int)BrainFlowExitCodes::STATUS_OK)
+    {
+        res = create_data_connection ();
+    }
+    if (res == (int)BrainFlowExitCodes::STATUS_OK)
+    {
+        res = create_control_connection ();
+    }
+    if (res == (int)BrainFlowExitCodes::STATUS_OK)
+    {
+        res = send_connect_msg ();
+    }
+    if (res == (int)BrainFlowExitCodes::STATUS_OK)
+    {
+        res = wait_for_connection ();
+    }
+    if (res == (int)BrainFlowExitCodes::STATUS_OK)
+    {
+        res = send_control_msg (MODE_LOW_POWER);
+    }
+
+    if (res != (int)BrainFlowExitCodes::STATUS_OK)
+    {
+        if (advertise_socket_server != NULL)
+        {
+            delete advertise_socket_server;
+            advertise_socket_server = NULL;
+        }
+        if (data_socket != NULL)
+        {
+            delete data_socket;
+            data_socket = NULL;
+        }
+        if (control_socket != NULL)
+        {
+            delete control_socket;
+            control_socket = NULL;
+        }
+    }
+    else
+    {
+        initialized = true;
+        connection_thread = std::thread ([this] { this->ping_thread (); });
+    }
+
+    return res;
+}
+
+int Emotibit::config_board (std::string conf, std::string &response)
+{
+    return send_control_msg (conf.c_str ());
+}
+
+int Emotibit::start_stream (int buffer_size, const char *streamer_params)
+{
+    if (!initialized)
+    {
+        safe_logger (spdlog::level::err, "You need to call prepare_session before config_board");
+        return (int)BrainFlowExitCodes::BOARD_NOT_CREATED_ERROR;
+    }
+    if (keep_alive)
+    {
+        safe_logger (spdlog::level::err, "Streaming thread already running");
+        return (int)BrainFlowExitCodes::STREAM_ALREADY_RUN_ERROR;
+    }
+    int res = prepare_for_acquisition (buffer_size, streamer_params);
+    if (res != (int)BrainFlowExitCodes::STATUS_OK)
+    {
+        return res;
+    }
+    res = send_control_msg (MODE_NORMAL_POWER);
+    if (res == (int)BrainFlowExitCodes::STATUS_OK)
+    {
+        keep_alive = true;
+        streaming_thread = std::thread ([this] { this->read_thread (); });
+    }
+    return (int)BrainFlowExitCodes::STATUS_OK;
+}
+
+int Emotibit::stop_stream ()
+{
+    if (keep_alive)
+    {
+        if (send_control_msg (MODE_LOW_POWER) != (int)BrainFlowExitCodes::STATUS_OK)
+        {
+            safe_logger (spdlog::level::warn, "failed to set low power mode");
+        }
+        keep_alive = false;
+        streaming_thread.join ();
+        return (int)BrainFlowExitCodes::STATUS_OK;
+    }
+    return (int)BrainFlowExitCodes::STREAM_THREAD_IS_NOT_RUNNING;
+}
+
+int Emotibit::release_session ()
+{
+    if (initialized)
+    {
+        if (keep_alive)
+        {
+            stop_stream ();
+        }
+        initialized = false;
+        connection_thread.join ();
+        free_packages ();
+        send_control_msg (EMOTIBIT_DISCONNECT);
+        if (data_socket)
+        {
+            data_socket->close ();
+            delete data_socket;
+            data_socket = NULL;
+        }
+        if (control_socket)
+        {
+            control_socket->close ();
+            delete control_socket;
+            control_socket = NULL;
+        }
+        if (advertise_socket_server)
+        {
+            advertise_socket_server->close ();
+            delete advertise_socket_server;
+            advertise_socket_server = NULL;
+        }
+        control_port = -1;
+        data_port = -1;
+    }
+    return (int)BrainFlowExitCodes::STATUS_OK;
+}
+
+void Emotibit::read_thread ()
+{
+    constexpr int max_size = 32768;
+    char message[max_size];
+
+    // emotibit sends multiple data points per transaction and for example accelerometer x and y
+    // data are in different transactions, we have to align them and use max_datapoints_in_package
+    // arrays instead single one
+    constexpr int max_datapoints_in_package =
+        1024; // experimental(random) value, in practice I saw max 9
+    double *default_packages[max_datapoints_in_package];
+    double *aux_packages[max_datapoints_in_package];
+    double *anc_packages[max_datapoints_in_package];
+    int num_default_rows = board_descr["default"]["num_rows"];
+    int num_aux_rows = board_descr["auxiliary"]["num_rows"];
+    int num_anc_rows = board_descr["ancillary"]["num_rows"];
+
+    for (int cur_package = 0; cur_package < max_datapoints_in_package; cur_package++)
+    {
+        default_packages[cur_package] = new double[num_default_rows];
+        for (int i = 0; i < num_default_rows; i++)
+        {
+            default_packages[cur_package][i] = 0.0;
+        }
+        aux_packages[cur_package] = new double[num_aux_rows];
+        for (int i = 0; i < num_aux_rows; i++)
+        {
+            aux_packages[cur_package][i] = 0.0;
+        }
+        anc_packages[cur_package] = new double[num_anc_rows];
+        for (int i = 0; i < num_anc_rows; i++)
+        {
+            anc_packages[cur_package][i] = 0.0;
+        }
+    }
+
+    while (keep_alive)
+    {
+        int bytes_recv = data_socket->recv (message, max_size);
+        if (bytes_recv < 1)
+        {
+            safe_logger (spdlog::level::trace, "no data received");
+            continue;
+        }
+        std::string message_received = std::string (message, bytes_recv);
+        std::vector<std::string> splitted_packages =
+            split_string (message_received, PACKET_DELIMITER_CSV);
+        for (std::string recv_package : splitted_packages)
+        {
+            int package_num = 0;
+            int data_len = 0;
+            std::string type_tag = "";
+            if (get_header (recv_package, &package_num, &data_len, type_tag))
+            {
+                // default package
+                std::vector<std::string> payload = get_payload (recv_package, data_len);
+                int channel = -1;
+                if (type_tag == ACCELEROMETER_X)
+                {
+                    channel = board_descr["default"]["accel_channels"][0];
+                }
+                if (type_tag == ACCELEROMETER_Y)
+                {
+                    channel = board_descr["default"]["accel_channels"][1];
+                }
+                if (type_tag == ACCELEROMETER_Z)
+                {
+                    channel = board_descr["default"]["accel_channels"][2];
+                }
+                if (type_tag == GYROSCOPE_X)
+                {
+                    channel = board_descr["default"]["gyro_channels"][0];
+                }
+                if (type_tag == GYROSCOPE_Y)
+                {
+                    channel = board_descr["default"]["gyro_channels"][1];
+                }
+                if (type_tag == GYROSCOPE_Z)
+                {
+                    channel = board_descr["default"]["gyro_channels"][2];
+                }
+                if (type_tag == MAGNETOMETER_X)
+                {
+                    channel = board_descr["default"]["magnetometer_channels"][0];
+                }
+                if (type_tag == MAGNETOMETER_Y)
+                {
+                    channel = board_descr["default"]["magnetometer_channels"][1];
+                }
+                if (type_tag == MAGNETOMETER_Z)
+                {
+                    channel = board_descr["default"]["magnetometer_channels"][2];
+                }
+                if (channel > 0)
+                {
+                    for (int i = 0; i < std::min ((int)payload.size (), max_datapoints_in_package);
+                         i++)
+                    {
+                        default_packages[i]
+                                        [board_descr["default"]["timestamp_channel"].get<int> ()] =
+                                            get_timestamp ();
+                        default_packages[i][board_descr["default"]["package_num_channel"]
+                                                .get<int> ()] = package_num;
+                        try
+                        {
+                            default_packages[i][channel] = std::stod (payload[i]);
+                        }
+                        catch (...)
+                        {
+                            safe_logger (spdlog::level::warn, "invalid data in payload: {}",
+                                payload[i].c_str ());
+                        }
+                    }
+                    channel = -1;
+                }
+                // push default preset when magnetometer z is received
+                if (type_tag == MAGNETOMETER_Z)
+                {
+                    for (int i = 0; i < data_len; i++)
+                    {
+                        push_package (default_packages[i], (int)BrainFlowPresets::DEFAULT_PRESET);
+                    }
+                }
+                // auxuliary package
+                if (type_tag == PPG_INFRARED)
+                {
+                    channel = board_descr["auxiliary"]["ppg_channels"][0];
+                }
+                if (type_tag == PPG_RED)
+                {
+                    channel = board_descr["auxiliary"]["ppg_channels"][1];
+                }
+                if (type_tag == PPG_GREEN)
+                {
+                    channel = board_descr["auxiliary"]["ppg_channels"][2];
+                }
+                if (channel > 0)
+                {
+                    for (int i = 0; i < std::min ((int)payload.size (), max_datapoints_in_package);
+                         i++)
+                    {
+                        aux_packages[i][board_descr["auxiliary"]["timestamp_channel"].get<int> ()] =
+                            get_timestamp ();
+                        aux_packages[i]
+                                    [board_descr["auxiliary"]["package_num_channel"].get<int> ()] =
+                                        package_num;
+                        try
+                        {
+                            aux_packages[i][channel] = std::stod (payload[i]);
+                        }
+                        catch (...)
+                        {
+                            safe_logger (spdlog::level::warn, "invalid data in payload: {}",
+                                payload[i].c_str ());
+                        }
+                    }
+                    channel = -1;
+                }
+                // push aux preset when ppg green is received
+                if (type_tag == PPG_GREEN)
+                {
+                    for (int i = 0; i < data_len; i++)
+                    {
+                        push_package (aux_packages[i], (int)BrainFlowPresets::AUXILIARY_PRESET);
+                    }
+                }
+                // ancillary package
+                if ((type_tag == TEMPERATURE_1) || (type_tag == THERMOPILE))
+                {
+                    int temperature_channel = 0;
+                    if (type_tag == TEMPERATURE_1)
+                    {
+                        temperature_channel = board_descr["ancillary"]["temperature_channels"][0];
+                    }
+                    if (type_tag == THERMOPILE)
+                    {
+                        temperature_channel = board_descr["ancillary"]["other_channels"][0];
+                    }
+                    // upsample temperature data 2x to match eda
+                    if (payload.size () < max_datapoints_in_package / 2)
+                    {
+                        for (int i = 0; i < (int)payload.size (); i++)
+                        {
+                            try
+                            {
+                                anc_packages[i * 2][temperature_channel] = std::stod (payload[i]);
+                                anc_packages[i + 1][temperature_channel] =
+                                    anc_packages[i][temperature_channel];
+                            }
+                            catch (...)
+                            {
+                                safe_logger (spdlog::level::warn, "invalid data in payload: {}",
+                                    payload[i].c_str ());
+                            }
+                        }
+                    }
+                    // should not happen, no place in buffer for upsampling, keep as is
+                    else
+                    {
+                        for (int i = 0;
+                             i < std::min ((int)payload.size (), max_datapoints_in_package); i++)
+                        {
+                            try
+                            {
+                                anc_packages[i][temperature_channel] = std::stod (payload[i]);
+                            }
+                            catch (...)
+                            {
+                                safe_logger (spdlog::level::warn, "invalid data in payload: {}",
+                                    payload[i].c_str ());
+                            }
+                        }
+                    }
+                }
+                if (type_tag == EDA)
+                {
+                    int eda_channel = board_descr["ancillary"]["eda_channels"][0];
+                    for (int i = 0; i < (int)payload.size (); i++)
+                    {
+                        anc_packages[i][board_descr["ancillary"]["timestamp_channel"].get<int> ()] =
+                            get_timestamp ();
+                        anc_packages[i]
+                                    [board_descr["ancillary"]["package_num_channel"].get<int> ()] =
+                                        package_num;
+                        try
+                        {
+                            anc_packages[i][eda_channel] = std::stod (payload[i]);
+                        }
+                        catch (...)
+                        {
+                            safe_logger (spdlog::level::warn, "invalid data in payload: {}",
+                                payload[i].c_str ());
+                        }
+                        push_package (anc_packages[i], (int)BrainFlowPresets::ANCILLARY_PRESET);
+                    }
+                }
+            }
+            else
+            {
+                safe_logger (
+                    spdlog::level::trace, "invalid header for package: {}", recv_package.c_str ());
+            }
+        }
+    }
+    for (int cur_package = 0; cur_package < max_datapoints_in_package; cur_package++)
+    {
+        delete[] default_packages[cur_package];
+        delete[] aux_packages[cur_package];
+        delete[] anc_packages[cur_package];
+    }
+}
+
+std::string Emotibit::create_package (const std::string &type_tag, uint16_t package_number,
+    const std::string &data, uint16_t data_length, uint8_t protocol_version,
+    uint8_t data_reliability)
+{
+    std::string header = create_header (
+        type_tag, 0, package_number, data_length, protocol_version, data_reliability);
+    if (data_length == 0)
+    {
+        return header + PACKET_DELIMITER_CSV;
+    }
+    else
+    {
+        return header + PAYLOAD_DELIMITER + data + PACKET_DELIMITER_CSV;
+    }
+}
+
+std::string Emotibit::create_package (const std::string &type_tag, uint16_t package_number,
+    std::vector<std::string> data, uint8_t protocol_version, uint8_t data_reliability)
+{
+    std::string package = create_header (
+        type_tag, 0, package_number, (uint16_t)data.size (), protocol_version, data_reliability);
+    for (std::string s : data)
+    {
+        package += PAYLOAD_DELIMITER + s;
+    }
+    package += PACKET_DELIMITER_CSV;
+    return package;
+}
+
+std::string Emotibit::create_header (const std::string &type_tag, uint32_t timestamp,
+    uint16_t package_number, uint16_t data_length, uint8_t protocol_version,
+    uint8_t data_reliability)
+{
+    std::string header = "";
+    header += std::to_string (timestamp);
+    header += PAYLOAD_DELIMITER;
+    header += std::to_string (package_number);
+    header += PAYLOAD_DELIMITER;
+    header += std::to_string (data_length);
+    header += PAYLOAD_DELIMITER;
+    header += type_tag;
+    header += PAYLOAD_DELIMITER;
+    header += std::to_string ((int)protocol_version);
+    header += PAYLOAD_DELIMITER;
+    header += std::to_string ((int)data_reliability);
+    return header;
+}
+
+std::vector<std::string> Emotibit::split_string (const std::string &package, char delim)
+{
+    std::vector<std::string> result;
+    size_t start;
+    size_t end = 0;
+    while ((start = package.find_first_not_of (delim, end)) != std::string::npos)
+    {
+        end = package.find (delim, start);
+        std::string cur_str = package.substr (start, end - start);
+        result.push_back (cur_str);
+    }
+    return result;
+}
+
+bool Emotibit::get_header (
+    const std::string &package_string, int *package_num, int *data_len, std::string &type_tag)
+{
+    std::vector<std::string> package = split_string (package_string, PAYLOAD_DELIMITER);
+    if (package.size () >= HEADER_LENGTH)
+    {
+        try
+        {
+            if (package.at (1) != "")
+            {
+                *package_num = stoi (package.at (1));
+            }
+            else
+            {
+                return false;
+            }
+            if (package.at (2) != "")
+            {
+                *data_len = stoi (package.at (2));
+            }
+            else
+            {
+                return false;
+            }
+            if (package.at (3) != "")
+            {
+                type_tag = package.at (3);
+            }
+            else
+            {
+                return false;
+            }
+        }
+        catch (...)
+        {
+            return false;
+        }
+    }
+    else
+    {
+        safe_logger (
+            spdlog::level::warn, "package size: {} is smaller than expected", package.size ());
+        return false;
+    }
+
+    if (package.size () < (size_t)HEADER_LENGTH + *data_len)
+    {
+        safe_logger (spdlog::level::warn, "small package: {}", package_string.c_str ());
+        return false;
+    }
+    else
+    {
+        return true;
+    }
+}
+
+std::vector<std::string> Emotibit::get_payload (const std::string &package_string, int data_len)
+{
+    std::vector<std::string> res;
+    std::vector<std::string> package = split_string (package_string, PAYLOAD_DELIMITER);
+    if (package.size () >= (size_t)HEADER_LENGTH + (size_t)data_len)
+    {
+        res.insert (res.begin (), package.begin () + HEADER_LENGTH,
+            package.begin () + (size_t)HEADER_LENGTH + (size_t)data_len);
+    }
+    return res;
+}
+
+int Emotibit::create_adv_connection ()
+{
+    int res = (int)BrainFlowExitCodes::STATUS_OK;
+    std::vector<std::string> broadcast_addresses;
+    if (!params.ip_address.empty ())
+    {
+        broadcast_addresses.push_back (params.ip_address);
+    }
+    else
+    {
+        safe_logger (spdlog::level::warn,
+            "no ip_address provided, trying to discover network, it may take longer");
+        broadcast_addresses = get_broadcast_addresses ();
+    }
+
+    if (broadcast_addresses.empty ())
+    {
+        safe_logger (spdlog::level::err, "no broadcast addresses found");
+        res = (int)BrainFlowExitCodes::BOARD_NOT_READY_ERROR;
+    }
+
+    for (std::string broadcast_address : broadcast_addresses)
+    {
+        res = (int)BrainFlowExitCodes::STATUS_OK;
+        safe_logger (
+            spdlog::level::info, "trying broadcast address: {}", broadcast_address.c_str ());
+        if (res == (int)BrainFlowExitCodes::STATUS_OK)
+        {
+            advertise_socket_server =
+                new BroadCastServer (broadcast_address.c_str (), WIFI_ADVERTISING_PORT);
+            int init_res = advertise_socket_server->init ();
+            if (init_res != (int)BroadCastServerReturnCodes::STATUS_OK)
+            {
+                safe_logger (spdlog::level::err, "failed to init broadcast server socket: {}", res);
+                res = (int)BrainFlowExitCodes::GENERAL_ERROR;
+            }
+        }
+        if (res == (int)BrainFlowExitCodes::STATUS_OK)
+        {
+            std::string package = create_package (HELLO_EMOTIBIT, 0, "", 0);
+            safe_logger (spdlog::level::info, "sending package: {}", package.c_str ());
+            int bytes_send = advertise_socket_server->send (package.c_str (), (int)package.size ());
+            if (bytes_send != (int)package.size ())
+            {
+                safe_logger (
+                    spdlog::level::err, "failed to send adv package, res is {}", bytes_send);
+                res = (int)BrainFlowExitCodes::GENERAL_ERROR;
+            }
+        }
+        if (res == (int)BrainFlowExitCodes::STATUS_OK)
+        {
+            constexpr int max_size = 32768;
+            constexpr int max_ip_addr_size = 100;
+            char recv_data[max_size];
+            char emotibit_ip[max_ip_addr_size];
+            bool found = false;
+            double start_time = get_timestamp ();
+            for (int i = 0; (i < 100) && (!found); i++)
+            {
+                int bytes_recv = advertise_socket_server->recv (
+                    recv_data, max_size, emotibit_ip, max_ip_addr_size);
+                if (bytes_recv > 0)
+                {
+                    std::vector<std::string> splitted_packages =
+                        split_string (std::string (recv_data, bytes_recv), PACKET_DELIMITER_CSV);
+                    for (std::string recv_package : splitted_packages)
+                    {
+                        safe_logger (spdlog::level::trace, "package is {}", recv_package.c_str ());
+                        int package_num = 0;
+                        int data_len = 0;
+                        std::string type_tag = "";
+                        if (get_header (recv_package, &package_num, &data_len, type_tag))
+                        {
+                            safe_logger (spdlog::level::info, "received {} package", type_tag);
+                            if ((type_tag == HELLO_HOST) || (type_tag == PONG))
+                            {
+                                found = true;
+                                ip_address = emotibit_ip;
+                            }
+                        }
+                        else
+                        {
+                            safe_logger (spdlog::level::trace, "invalid header, package is: {}",
+                                recv_package.c_str ());
+                        }
+                    }
+                }
+                if (get_timestamp () - start_time > params.timeout)
+                {
+                    break;
+                }
+            }
+            if (!found)
+            {
+                safe_logger (spdlog::level::err, "no emotibit found");
+                res = (int)BrainFlowExitCodes::BOARD_NOT_READY_ERROR;
+            }
+        }
+        if (res != (int)BrainFlowExitCodes::STATUS_OK)
+        {
+            if (advertise_socket_server != NULL)
+            {
+                delete advertise_socket_server;
+                advertise_socket_server = NULL;
+            }
+        }
+        else
+        {
+            break;
+        }
+    }
+    return res;
+}
+
+int Emotibit::create_data_connection ()
+{
+    int res = (int)BrainFlowExitCodes::BOARD_NOT_READY_ERROR;
+    for (int i = 2; i < 40; i += 2)
+    {
+        data_port = WIFI_ADVERTISING_PORT + i;
+        data_socket = new SocketClientUDP (ip_address.c_str (), data_port);
+        if (data_socket->bind () == ((int)SocketClientUDPReturnCodes::STATUS_OK))
+        {
+            safe_logger (spdlog::level::info, "use port {} for data", data_port);
+            res = (int)BrainFlowExitCodes::STATUS_OK;
+            break;
+        }
+        else
+        {
+            safe_logger (spdlog::level::warn, "failed to bind to {}", data_port);
+        }
+        data_socket->close ();
+        delete data_socket;
+        data_socket = NULL;
+    }
+    return res;
+}
+
+int Emotibit::create_control_connection ()
+{
+    char local_ip[80];
+    int local_ip_res =
+        SocketClientUDP::get_local_ip_addr (ip_address.c_str (), WIFI_ADVERTISING_PORT, local_ip);
+    if (local_ip_res != (int)SocketClientUDPReturnCodes::STATUS_OK)
+    {
+        safe_logger (spdlog::level::err, "failed to get local ip addr: {}", local_ip_res);
+        return (int)BrainFlowExitCodes::GENERAL_ERROR;
+    }
+    safe_logger (spdlog::level::info, "local ip addr is {}", local_ip);
+
+    int res = (int)BrainFlowExitCodes::BOARD_NOT_READY_ERROR;
+    for (int i = 1; i < 39; i += 2)
+    {
+        control_port = WIFI_ADVERTISING_PORT + i;
+        control_socket = new SocketServerTCP (local_ip, control_port, true);
+        if (control_socket->bind () == ((int)SocketServerTCPReturnCodes::STATUS_OK))
+        {
+            safe_logger (spdlog::level::info, "use port {} for control", control_port);
+            res = (int)BrainFlowExitCodes::STATUS_OK;
+            break;
+        }
+        else
+        {
+            safe_logger (spdlog::level::warn, "failed to connect to {}", control_port);
+        }
+        control_socket->close ();
+        delete control_socket;
+        control_socket = NULL;
+    }
+    return res;
+}
+
+int Emotibit::send_connect_msg ()
+{
+    // should never happen
+    if ((control_port < 0) || (data_port < 0))
+    {
+        safe_logger (spdlog::level::info, "ports for data or control are not set");
+        return (int)BrainFlowExitCodes::GENERAL_ERROR;
+    }
+
+    std::vector<std::string> payload;
+    payload.push_back (CONTROL_PORT);
+    payload.push_back (std::to_string (control_port));
+    payload.push_back (DATA_PORT);
+    payload.push_back (std::to_string (data_port));
+    std::string package = create_package (EMOTIBIT_CONNECT, 0, payload);
+    safe_logger (spdlog::level::info, "sending connect package: {}", package.c_str ());
+
+    int res = (int)BrainFlowExitCodes::STATUS_OK;
+    int bytes_send = advertise_socket_server->send (package.c_str (), (int)package.size ());
+    if (bytes_send != (int)package.size ())
+    {
+        safe_logger (spdlog::level::err, "failed to send connect package, res is {}", bytes_send);
+        res = (int)BrainFlowExitCodes::GENERAL_ERROR;
+    }
+    return res;
+}
+
+int Emotibit::send_control_msg (const char *msg)
+{
+    // should never happen
+    if ((control_port < 0) || (data_port < 0))
+    {
+        safe_logger (spdlog::level::info, "ports for data or control are not set");
+        return (int)BrainFlowExitCodes::GENERAL_ERROR;
+    }
+
+    std::string package = create_package (msg, 0, "", 0);
+
+    int res = (int)BrainFlowExitCodes::STATUS_OK;
+    int bytes_send = control_socket->send (package.c_str (), (int)package.size ());
+    if (bytes_send != (int)package.size ())
+    {
+        safe_logger (spdlog::level::err, "failed to send control msg package: {}, res is {}", msg,
+            bytes_send);
+        res = (int)BrainFlowExitCodes::GENERAL_ERROR;
+    }
+    else
+    {
+        safe_logger (spdlog::level::info, "Message: {} sent", msg);
+    }
+    return res;
+}
+
+int Emotibit::wait_for_connection ()
+{
+    int res = (int)BrainFlowExitCodes::STATUS_OK;
+    int accept_res = control_socket->accept ();
+    if (accept_res != (int)SocketServerTCPReturnCodes::STATUS_OK)
+    {
+        safe_logger (spdlog::level::err, "error in accept");
+        res = (int)BrainFlowExitCodes::GENERAL_ERROR;
+    }
+    else
+    {
+        int max_attempts = 15;
+        for (int i = 0; i < max_attempts; i++)
+        {
+            safe_logger (spdlog::level::trace, "waiting for accept {}/{}", i, max_attempts);
+            if (control_socket->client_connected)
+            {
+                safe_logger (spdlog::level::trace, "emotibit connected");
+                break;
+            }
+            else
+            {
+#ifdef _WIN32
+                Sleep (300);
+#else
+                usleep (300000);
+#endif
+            }
+        }
+        if (!control_socket->client_connected)
+        {
+            safe_logger (spdlog::level::trace, "failed to establish connection");
+            res = (int)BrainFlowExitCodes::BOARD_NOT_READY_ERROR;
+        }
+    }
+    return res;
+}
+
+void Emotibit::ping_thread ()
+{
+    int sleep_time = 1;
+    int package_num = 1;
+    while (initialized)
+    {
+#ifdef _WIN32
+        Sleep (sleep_time * 1000);
+#else
+        usleep (sleep_time * 1000000);
+#endif
+        std::vector<std::string> payload;
+        payload.push_back (DATA_PORT);
+        payload.push_back (std::to_string (data_port));
+        std::string package = create_package (PING, package_num++, payload);
+        // safe_logger (spdlog::level::trace, "sending package: {}", package.c_str ());
+        int bytes_send = advertise_socket_server->send (package.c_str (), (int)package.size ());
+        if (bytes_send != (int)package.size ())
+        {
+            safe_logger (spdlog::level::err, "failed to send adv package, res is {}", bytes_send);
+        }
+    }
+}
diff --git a/src/board_controller/emotibit/inc/emotibit.h b/src/board_controller/emotibit/inc/emotibit.h
new file mode 100644
index 000000000..e639e822a
--- /dev/null
+++ b/src/board_controller/emotibit/inc/emotibit.h
@@ -0,0 +1,66 @@
+#pragma once
+
+#include <stdint.h>
+#include <string>
+#include <thread>
+
+#include "board.h"
+#include "board_controller.h"
+
+#include "broadcast_server.h"
+#include "socket_client_udp.h"
+#include "socket_server_tcp.h"
+
+
+class Emotibit : public Board
+{
+
+private:
+    volatile bool keep_alive;
+    volatile bool initialized;
+
+    std::string ip_address;
+    std::thread streaming_thread;
+    std::thread connection_thread;
+    SocketClientUDP *data_socket;
+    SocketServerTCP *control_socket;
+    BroadCastServer *advertise_socket_server;
+    std::mutex m;
+    std::condition_variable cv;
+    int control_port;
+    int data_port;
+
+    void read_thread ();
+    void ping_thread ();
+
+    std::string create_package (const std::string &type_tag, uint16_t packet_number,
+        const std::string &data, uint16_t data_length, uint8_t protocol_version = 1,
+        uint8_t data_reliability = 100);
+    std::string create_package (const std::string &type_tag, uint16_t packet_number,
+        std::vector<std::string> data, uint8_t protocol_version = 1,
+        uint8_t data_reliability = 100);
+    std::string create_header (const std::string &type_tag, uint32_t timestamp,
+        uint16_t packet_number, uint16_t data_length, uint8_t protocol_version = 1,
+        uint8_t data_reliability = 100);
+    std::vector<std::string> split_string (const std::string &package, char delim);
+    bool get_header (
+        const std::string &package_string, int *package_num, int *data_len, std::string &type_tag);
+    std::vector<std::string> get_payload (const std::string &package_string, int data_len);
+
+    int create_adv_connection ();
+    int create_data_connection ();
+    int create_control_connection ();
+    int send_connect_msg ();
+    int send_control_msg (const char *msg);
+    int wait_for_connection ();
+
+public:
+    Emotibit (struct BrainFlowInputParams params);
+    ~Emotibit ();
+
+    int prepare_session ();
+    int start_stream (int buffer_size, const char *streamer_params);
+    int stop_stream ();
+    int release_session ();
+    int config_board (std::string config, std::string &response);
+};
\ No newline at end of file
diff --git a/src/board_controller/emotibit/inc/emotibit_defines.h b/src/board_controller/emotibit/inc/emotibit_defines.h
new file mode 100644
index 000000000..8404fc0fa
--- /dev/null
+++ b/src/board_controller/emotibit/inc/emotibit_defines.h
@@ -0,0 +1,79 @@
+#pragma once
+
+#define EDA "EA\0"
+#define EDL "EL\0"
+#define EDR "ER\0"
+#define PPG_INFRARED "PI\0"
+#define PPG_RED "PR\0"
+#define PPG_GREEN "PG\0"
+#define SPO2 "O2\0"
+#define TEMPERATURE_0 "T0\0"
+#define TEMPERATURE_1 "T1\0"
+#define THERMOPILE "TH\0"
+#define HUMIDITY_0 "H0\0"
+#define ACCELEROMETER_X "AX\0"
+#define ACCELEROMETER_Y "AY\0"
+#define ACCELEROMETER_Z "AZ\0"
+#define GYROSCOPE_X "GX\0"
+#define GYROSCOPE_Y "GY\0"
+#define GYROSCOPE_Z "GZ\0"
+#define MAGNETOMETER_X "MX\0"
+#define MAGNETOMETER_Y "MY\0"
+#define MAGNETOMETER_Z "MZ\0"
+#define BATTERY_VOLTAGE "BV\0"
+#define BATTERY_PERCENT "B%\0"
+#define BUTTON_PRESS_SHORT "BS\0"
+#define BUTTON_PRESS_LONG "BL\0"
+#define DATA_CLIPPING "DC\0"
+#define DATA_OVERFLOW "DO\0"
+#define SD_CARD_PERCENT "SD\0"
+#define RESET "RS\0" // still necessary?
+#define EMOTIBIT_DEBUG "DB\0"
+#define ACK "AK\0"
+#define REQUEST_DATA "RD\0"
+#define TIMESTAMP_EMOTIBIT "TE\0"
+#define TIMESTAMP_LOCAL "TL\0"
+#define TIMESTAMP_UTC "TU\0"
+#define TIMESTAMP_CROSS_TIME "TX\0"
+#define EMOTIBIT_MODE "EM\0"
+#define EMOTIBIT_INFO "EI\0"
+#define HEART_RATE "HR\0"
+#define INTER_BEAT_INTERVAL "BI\0"
+#define SKIN_CONDUCTANCE_RESPONSE_AMPLITUDE "SA\0"
+#define SKIN_CONDUCTANCE_RESPONSE_FREQ "SF\0"
+#define SKIN_CONDUCTANCE_RESPONSE_RISE_TIME "SR\0"
+// Computer data TypeTags (sent over reliable channel e.g. Control)
+#define GPS_LATLNG "GL\0"
+#define GPS_SPEED "GS\0"
+#define GPS_BEARING "GB\0"
+#define GPS_ALTITUDE "GA\0"
+#define USER_NOTE "UN\0"
+#define LSL_MARKER "LM\0"
+// Control TypeTags
+#define RECORD_BEGIN "RB\0"
+#define RECORD_END "RE\0"
+#define MODE_NORMAL_POWER "MN\0"  // Stops sending data timestamping should be accurate
+#define MODE_LOW_POWER "ML\0"     // Stops sending data timestamping should be accurate
+#define MODE_MAX_LOW_POWER "MM\0" // Stops sending data timestamping accuracy drops
+#define MODE_WIRELESS_OFF "MO\0"  // Stops sending data timestamping should be accurate
+#define MODE_HIBERNATE "MH\0"     // Full shutdown of all operation
+#define EMOTIBIT_DISCONNECT "ED\0"
+#define SERIAL_DATA_ON "S+\0"
+#define SERIAL_DATA_OFF "S-\0"
+// Advertising TypeTags
+#define PING "PN\0"
+#define PONG "PO\0"
+#define HELLO_EMOTIBIT "HE\0"
+#define HELLO_HOST "HH\0"
+#define EMOTIBIT_CONNECT "EC\0"
+
+#define CONTROL_PORT "CP\0"
+#define DATA_PORT "DP\0"
+#define RECORDING_STATUS "RS\0"
+#define POWER_STATUS "PS\0"
+
+#define PACKET_DELIMITER_CSV '\n'
+#define PAYLOAD_DELIMITER ','
+
+#define HEADER_LENGTH 6
+#define WIFI_ADVERTISING_PORT 3131
\ No newline at end of file
diff --git a/src/board_controller/inc/board_info_getter.h b/src/board_controller/inc/board_info_getter.h
index 0eff79371..53a423e51 100644
--- a/src/board_controller/inc/board_info_getter.h
+++ b/src/board_controller/inc/board_info_getter.h
@@ -48,6 +48,8 @@ extern "C"
         int board_id, int preset, int *temperature_channels, int *len);
     SHARED_EXPORT int CALLING_CONVENTION get_resistance_channels (
         int board_id, int preset, int *resistance_channels, int *len);
+    SHARED_EXPORT int CALLING_CONVENTION get_magnetometer_channels (
+        int board_id, int preset, int *magnetometer_channels, int *len);
     SHARED_EXPORT int CALLING_CONVENTION get_device_name (
         int board_id, int preset, char *name, int *len);
     SHARED_EXPORT int CALLING_CONVENTION get_board_presets (int board_id, int *presets, int *len);
diff --git a/src/board_controller/mentalab/explore.cpp b/src/board_controller/mentalab/explore.cpp
index 8289c428c..80593cfe6 100644
--- a/src/board_controller/mentalab/explore.cpp
+++ b/src/board_controller/mentalab/explore.cpp
@@ -339,7 +339,7 @@ void Explore::parse_orientation_data (
 
     std::vector<int> accel_channels = board_descr["auxiliary"]["accel_channels"];
     std::vector<int> gyro_channels = board_descr["auxiliary"]["gyro_channels"];
-    std::vector<int> other_channels = board_descr["auxiliary"]["other_channels"];
+    std::vector<int> magnetometer_channels = board_descr["auxiliary"]["magnetometer_channels"];
 
     if (payload_size % 2 != 0) // 2 is int16
     {
@@ -365,7 +365,7 @@ void Explore::parse_orientation_data (
             {
                 data *= -1; // no idea why, copypaste, maybe bug in fw
             }
-            package[other_channels[i - 6]] = 1.52 * data;
+            package[magnetometer_channels[i - 6]] = 1.52 * data;
         }
     }
     package[board_descr["auxiliary"]["timestamp_channel"].get<int> ()] = get_timestamp ();
diff --git a/src/utils/broadcast_server.cpp b/src/utils/broadcast_server.cpp
new file mode 100644
index 000000000..a045f0028
--- /dev/null
+++ b/src/utils/broadcast_server.cpp
@@ -0,0 +1,193 @@
+#include <string.h>
+
+#include "broadcast_server.h"
+
+
+///////////////////////////////
+/////////// WINDOWS ///////////
+//////////////////////////////
+#ifdef _WIN32
+
+#ifndef INET_ADDRSTRLEN
+#define INET_ADDRSTRLEN 46
+#endif
+
+#pragma comment(lib, "Ws2_32.lib")
+#pragma comment(lib, "Mswsock.lib")
+#pragma comment(lib, "AdvApi32.lib")
+
+
+BroadCastServer::BroadCastServer (int port)
+{
+    this->port = port;
+    connect_socket = INVALID_SOCKET;
+    memset (&socket_addr, 0, sizeof (socket_addr));
+    strcpy (address, "255.255.255.255");
+}
+
+BroadCastServer::BroadCastServer (const char *address, int port)
+{
+    this->port = port;
+    connect_socket = INVALID_SOCKET;
+    memset (&socket_addr, 0, sizeof (socket_addr));
+    strcpy (this->address, address);
+}
+
+int BroadCastServer::init ()
+{
+    WSADATA wsadata;
+    int res = WSAStartup (MAKEWORD (2, 2), &wsadata);
+    if (res != 0)
+    {
+        return (int)BroadCastServerReturnCodes::WSA_STARTUP_ERROR;
+    }
+    socket_addr.sin_family = AF_INET;
+    socket_addr.sin_port = htons (port);
+    if (inet_pton (AF_INET, address, &socket_addr.sin_addr) == 0)
+    {
+        return (int)BroadCastServerReturnCodes::INIT_ERROR;
+    }
+    connect_socket = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+    if (connect_socket == INVALID_SOCKET)
+    {
+        return (int)BroadCastServerReturnCodes::CREATE_SOCKET_ERROR;
+    }
+
+    // ensure that library will not hang in blocking recv/send call
+    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));
+    DWORD broadcast = 1;
+    setsockopt (connect_socket, SOL_SOCKET, SO_BROADCAST, (char *)&broadcast, sizeof (broadcast));
+
+    return (int)BroadCastServerReturnCodes::STATUS_OK;
+}
+
+int BroadCastServer::send (const char *data, int size)
+{
+    int len = sizeof (socket_addr);
+    int res = sendto (connect_socket, data, size, 0, (sockaddr *)&socket_addr, len);
+    if (res == SOCKET_ERROR)
+    {
+        return -1;
+    }
+    return res;
+}
+
+int BroadCastServer::recv (void *data, int size, char *sender_ip, int max_len)
+{
+    char ip_address_sender[INET_ADDRSTRLEN];
+    memset (ip_address_sender, 0, sizeof (char) * INET_ADDRSTRLEN);
+    struct sockaddr_in socket_addr_recv;
+    memset (&socket_addr_recv, 0, sizeof (socket_addr_recv));
+    int len = sizeof (socket_addr_recv);
+    int res = recvfrom (connect_socket, (char *)data, size, 0, (sockaddr *)&socket_addr_recv, &len);
+    if (res == SOCKET_ERROR)
+    {
+        return -1;
+    }
+    if (inet_ntop (AF_INET, &(socket_addr_recv.sin_addr), ip_address_sender, INET_ADDRSTRLEN) ==
+        NULL)
+    {
+        return -2;
+    }
+    strncpy (sender_ip, ip_address_sender, max_len);
+    return res;
+}
+
+void BroadCastServer::close ()
+{
+    closesocket (connect_socket);
+    connect_socket = INVALID_SOCKET;
+    WSACleanup ();
+}
+
+///////////////////////////////
+//////////// UNIX /////////////
+///////////////////////////////
+#else
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#ifndef INET_ADDRSTRLEN
+#define INET_ADDRSTRLEN 46
+#endif
+
+
+BroadCastServer::BroadCastServer (int port)
+{
+    this->port = port;
+    connect_socket = -1;
+    memset (&socket_addr, 0, sizeof (socket_addr));
+    strcpy (address, "255.255.255.255");
+}
+
+BroadCastServer::BroadCastServer (const char *address, int port)
+{
+    this->port = port;
+    connect_socket = -1;
+    memset (&socket_addr, 0, sizeof (socket_addr));
+    strcpy (this->address, address);
+}
+
+int BroadCastServer::init ()
+{
+    connect_socket = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+    if (connect_socket < 0)
+    {
+        return (int)BroadCastServerReturnCodes::CREATE_SOCKET_ERROR;
+    }
+
+    socket_addr.sin_family = AF_INET;
+    socket_addr.sin_port = htons (port);
+    socket_addr.sin_addr.s_addr = htonl (INADDR_ANY);
+    if (inet_pton (AF_INET, address, &socket_addr.sin_addr) == 0)
+    {
+        return (int)BroadCastServerReturnCodes::INIT_ERROR;
+    }
+
+    // ensure that library will not hang in blocking recv/send call
+    struct timeval tv;
+    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));
+    int broadcast = 1;
+    setsockopt (connect_socket, SOL_SOCKET, SO_BROADCAST, (char *)&broadcast, sizeof (broadcast));
+    return (int)BroadCastServerReturnCodes::STATUS_OK;
+}
+
+int BroadCastServer::send (const char *data, int size)
+{
+    socklen_t len = (socklen_t)sizeof (socket_addr);
+    int res = sendto (connect_socket, data, size, 0, (sockaddr *)&socket_addr, len);
+    return res;
+}
+
+int BroadCastServer::recv (void *data, int size, char *sender_ip, int max_len)
+{
+    char ip_address_sender[INET_ADDRSTRLEN];
+    memset (ip_address_sender, 0, sizeof (char) * INET_ADDRSTRLEN);
+    struct sockaddr_in socket_addr_recv;
+    memset (&socket_addr_recv, 0, sizeof (socket_addr_recv));
+    socklen_t len = (socklen_t)sizeof (socket_addr_recv);
+    int res = recvfrom (connect_socket, (char *)data, size, 0, (sockaddr *)&socket_addr_recv, &len);
+    if (res > 0)
+    {
+        if (inet_ntop (AF_INET, &(socket_addr_recv.sin_addr), ip_address_sender, INET_ADDRSTRLEN) ==
+            NULL)
+        {
+            return -2;
+        }
+    }
+    strncpy (sender_ip, ip_address_sender, max_len);
+    return res;
+}
+
+void BroadCastServer::close ()
+{
+    ::close (connect_socket);
+    connect_socket = -1;
+}
+#endif
diff --git a/src/utils/inc/brainflow_constants.h b/src/utils/inc/brainflow_constants.h
index 7ae72eeaa..1688ff7b9 100644
--- a/src/utils/inc/brainflow_constants.h
+++ b/src/utils/inc/brainflow_constants.h
@@ -80,9 +80,10 @@ enum class BoardIds : int
     EXPLORE_4_CHAN_BOARD = 44,
     EXPLORE_8_CHAN_BOARD = 45,
     GANGLION_NATIVE_BOARD = 46,
+    EMOTIBIT_BOARD = 47,
     // use it to iterate
     FIRST = PLAYBACK_FILE_BOARD,
-    LAST = GANGLION_NATIVE_BOARD
+    LAST = EMOTIBIT_BOARD
 };
 
 enum class IpProtocolTypes : int
diff --git a/src/utils/inc/broadcast_server.h b/src/utils/inc/broadcast_server.h
new file mode 100644
index 000000000..6be97f913
--- /dev/null
+++ b/src/utils/inc/broadcast_server.h
@@ -0,0 +1,50 @@
+#pragma once
+
+#ifdef _WIN32
+#include <ws2tcpip.h>
+#else
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+
+enum class BroadCastServerReturnCodes : int
+{
+    STATUS_OK = 0,
+    WSA_STARTUP_ERROR = 1,
+    CREATE_SOCKET_ERROR = 2,
+    INIT_ERROR = 3
+};
+
+
+class BroadCastServer
+{
+
+public:
+    BroadCastServer (int port);
+    BroadCastServer (const char *address, int port);
+    ~BroadCastServer ()
+    {
+        close ();
+    }
+
+    int init ();
+    int send (const char *data, int size);
+    int recv (void *data, int size, char *sender_ip, int max_len);
+    void close ();
+
+private:
+    int port;
+    char address[64];
+#ifdef _WIN32
+    SOCKET connect_socket;
+    struct sockaddr_in socket_addr;
+#else
+    int connect_socket;
+    struct sockaddr_in socket_addr;
+#endif
+};
diff --git a/src/utils/inc/network_interfaces.h b/src/utils/inc/network_interfaces.h
new file mode 100644
index 000000000..7314ad4c1
--- /dev/null
+++ b/src/utils/inc/network_interfaces.h
@@ -0,0 +1,107 @@
+#pragma once
+
+#include <string>
+#include <vector>
+
+#ifdef _WIN32
+
+#include <iphlpapi.h>
+#include <winsock2.h>
+#include <ws2tcpip.h>
+
+#pragma comment(lib, "iphlpapi.lib")
+#pragma comment(lib, "ws2_32.lib")
+
+static std::vector<std::string> get_broadcast_addresses ()
+{
+    std::vector<std::string> result;
+    int i = 0;
+    PMIB_IPADDRTABLE p_ip_addr_table;
+    DWORD dw_size = 0;
+    DWORD dw_ret_val = 0;
+    IN_ADDR ip_addr;
+
+    p_ip_addr_table = (MIB_IPADDRTABLE *)malloc (sizeof (MIB_IPADDRTABLE));
+    if (p_ip_addr_table)
+    {
+        // Make an initial call to GetIpAddrTable to get the
+        // necessary size into the dw_size variable
+        if (GetIpAddrTable (p_ip_addr_table, &dw_size, 0) == ERROR_INSUFFICIENT_BUFFER)
+        {
+            free (p_ip_addr_table);
+            p_ip_addr_table = (MIB_IPADDRTABLE *)malloc (dw_size);
+        }
+        if (p_ip_addr_table == NULL)
+        {
+            return result;
+        }
+    }
+    // Make a second call to GetIpAddrTable to get the
+    // actual data we want
+    if ((dw_ret_val = GetIpAddrTable (p_ip_addr_table, &dw_size, 0)) != NO_ERROR)
+    {
+        return result;
+    }
+
+    for (i = 0; i < (int)p_ip_addr_table->dwNumEntries; i++)
+    {
+        ip_addr.S_un.S_addr = p_ip_addr_table->table[i].dwAddr | ~p_ip_addr_table->table[i].dwMask;
+        char bw_addr[32];
+        if (inet_ntop (AF_INET, &ip_addr, bw_addr, 32) != NULL)
+        {
+            result.push_back (bw_addr);
+        }
+    }
+
+    if (p_ip_addr_table)
+    {
+        free (p_ip_addr_table);
+        p_ip_addr_table = NULL;
+    }
+    return result;
+}
+
+#else
+
+#include <arpa/inet.h>
+#include <ifaddrs.h>
+#include <sys/socket.h>
+
+#include <algorithm>
+
+static std::vector<std::string> get_broadcast_addresses ()
+{
+    std::vector<std::string> result;
+    struct ifaddrs *ifap = NULL;
+    struct ifaddrs *ifa = NULL;
+    char bw_addr[32];
+    struct in_addr ip_addr;
+    ip_addr.s_addr = 0;
+
+    if (getifaddrs (&ifap) == 0)
+    {
+        for (ifa = ifap; ifa; ifa = ifa->ifa_next)
+        {
+            if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET)
+            {
+                ip_addr.s_addr = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr |
+                    ~(((struct sockaddr_in *)ifa->ifa_netmask)->sin_addr.s_addr);
+                if (inet_ntop (AF_INET, &ip_addr, bw_addr, 32) != NULL)
+                {
+                    // somehow it creates duplicates
+                    std::string bw_str = bw_addr;
+                    if (std::find (result.begin (), result.end (), bw_str) == result.end ())
+                    {
+                        result.push_back (bw_str);
+                    }
+                }
+            }
+        }
+
+        freeifaddrs (ifap);
+    }
+
+    return result;
+}
+
+#endif
diff --git a/src/utils/inc/socket_client_udp.h b/src/utils/inc/socket_client_udp.h
index 123240054..711d7153e 100644
--- a/src/utils/inc/socket_client_udp.h
+++ b/src/utils/inc/socket_client_udp.h
@@ -19,7 +19,8 @@ enum class SocketClientUDPReturnCodes : int
     CREATE_SOCKET_ERROR = 2,
     CONNECT_ERROR = 3,
     PTON_ERROR = 4,
-    INVALID_ARGUMENT_ERROR = 5
+    INVALID_ARGUMENT_ERROR = 5,
+    SOCKET_ALREADY_CREATED_ERROR = 6
 };
 
 
diff --git a/src/utils/inc/socket_server_tcp.h b/src/utils/inc/socket_server_tcp.h
index f66759b0e..d86fefd80 100644
--- a/src/utils/inc/socket_server_tcp.h
+++ b/src/utils/inc/socket_server_tcp.h
@@ -36,6 +36,7 @@ class SocketServerTCP
     int bind ();
     int accept ();
     int recv (void *data, int size);
+    int send (const void *data, int size);
     void close ();
     void accept_worker ();
 
diff --git a/src/utils/socket_client_udp.cpp b/src/utils/socket_client_udp.cpp
index e711f2d52..ce0b20c92 100644
--- a/src/utils/socket_client_udp.cpp
+++ b/src/utils/socket_client_udp.cpp
@@ -141,10 +141,31 @@ int SocketClientUDP::bind ()
     // for socket clients which dont call sendto before recvfrom bind should be called on client
     // side
     // https://stackoverflow.com/questions/3057029/do-i-have-to-bind-a-udp-socket-in-my-client-program-to-receive-data-i-always-g
+    if (connect_socket != INVALID_SOCKET)
+    {
+        return (int)SocketClientUDPReturnCodes::SOCKET_ALREADY_CREATED_ERROR;
+    }
+
+    WSADATA wsadata;
+    int res = WSAStartup (MAKEWORD (2, 2), &wsadata);
+    if (res != 0)
+    {
+        return (int)SocketClientUDPReturnCodes::WSA_STARTUP_ERROR;
+    }
+    socket_addr.sin_family = AF_INET;
+    socket_addr.sin_port = htons (port);
+    socket_addr.sin_addr.s_addr = htonl (INADDR_ANY);
+    connect_socket = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP);
     if (connect_socket == INVALID_SOCKET)
     {
         return (int)SocketClientUDPReturnCodes::CREATE_SOCKET_ERROR;
     }
+
+    // ensure that library will not hang in blocking recv/send call
+    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 (::bind (connect_socket, (const struct sockaddr *)&socket_addr, sizeof (socket_addr)) != 0)
     {
         return (int)SocketClientUDPReturnCodes::CONNECT_ERROR;
@@ -328,10 +349,28 @@ int SocketClientUDP::bind ()
     // for socket clients which dont call sendto before recvfrom bind should be called on client
     // side
     // https://stackoverflow.com/questions/3057029/do-i-have-to-bind-a-udp-socket-in-my-client-program-to-receive-data-i-always-g
+    if (connect_socket >= 0)
+    {
+        return (int)SocketClientUDPReturnCodes::SOCKET_ALREADY_CREATED_ERROR;
+    }
+
+    connect_socket = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP);
     if (connect_socket < 0)
     {
         return (int)SocketClientUDPReturnCodes::CREATE_SOCKET_ERROR;
     }
+
+    socket_addr.sin_family = AF_INET;
+    socket_addr.sin_port = htons (port);
+    socket_addr.sin_addr.s_addr = htonl (INADDR_ANY);
+
+    // ensure that library will not hang in blocking recv/send call
+    struct timeval tv;
+    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));
+
     if (::bind (connect_socket, (const struct sockaddr *)&socket_addr, sizeof (socket_addr)) != 0)
     {
         return (int)SocketClientUDPReturnCodes::CONNECT_ERROR;
diff --git a/src/utils/socket_server_tcp.cpp b/src/utils/socket_server_tcp.cpp
index 1ab7cd56f..be90dee81 100644
--- a/src/utils/socket_server_tcp.cpp
+++ b/src/utils/socket_server_tcp.cpp
@@ -96,6 +96,20 @@ void SocketServerTCP::accept_worker ()
     }
 }
 
+int SocketServerTCP::send (const void *data, int size)
+{
+    if (connected_socket == INVALID_SOCKET)
+    {
+        return -1;
+    }
+    int res = ::send (connected_socket, (const char *)data, size, 0);
+    if (res == SOCKET_ERROR)
+    {
+        return -1;
+    }
+    return res;
+}
+
 int SocketServerTCP::recv (void *data, int size)
 {
     if (connected_socket == INVALID_SOCKET)
@@ -245,6 +259,16 @@ void SocketServerTCP::accept_worker ()
     }
 }
 
+int SocketServerTCP::send (const void *data, int size)
+{
+    if (connected_socket <= 0)
+    {
+        return -1;
+    }
+    int res = ::send (connected_socket, (const char *)data, size, 0);
+    return res;
+}
+
 int SocketServerTCP::recv (void *data, int size)
 {
     if (connected_socket <= 0)


Test patch:
diff --git a/python_package/examples/tests/emotibit_test.py b/python_package/examples/tests/emotibit_test.py
new file mode 100644
index 000000000..4c0eaba03
--- /dev/null
+++ b/python_package/examples/tests/emotibit_test.py
@@ -0,0 +1,31 @@
+import time
+
+from brainflow.board_shim import BoardShim, BrainFlowInputParams, BoardIds, BrainFlowPresets
+from brainflow.data_filter import DataFilter
+
+
+def main():
+    BoardShim.enable_dev_board_logger()
+
+    params = BrainFlowInputParams()
+    board_id = BoardIds.EMOTIBIT_BOARD.value
+
+    presets = BoardShim.get_board_presets(board_id)
+    print (presets)
+    
+    board = BoardShim(board_id, params)
+    board.prepare_session()
+    board.start_stream()
+    time.sleep(10)
+    data_default = board.get_board_data(preset=BrainFlowPresets.DEFAULT_PRESET)
+    data_aux = board.get_board_data(preset=BrainFlowPresets.AUXILIARY_PRESET)
+    data_anc = board.get_board_data(preset=BrainFlowPresets.ANCILLARY_PRESET)
+    board.stop_stream()
+    board.release_session()
+    DataFilter.write_file(data_default, 'default.csv', 'w')
+    DataFilter.write_file(data_aux, 'aux.csv', 'w')
+    DataFilter.write_file(data_anc, 'anc.csv', 'w')
+
+
+if __name__ == "__main__":
+    main()