T1 Arduino and 2 RaspBerry

Paolo Bellagente edited this page Mar 13, 2017 · 17 revisions

NOTE: You may find the full source of this application here

Introduction

In this document the steps to build an Android App to handle an Arduino and 2 Raspberry boards using the SAndroidE framework are described.

The set up is composed by the following SAndroidE compatible/described devices:

The system described allows to handle the buttons and leds attached to Arduino's and Raspberry pi3 GPIOs, exploiting a SAndroidE smartphone application. Arduino needs the BLE shield to communicate with smartphone via Bluetooth, whereas Bluetooth 4.1 is built-in for the Raspberry pi3. The application implements the following logic: pressing the button connected to Arduino lights up the led attached to RaspA. Pressing the button connected to RaspA lights up the led attached to RaspB and finally pressing the button connected to RaspB button lights up the led attached to the Arduino.

Here you can see the logic of this application... SchemaPrincipio.png

... and here the practical implementation. arduino-2rasp.jpg

NOTE: As you can see from the picture, the circuit for Arduino is different from those for the two Raspberry. This is due to the fact our Raspberry devices are set up in pull-up mode. We could eventually change the Raspberry mode or change our application source to invert HIGH and LOW values.


Requirements

  1. One Arduino UNO
  2. One BLE Shield v2.0 or later from RedBearLab
  3. 3 breadboard buttons
  4. 3 LED
  5. 4 220Ω resistors
  6. A USB cable to connect Arduino to the PC
  7. Two Raspberry Pi 3
  8. A smartphone supporting Bluetooth 4.0-4.1 (aka Bluetooth Smart)
  9. A micro USB cable to deploy application to your smartphone
  10. Android Studio
  11. SAndroidE binaries zip package: download the zip file and extract to a directory
  12. Basic programming knowledge of Android applications

Mount the circuit

In the following picture you can see how to mount the hardware to be ready for test your App:

NOTE: Unfortunately, we don't have the correct image for the RedBear BLE Shield, so you wont see it in the figure. Anyway, the pin are exactly the same, so it shouldn't be a problem to connect them.

Schema2.png


Prepare your app for SAndroidE

Some steps are common to all SAndroidE based projects. Please follow our Prepare your app for SAndroidE guide before proceeding with next steps.


Usage

Implementation consists of three steps:

  1. Devices firmware flashing (if needed)during this operation, the software necessary for the correct functioning of the involved devices (Arduino UNO and BLE Shield, and Raspberry Pi) is uploaded on them.
  2. Enumerating the resources provided by external devices we want to interact with (only the first time); with this operation, the files necessary to make the smartphone and the external device communicate are uploaded into the smartphone, allowing it to establish such communication.
  3. Developing an Android app which exploits the SAndrodE framework to implement the desired application logic.

Firmware flashing

Arduino

First of all, if needed, upload the 'BLEControllerSketch' fournished by RedBearLab as described at http://redbearlab.com/getting-started-bleshield on Arduino. SAndroidE framework handles the the remote devices resources exploiting the description of the firmware provided by the .xml files. How to describe a new device/firmware is described [here](Support a new device).

NOTE 1: The guide that you will find at this link is somewhat outdated. In particular, if you download the "RBL_nRF8001" library from the website and continue with instructions, you will get an error message. To avoid that, upload this library on Arduino by the Sketch voice of Arduino (Sketch → Include library → Manage library, then look for RBL_nRF8001 in the menù) lib1.png lib2.png NOTE 2: Since the usage of the BT channel is exclusive, it is important to remember that the BLE controller App has to be terminated before running other Apps that require BT usage.

Raspberry

Raspberry pi 3 is the notorious board which natively supports bluetooth 4.1. In order to make Raspberry acts like a bluetooth device the SAndroidE developer has created a nodejs script, which exploits bleno and onoff library to handle respectively bluetooth (exposing services) and GPIOs. The image of the already set up Raspberry can be found at this URL. For sake of semplicity in the remainder of the this document the nodejs script developed will be referred as 'firmware'.


Enumerating resources

The resources available on each device will be enumerated; this this creates a sort of pointer to access the resources by the framework.This operation is achieved by means of the BLEEMbeddedFlasher App. This App basically creates a match between every BLE device connected with the smartphone and a name that will be used in the Android code.

Now start the BLEEmbeddedFlasher app to enumerate the device resources.

addressing-resources.jpg

In order to pair the Arduino/BLEShield with the smartphone, you first need to make it discoverable, and then following the instruction in the figures below, to connect with it and giving it a unique name. In this case Arduino is discoverable as soon as it is powered up. The same goes for Raspi.

NOTE: If the devices is supported/described as previously described the BLE services and attributes will be shown after the connection with the remote device. It is very important to wait for the services and attributes to be shown before saving the resources.

In this example the device nickname is BLE Shield , whereas the name of the specific device is set as arduino.

NOTE: The nickname does not define the kind of the device, only the name in GAP characteristics does (this name is automatically checked by the SAndroidE library).

enumerating-arduino.jpg

The two Raspberry pi 3 boards nickname is Raspi_GPIOCommander, wheras the specific devices are named respectively raspA and raspB.

NOTE: As above, the nickname does not define the kind of the device, only the name in GAP characteristics does (this name is automatically checked by the SAndroidE library).

enumerating-raspA.jpg

The configuration procedure aims to populate the bleresources.xml with the resources; the BLEEmbeddedFlasher App automatically adds them to the file. This generated file, together with previously mentioned [Configuration Files](05-Configuration files), are necessary by the App for the next steps. In case the file already exists, because of a previous configuration procedure, such procedure is not needed.


Writing the app

App's layout

The application layout is created by editing the file res/layout/activity_main.xml.

arduino-rasp-layout.jpg

The simple layout shown in figure above may be obtained by switching from Design to Text tab in Android Studio IDE and entering the following XML code:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.my.sarduinorasp.MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="LED-Arduino"
        android:id="@+id/tvArduinoLed"
        android:textColor="@android:color/holo_red_dark"
        android:textSize="36sp"
        android:layout_above="@+id/tvArduinoButton"
        android:layout_alignParentStart="true"
        android:layout_alignEnd="@+id/tvArduinoButton" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button-Arduino"
        android:id="@+id/tvArduinoButton"
        android:textColor="@android:color/holo_red_dark"
        android:textSize="36sp"
        android:layout_above="@+id/tvRaspALed"
        android:layout_alignParentStart="true"
        android:layout_alignEnd="@+id/tvRaspALed" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="LED-RaspberryA"
        android:id="@+id/tvRaspALed"
        android:textColor="@android:color/holo_red_dark"
        android:textSize="36sp"
        android:layout_above="@+id/tvRaspAButton"
        android:layout_alignParentStart="true"
        android:layout_alignParentEnd="true" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button-RaspberryA"
        android:id="@+id/tvRaspAButton"
        android:textColor="@android:color/holo_red_dark"
        android:textSize="36sp"
        android:layout_above="@+id/tvRaspBLed"
        android:layout_alignParentEnd="true"
        android:layout_alignParentStart="true" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="LED-RaspberryB"
        android:id="@+id/tvRaspBLed"
        android:textColor="@android:color/holo_red_dark"
        android:textSize="36sp"
        android:layout_above="@+id/tvRaspBButton"
        android:layout_alignParentStart="true"
        android:layout_alignEnd="@+id/tvRaspAButton" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button-RaspberryB"
        android:id="@+id/tvRaspBButton"
        android:textColor="@android:color/holo_red_dark"
        android:textSize="36sp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentEnd="true" />

</RelativeLayout>

App's logic

The application logic is implemented in the MainActivity.java file.

Both the virtual objects called by the SAndroidE library, which handle the remote resources, and the textviews shown on the App's User Interface are declared as global variables:

TextView tvArduinoButton, tvArduinoLed, tvRaspALed, tvRaspAButton, tvRaspBLed, tvRaspBButton;
BLEGeneralIO arduinoButton, arduinoLed, raspALed, raspAButton, raspBLed, raspBButton;

Then in the activity's onCreate method the SAndroidE library is initializated by the means of the BLEContext.init method. It is mandatory to insert this method before any other operations related to the SAndrodE library.

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);

	BLEContext.initBLE(this);
	...

Link and initialize both remote resources and textviews:

tvArduinoButton =(TextView) findViewById(R.id.tvArduinoButton);
tvArduinoLed    =(TextView) findViewById(R.id.tvArduinoLed);
tvRaspALed      =(TextView) findViewById(R.id.tvRaspALed);
tvRaspAButton   =(TextView) findViewById(R.id.tvRaspAButton);
tvRaspBLed      =(TextView) findViewById(R.id.tvRaspBLed);
tvRaspBButton   =(TextView) findViewById(R.id.tvRaspBButton);

arduinoLed = (BLEGeneralIO) BLEContext.findViewById("arduino_rbs_general_io_5");
arduinoButton = (BLEGeneralIO) BLEContext.findViewById("arduino_rbs_general_io_2");

raspALed = (BLEGeneralIO) BLEContext.findViewById("raspA_raspi_general_io_5");
raspAButton = (BLEGeneralIO) BLEContext.findViewById("raspA_raspi_general_io_2");

raspBLed = (BLEGeneralIO) BLEContext.findViewById("raspB_raspi_general_io_5");
raspBButton = (BLEGeneralIO) BLEContext.findViewById("raspB_raspi_general_io_2");

NOTE: the String parameters passed to the methods, which initialize the SAndrodE Objects are the 'pointer' defined in the configuration procedure. This 'pointer' can be found in the bleresources.xml file. In the current release of the library (version 0.1) this file is stored in the private memory of the 'BLEEmbeddedFlasher' application, thus in order to work correctly the library needs the application to be installed. In the next releases this limitation will be eliminated

Finally for each remote resource define the status of the GPIO (as input or output) and its behaviour, overriding the BLEOnGeneralIOEventListener interface methods. When a button is clicked, in a ring-like system, the led on the next board is lighted on and the related textViews on the app's UI changes color.

Below the full MainActivity.java source code is reported: it is sufficient for you to copy it in Android Studio and save it!

NOTE: At the line 'package com.example.my.sarduino;', you will have to insert your username, instead of 'my'.

package com.example.my.sarduinorasp;

import android.graphics.Color;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

import eu.angel.bleembedded.lib.BLEContext;
import eu.angel.bleembedded.lib.item.generalIO.BLEGeneralIO;
import eu.angel.bleembedded.lib.item.generalIO.BLEGeneralIOEvent;
import eu.angel.bleembedded.lib.item.generalIO.BLEOnGeneralIOEventListener;

public class MainActivity extends AppCompatActivity {

    protected static final String TAG = "MainActivity";

    TextView tvArduinoButton,tvArduinoLed,tvRaspALed,tvRaspAButton,tvRaspBLed,tvRaspBButton;
    BLEGeneralIO arduinoButton,arduinoLed,raspALed,raspAButton,raspBLed,raspBButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        BLEContext.initBLE(this);

        tvArduinoButton =(TextView) findViewById(R.id.tvArduinoButton);
        tvArduinoLed    =(TextView) findViewById(R.id.tvArduinoLed);
        tvRaspALed      =(TextView) findViewById(R.id.tvRaspALed);
        tvRaspAButton   =(TextView) findViewById(R.id.tvRaspAButton);
        tvRaspBLed      =(TextView) findViewById(R.id.tvRaspBLed);
        tvRaspBButton   =(TextView) findViewById(R.id.tvRaspBButton);

        arduinoLed = (BLEGeneralIO) BLEContext.findViewById("arduino_rbs_general_io_5");
        arduinoButton = (BLEGeneralIO) BLEContext.findViewById("arduino_rbs_general_io_2");

        raspALed = (BLEGeneralIO) BLEContext.findViewById("raspA_raspi_general_io_5");
        raspAButton = (BLEGeneralIO) BLEContext.findViewById("raspA_raspi_general_io_2");

        raspBLed = (BLEGeneralIO) BLEContext.findViewById("raspB_raspi_general_io_5");
        raspBButton = (BLEGeneralIO) BLEContext.findViewById("raspB_raspi_general_io_2");

        arduinoLed.setOnGeneralIOEventListener(new BLEOnGeneralIOEventListener() {
            @Override
            public void onBoardInitEnded() {
                arduinoLed.setStatus(BLEGeneralIO.GENERAL_IO_DO);
            }

            @Override
            public void onDigitalInputValueChanged(BLEGeneralIOEvent bleGeneralIOEvent) {

            }

            @Override
            public void onAnalogValueChanged(BLEGeneralIOEvent bleGeneralIOEvent) {
            }

            @Override
            public void onDigitalOutputValueChanged(final BLEGeneralIOEvent bleGeneralIOEvent) {
                (MainActivity.this).runOnUiThread(new Runnable() {
                                                      @Override
                                                      public void run() {
                          tvArduinoLed.setTextColor(bleGeneralIOEvent.values[1]==1?Color.BLUE:Color.RED);
                      }
                  }
                );
            }

            @Override
            public void onServoValueChanged(BLEGeneralIOEvent bleGeneralIOEvent) {

            }

            @Override
            public void onPWMValueChanged(BLEGeneralIOEvent bleGeneralIOEvent) {

            }

            @Override
            public void onGeneralIOStatusChanged(BLEGeneralIOEvent bleGeneralIOEvent) {

            }

            @Override
            public void onSetGeneralIOParameter(BLEGeneralIOEvent bleGeneralIOEvent) {

            }
        });

        arduinoButton.setOnGeneralIOEventListener(new BLEOnGeneralIOEventListener() {
            @Override
            public void onBoardInitEnded() {
                arduinoButton.setStatus(BLEGeneralIO.GENERAL_IO_DI);
            }

            @Override
            public void onDigitalInputValueChanged(BLEGeneralIOEvent bleGeneralIOEvent) {
                Log.d(TAG, "arduino button pressing: "+ bleGeneralIOEvent.values[1]);
                if(bleGeneralIOEvent.values[1]==1)
                {
                    Log.d(TAG, "arduino led: setting HIGH");
                    raspALed.setDigitalValueHigh(true);
                    (MainActivity.this).runOnUiThread(new Runnable() {
                          @Override
                          public void run() {
                              tvArduinoButton.setTextColor(Color.BLUE);
                          }
                      }
                    );
                } else {
                    Log.d(TAG, "arduino led: setting LOW");
                    raspALed.setDigitalValueHigh(false);
                    (MainActivity.this).runOnUiThread(new Runnable() {
                          @Override
                          public void run() {
                              tvArduinoButton.setTextColor(Color.RED);
                          }
                      }
                    );
                }
            }

            @Override
            public void onAnalogValueChanged(BLEGeneralIOEvent bleGeneralIOEvent) {

            }

            @Override
            public void onDigitalOutputValueChanged(BLEGeneralIOEvent bleGeneralIOEvent) {

            }

            @Override
            public void onServoValueChanged(BLEGeneralIOEvent bleGeneralIOEvent) {

            }

            @Override
            public void onPWMValueChanged(BLEGeneralIOEvent bleGeneralIOEvent) {

            }

            @Override
            public void onGeneralIOStatusChanged(BLEGeneralIOEvent bleGeneralIOEvent) {

            }

            @Override
            public void onSetGeneralIOParameter(BLEGeneralIOEvent bleGeneralIOEvent) {

            }
        });


        raspALed.setOnGeneralIOEventListener(new BLEOnGeneralIOEventListener() {
            @Override
            public void onBoardInitEnded() {
                raspALed.setStatus(BLEGeneralIO.GENERAL_IO_DO);
            }

            @Override
            public void onDigitalInputValueChanged(BLEGeneralIOEvent bleGeneralIOEvent) {

            }

            @Override
            public void onAnalogValueChanged(BLEGeneralIOEvent bleGeneralIOEvent) {
            }

            @Override
            public void onDigitalOutputValueChanged(final BLEGeneralIOEvent bleGeneralIOEvent) {
                (MainActivity.this).runOnUiThread(new Runnable() {
                                                      @Override
                                                      public void run() {
                                                          tvRaspALed.setTextColor(bleGeneralIOEvent.values[1]==1?Color.BLUE:Color.RED);
                                                      }
                                                  }
                );
            }

            @Override
            public void onServoValueChanged(BLEGeneralIOEvent bleGeneralIOEvent) {

            }

            @Override
            public void onPWMValueChanged(BLEGeneralIOEvent bleGeneralIOEvent) {

            }

            @Override
            public void onGeneralIOStatusChanged(BLEGeneralIOEvent bleGeneralIOEvent) {

            }

            @Override
            public void onSetGeneralIOParameter(BLEGeneralIOEvent bleGeneralIOEvent) {

            }
        });

        raspAButton.setOnGeneralIOEventListener(new BLEOnGeneralIOEventListener() {
            @Override
            public void onBoardInitEnded() {
                raspAButton.setStatus(BLEGeneralIO.GENERAL_IO_DI);
            }

            @Override
            public void onDigitalInputValueChanged(BLEGeneralIOEvent bleGeneralIOEvent) {
                Log.d(TAG, "raspA button pressing: "+ bleGeneralIOEvent.values[1]);
                if(bleGeneralIOEvent.values[1]==0)
                {
                    Log.d(TAG, "raspA led: setting HIGH");
                    raspBLed.setDigitalValueHigh(true);
                    (MainActivity.this).runOnUiThread(new Runnable() {
                                                          @Override
                                                          public void run() {
                                                              tvRaspAButton.setTextColor(Color.BLUE);
                                                          }
                                                      }
                    );
                } else {
                    Log.d(TAG, "raspA led: setting LOW");
                    raspBLed.setDigitalValueHigh(false);
                    (MainActivity.this).runOnUiThread(new Runnable() {
                                                          @Override
                                                          public void run() {
                                                              tvRaspAButton.setTextColor(Color.RED);
                                                          }
                                                      }
                    );
                }
            }

            @Override
            public void onAnalogValueChanged(BLEGeneralIOEvent bleGeneralIOEvent) {

            }

            @Override
            public void onDigitalOutputValueChanged(BLEGeneralIOEvent bleGeneralIOEvent) {

            }

            @Override
            public void onServoValueChanged(BLEGeneralIOEvent bleGeneralIOEvent) {

            }

            @Override
            public void onPWMValueChanged(BLEGeneralIOEvent bleGeneralIOEvent) {

            }

            @Override
            public void onGeneralIOStatusChanged(BLEGeneralIOEvent bleGeneralIOEvent) {

            }

            @Override
            public void onSetGeneralIOParameter(BLEGeneralIOEvent bleGeneralIOEvent) {

            }
        });


        raspBLed.setOnGeneralIOEventListener(new BLEOnGeneralIOEventListener() {
            @Override
            public void onBoardInitEnded() {
                raspBLed.setStatus(BLEGeneralIO.GENERAL_IO_DO);
            }

            @Override
            public void onDigitalInputValueChanged(BLEGeneralIOEvent bleGeneralIOEvent) {

            }

            @Override
            public void onAnalogValueChanged(BLEGeneralIOEvent bleGeneralIOEvent) {
            }

            @Override
            public void onDigitalOutputValueChanged(final BLEGeneralIOEvent bleGeneralIOEvent) {
                (MainActivity.this).runOnUiThread(new Runnable() {
                                                      @Override
                                                      public void run() {
                                                          tvRaspBLed.setTextColor(bleGeneralIOEvent.values[1]==1?Color.BLUE:Color.RED);
                                                      }
                                                  }
                );
            }

            @Override
            public void onServoValueChanged(BLEGeneralIOEvent bleGeneralIOEvent) {

            }

            @Override
            public void onPWMValueChanged(BLEGeneralIOEvent bleGeneralIOEvent) {

            }

            @Override
            public void onGeneralIOStatusChanged(BLEGeneralIOEvent bleGeneralIOEvent) {

            }

            @Override
            public void onSetGeneralIOParameter(BLEGeneralIOEvent bleGeneralIOEvent) {

            }
        });

        raspBButton.setOnGeneralIOEventListener(new BLEOnGeneralIOEventListener() {
            @Override
            public void onBoardInitEnded() {
                raspBButton.setStatus(BLEGeneralIO.GENERAL_IO_DI);
            }

            @Override
            public void onDigitalInputValueChanged(BLEGeneralIOEvent bleGeneralIOEvent) {
                Log.d(TAG, "raspB button pressing: "+ bleGeneralIOEvent.values[1]);
                if(bleGeneralIOEvent.values[1]==0)
                {
                    Log.d(TAG, "raspB led: setting HIGH");
                    arduinoLed.setDigitalValueHigh(true);
                    (MainActivity.this).runOnUiThread(new Runnable() {
                                                          @Override
                                                          public void run() {
                                                              tvRaspBButton.setTextColor(Color.BLUE);
                                                          }
                                                      }
                    );
                } else {
                    Log.d(TAG, "raspB led: setting LOW");
                    arduinoLed.setDigitalValueHigh(false);
                    (MainActivity.this).runOnUiThread(new Runnable() {
                                                          @Override
                                                          public void run() {
                                                              tvRaspBButton.setTextColor(Color.RED);
                                                          }
                                                      }
                    );
                }
            }

            @Override
            public void onAnalogValueChanged(BLEGeneralIOEvent bleGeneralIOEvent) {

            }

            @Override
            public void onDigitalOutputValueChanged(BLEGeneralIOEvent bleGeneralIOEvent) {

            }

            @Override
            public void onServoValueChanged(BLEGeneralIOEvent bleGeneralIOEvent) {

            }

            @Override
            public void onPWMValueChanged(BLEGeneralIOEvent bleGeneralIOEvent) {

            }

            @Override
            public void onGeneralIOStatusChanged(BLEGeneralIOEvent bleGeneralIOEvent) {

            }

            @Override
            public void onSetGeneralIOParameter(BLEGeneralIOEvent bleGeneralIOEvent) {

            }
        });

    }
}

NOTE: As you can see from the source code above, in the onDigitalInputValueChanged method for Raspberry buttons, the state of the button is tested against the value zero (which means HIGH value in our Raspberry configuration), as opposed to the code implemented for Arduino button, where the button state is tested against 1 (which is the HIGH value in our Arduino configuration). This behaviour is related to the circuit design of the button stage, because the Raspberry GPIO used is pulled up by default on input state (Arduino GPIO input is always in high impedance configuration). Changing the Raspberry GPIO input configuration (and obviously the circuit design properly) it's possible force the same behaviour for all the buttons.


Let's run it

Finally run the application connecting the smartphone to the PC with the USB micro cable. Clicking Run button in Android Studio and selecting the connected smartphone the application will be deployed and run on it.

NOTE: If you are not familiar with this procedure, you can find everything you need to know at this link.

In this example, pressing the button attached to to Arduino will light on the led attached to RaspA. Pressing the button attached to RaspA will light on the led attached to RaspB and finally pressing the button attached to RaspB will light on the led attached to Arduino. The textViews on app's UI change color accordingly with the behaviour of the related button/led.

arduino-rasp-press1.jpg arduino-rasp-press2.jpg arduino-rasp-press3.jpg

NOTE: As you can see from the picture, the textviews for Raspberry leds within the app's interface seem apparently to not work correctly. This is the expected behaviour, because the Raspberry 'firmware' used for this example, does not send any update on the state of the digital output, thus the related callback onDigitalOutputValueChanged is never triggered (this is a good example on how different 'firmwares' can act on the library items in different ways). NOTE: If devices do not connect at startup, please shutdown all Bluetooth applications already running, like the BleembeddedFlasher app or previously launched versions of our app.

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.