Bluetooth BLE 0.2.1 Library + bluetooth.html + arduino_R4 = Bluetooth from Browser to Arduino R4 !

Dear Beloved Arduino UNO R4 WiFi brothers and sisters:

Many questions here about how to make Bluetooth LE work on the Arduino UNO R4. Here I am sharing some sample code which has worked for me, for your kind copy/paste and evaluation:

I managed to modify some interesting bits of code in HTML to be run from the server (works only in Google Browser, as I cannot get Bluetooth activated in Firefox) via JavaScript - and then I used the LedControl software from the sample code brought by the Bluetooth BLE 0.2.1 library (thanks, @ptillish !) - and I got the following:

bluetooth.html:

<!DOCTYPE html>
<html>
<head>
    <title>Bluetooth BLE Device Communication</title>
</head>
<body>
    <button id="connectButton">Connect to Bluetooth Device</button>



    <script>
        if ('bluetooth' in navigator) {
/*
Error:TypeError: Failed to execute 'getPrimaryService' on 'BluetoothRemoteGATTServer': 
Invalid Service name: '19B10000-E8F2-537E-4F6C-D104768A1214'. 
It must be a valid UUID alias (e.g. 0x1234), 
UUID (lowercase hex characters e.g. '00001234-0000-1000-8000-00805f9b34fb'), or 
recognized standard name from https://www.bluetooth.com/specifications/gatt/services e.g. 
'alert_notification'.
*/

/*
Error:SecurityError: 
Origin is not allowed to access any service. 
Tip: Add the service UUID to 'optionalServices' in requestDevice() options. https://goo.gl/HxfxSQ
*/

            const bluetooth = navigator.bluetooth;

            // Function to connect to the device
            async function connectToDevice() {
                rv = "Initiating Bluetooth ";
                alert("Initiating Bluetooth");
                try {
                    const device = await bluetooth.requestDevice({
                        filters: [{ name: 'LEDCallback' }],
                        optionalServices: ['19b10000-e8f2-537e-4f6c-d104768a1214'] // Add your service UUID here

                    });
                    rv += "A";
                    const server = await device.gatt.connect();
                    rv += "B"; 

                    // Replace these UUIDs with your Arduino's service and characteristic UUIDs
                     const service = await server.getPrimaryService('19b10000-e8f2-537e-4f6c-d104768a1214');
                    rv += "C";
                     const characteristic = await service.getCharacteristic('19b10001-e8f2-537e-4f6c-d104768a1214');
                    rv += "D";

                    singleshot = true; 
                    if (singleshot) {
                        // Read the value of the characteristic
                        //characteristic.writeValue("Browser");
                        const value = await characteristic.readValue();
                        rv += "E";
                        const decodedValue = new TextDecoder().decode(value);
                        rv += "F";
                        document.body.innerHTML = '<home><body><h1>Fetched Value: ' + rv + ' '+ decodedValue + '</h1></body></home>';
                    } else {
                        // Enable notifications for the characteristic
                        await characteristic.startNotifications();
                        rv += "J";
    
                        // Set up a callback to handle incoming notifications
                        characteristic.addEventListener('characteristicvaluechanged', handleNotifications);
                        rv += "K";
    
                        document.getElementById('connectButton').disabled = true;
                    }

                } catch (error) {
                    alert ('Error: ' + rv + ' ' + error);
                }
            }
            
            function handleNotifications(event) {
                const value = event.target.value;
                const decodedValue = new TextDecoder().decode(value);

                console.log('Received Value:', decodedValue);

                // You can update your HTML to display the received value
                document.getElementById('receivedValue').textContent = decodedValue;
            }            

            // Attach the connectToDevice function to the button click event
            const connectButton = document.getElementById('connectButton');
            connectButton.addEventListener('click', connectToDevice);
        } else {
            console.log('Bluetooth is not supported by this browser.');
        }
    </script>
</body>
</html>

bluetooth_arduino.ino:

#include <ArduinoBLE.h>

BLEService ledService("19b10000-e8f2-537e-4f6c-d104768a1214");
BLECharacteristic switchCharacteristic("19b10001-e8f2-537e-4f6c-d104768a1214", BLERead | BLEWrite | BLENotify | BLEBroadcast, 20);

const int ledPin = LED_BUILTIN;
const char* stringArray[4] = {
    "YapplyHoo",
    "Superposition",
    "QuantumLeap",
    "Einsteinium"
};

void setup() {
  Serial.begin(9600);
  while (!Serial);

  pinMode(ledPin, OUTPUT);

  if (!BLE.begin()) {
    Serial.println("Starting Bluetooth® Low Energy module failed!");
    while (1);
  }

  BLE.setLocalName("LEDCallback");
  BLE.setAdvertisedService(ledService);
  ledService.addCharacteristic(switchCharacteristic);
  BLE.addService(ledService);

  BLE.setEventHandler(BLEConnected, blePeripheralConnectHandler);
  BLE.setEventHandler(BLEDisconnected, blePeripheralDisconnectHandler);

  switchCharacteristic.setEventHandler(BLEWritten, switchCharacteristicWritten);
  switchCharacteristic.writeValue("Heureka");

  BLE.advertise();

  Serial.println("Bluetooth® device active, waiting for connections...");
}

int counter=0;

void loop() {
  BLE.poll();
  delay(1000);
  Serial.print("."); // Timer tick
}

void blePeripheralConnectHandler(BLEDevice central) {
  Serial.print("Connected event, central: ");
  Serial.println(central.address());
}

void blePeripheralDisconnectHandler(BLEDevice central) {
  Serial.print("Disconnected event, central: ");
  Serial.println(central.address());
}

void switchCharacteristicWritten(BLEDevice central, BLECharacteristic characteristic) {
  Serial.print("Characteristic event, written: ");

  uint8_t characteristicValue[20];
  int bytesRead = characteristic.readValue(characteristicValue, sizeof(characteristicValue));

  Serial.print("Received bytes: ");
  for (int i = 0; i < bytesRead; i++) {
    Serial.print(characteristicValue[i], HEX);
    Serial.print(" ");
  }
  Serial.println();

  String receivedString = "";

  for (int i = 0; i < bytesRead; i++) {
    receivedString += (char)characteristicValue[i];
  }

  Serial.println("Value: " + receivedString);
  characteristic.writeValue(stringArray[random(0, 4)]); // set initial value for this characteristic 
}
1 Like

Now, here is how it works:

You run the Arduino script, and it sets up a listening post for Bluetooth and advertises LEDControl as its bluetooth name.

Then you run the bluetooth.html from your server in the Google Browser, with your Bluetooth enabled. I have run it both on my laptop (an ASUS 555), and on my phone (Android v. 10), and I manage to connect to the Arduino Uno R4 WiFi's Bluetooth - you can ascertain that the two - the JavaScript in the browser and the Arduino are talking. They are sending strings forth and back, which I think is crucial for anything to really be of value. Sending single bytes is not really of any value.

Please let me hear what you think about this. I am sharing this code, such that hopefully those of you who are beginners like myself, can get started.

I am also sharing it, because I thought that connecting from a browser via the standard connection interface on phones or laptops would be factors better than having to write an App for Android and iPhone and even a piece of stand-alone software for Windows and yet another one for Linux.

Please let me know, anyone, if you know how to activate Bluetooth under Firefox, or other browsers. I have tried quite some things but in vain.

With love to my Arduinosic Brothers and Sisters

Thanks for sharing @den1willemoes!

I also think this is a promising approach to creating a project-specific interface for communication between a PC or mobile device and an Arduino board-based device.

By "Bluetooth BLE 0.2.1 library" I guess you are referring to the latest version of the "bridge" firmware for the UNO R4 WiFi, which provides the BLE capability not available from the board's original firmware, as announced here:

Since your sketch uses the "ArduinoBLE" library, I should also mention that support for the UNO R4 WiFi is currently only available when using the beta tester version of that library. Instructions for installing that version in Arduino IDE are provided here:

and for installing it in Arduino Cloud here:

Thanks for all the clarifications, @ptillisch, I am actually not asking anything, because you gave me that good library as you have indicated there, and that is the one I am using. And your original installation guide worked too.

So, consider my little contribution as a "giving back to the Arduino community". My code is INDEED not perfect, as it can be seen, but it can maybe open a door for newcomers like myself to get started.

I have for instance a big problem in establishing a continuous communication between the Java Script in the HTML file and the Arduino. I have tried many things, but likely my understanding of certain profound technical parts is missing.

If I touch the above code, the slightest, then I get errors. It is as if the code above is sensitive like an egg. I mean - I have tried to put loops inside, then it stalls (and I get all sorts of connection errors etc). I am not really sure that my code covers the reality of the JavaScript world, nor am I sure that I have understood what it takes to establish a "connection" if such a thing exists between the two BLE devices (central and peripheral), so that I can send texts both ways in a continuous stream until the work has been done.

So some help in establishing a continuous contact between the Javascript code and the Arduino UNO R4 WiFi code would be very wonderful!

I understand you aren't asking anything here. The mention of "asking" in my previous reply was from an excerpt the forum automatically adds when you link to a forum post. This excerpt is from our earlier conversation in the other topic. If you look closely, you can see the forum has a special visual formatting for these excerpts (similar to the formatting of quotes).

Dear Ptillisch,

I just wanted to provide a heads up. The Bluetooth BLE connection from the website to the Arduino was working and we are able to send data forth and back. Where would I be putting an example of this for all the other Arduinoer's ?

Sincerely

I expanded this sample a bit since it was something I found and there were a couple of parts that weren't clear.

BLE_Example.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Bluetooth® Low Energy Example</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 10px;
            font-size: 12px;
        }

        button, input[type="text"], select {
            font-size: 12px;
            padding: 5px;
        }

        button {
            height: 30px;
            align-items: center;
        }

        input[type="text"] {
            margin: 0 5px;
            width: 100%;
        }

        p {
            font-size: 16px;
        }

        .container, .title {
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .title {
            font-size: 24px;
            justify-content: center;
        }

        textarea {
            width: 100%;
            height: 200px;
            display: inline-block;
            font-family: monospace;
            white-space: pre;
        }
    </style>
</head>
<body>
    <div class="title">Bluetooth® Low Energy HTML Example</div>
    <div class="container">
        <button id="connectButton">Connect</button>
        <input type="text" id="textToSend" placeholder="Message | Press Enter to Send" disabled>
    </div>
    <div class="container">
        <p>Logger</p>
        <div class="log-buttons">
            <button id="copyButton">&#x1F4CB;</button>
            <button id="clearButton">&#x274C;</button>
            <button id="scrollButton">Scroll</button>
            <button id="togglePollingButton">Disable Polling</button>
        </div>
    </div>
    <div class="container">
        <textarea id="logBox" readonly></textarea>
    </div>
    
    <script>
        const connectButton = document.getElementById('connectButton');
        const textToSend = document.getElementById('textToSend');
        const logBox = document.getElementById('logBox');
        const clearButton = document.getElementById('clearButton');
        const copyButton = document.getElementById('copyButton');
        const scrollButton = document.getElementById('scrollButton');
        const togglePollingButton = document.getElementById('togglePollingButton');

        let bleDevice;
        let dataCharacteristic;
        let pollCharacteristic;
        let autoScroll = true;
        let recordPolling = true;

        const timestampWidth = '[00:00:00.000] '.length;

        connectButton.addEventListener('click', async () => {
            log('Attempting to connect...');
            try {
                bleDevice = await navigator.bluetooth.requestDevice({
                    filters: [{ services: ['abcdef12-0000-0000-0000-000000000000'] }]
                });
                const server = await bleDevice.gatt.connect();
                const service = await server.getPrimaryService('abcdef12-0000-0000-0000-000000000000');
                dataCharacteristic = await service.getCharacteristic('abcdef12-0000-0000-0000-000000000001');
                dataCharacteristic.addEventListener('characteristicvaluechanged', handleCharacteristicValueChanged);
                pollCharacteristic = await service.getCharacteristic('abcdef12-0000-0000-0000-000000000002');
                pollCharacteristic.addEventListener('characteristicvaluechanged', handlePollValueChanged);
                await dataCharacteristic.startNotifications();
                await pollCharacteristic.startNotifications();
                log('Connected to device');
                connectButton.textContent = 'Connected';
                connectButton.disabled = true;
                textToSend.disabled = false;
            } catch (error) {
                console.error('Error:', error);
                log(`Error connecting: ${error}`);
            }
        });

        textToSend.addEventListener('keydown', (event) => {
            if (event.key === 'Enter') {
                event.preventDefault();
                sendData();
            }
        });

        function sendData() {
            if (!dataCharacteristic) {
                alert('Not connected to a device.');
                return;
            }

            let sentText = textToSend.value;
            if (!sentText) {
                alert('Enter text to send.');
                return;
            }

            const value = new TextEncoder().encode(sentText);
            log(`Sent Data: ${sentText}`);
            textToSend.value = '';
            try {
                dataCharacteristic.writeValue(value);
            } catch (error) {
                console.error('Error:', error);
                log(`Error sending data: ${error}`);
            }
        }

        clearButton.addEventListener('click', () => {
            logBox.value = '';
        });

        copyButton.addEventListener('click', () => {
            logBox.select();
            document.execCommand('copy');
            copyButton.textContent = 'Copied!';
            setTimeout(() => {
                copyButton.innerHTML = '&#x1F4CB;';
            }, 2000);
        });

        scrollButton.addEventListener('click', () => {
            autoScroll = !autoScroll;
            scrollButton.textContent = autoScroll ? 'Freeze' : 'Scroll';
        });

        togglePollingButton.addEventListener('click', () => {
            recordPolling = !recordPolling;
            togglePollingButton.textContent = recordPolling ? 'Disable Polling' : 'Enable Polling';
        });

        function handleCharacteristicValueChanged(event) {
            const value = new TextDecoder().decode(event.target.value);
            log(`Received Data: ${value}`);
        }

        function handlePollValueChanged(event) {
            if (!recordPolling) return;

            const value = new DataView(event.target.value.buffer).getUint32(0, true);
            log(`Received Poll Data: ${value}`);
        }

        function log(message) {
            const now = new Date();
            const timestamp = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}.${now.getMilliseconds().toString().padStart(3, '0')}`;
            const paddedMessage = message.split('\n').map((line, index) => {
                return index === 0 ? line : ' '.repeat(timestampWidth) + line;
            }).join('\n');
            logBox.value += `[${timestamp}] ${paddedMessage}\n`;
            if (autoScroll) {
                logBox.scrollTop = logBox.scrollHeight; // Auto-scroll to the bottom
            }
        }
    </script>
</body>
</html>

BLE_Example.ino

#include <ArduinoBLE.h>

// Create a BLE service with a unique identifier
BLEService dataService("abcdef12-0000-0000-0000-000000000000");

BLECharacteristic dataCharacteristic("abcdef12-0000-0000-0000-000000000001", BLERead | BLEWrite | BLENotify | BLEBroadcast, 500);
BLECharacteristic pollCharacteristic("abcdef12-0000-0000-0000-000000000002", BLERead | BLENotify | BLEBroadcast, 4);

const char* dataArray[4] = { // Array of strings to send back as characteristic values
    "DataOne\n1",
    "DataTwo\n2",
    "DataThree\n3",
    "DataFour\n4"
};

uint32_t counter = 0;

void setup() {
  Serial.begin(9600); 
  while (!Serial); 

  if (!BLE.begin()) { // Initialize the BLE hardware
    Serial.println("Error: Starting Bluetooth® Low Energy module failed!");
    while (1); 
  }

  BLE.setLocalName("Arduino BLE");
  BLE.setDeviceName("Arduino");
  
  BLE.setAdvertisedService(dataService); 
  dataService.addCharacteristic(dataCharacteristic); 
  dataService.addCharacteristic(pollCharacteristic); 
  BLE.addService(dataService); // Add the service to the BLE stack

  BLE.setEventHandler(BLEConnected, blePeripheralConnectHandler); // Set event handler for device connection
  BLE.setEventHandler(BLEDisconnected, blePeripheralDisconnectHandler); // Set event handler for device disconnection

  dataCharacteristic.setEventHandler(BLEWritten, dataCharacteristicWritten); // Set event handler for characteristic write
  
  if (!pollCharacteristic.writeValue(counter)) { // Initial write to the characteristic
    Serial.println("Error: Initial write to characteristic failed!");
  } else {
    Serial.println("Initial characteristic value written: Hello World!");
  }

  BLE.advertise();
  Serial.println("Bluetooth® device active, waiting for connections...");
}

void loop() {
  BLE.poll(); // Poll the BLE stack
  counter += 1;
  pollCharacteristic.writeValue(counter);
  Serial.print("."); // Print a dot to indicate timer tick
  delay(1000); // Wait for 1 second
}  

void blePeripheralConnectHandler(BLEDevice central) {
  Serial.print("Connected event, central: ");
  Serial.println(central.address()); // Print the address of the connected central device
}

void blePeripheralDisconnectHandler(BLEDevice central) {
  Serial.print("Disconnected event, central: ");
  Serial.println(central.address()); // Print the address of the disconnected central device
}

void dataCharacteristicWritten(BLEDevice central, BLECharacteristic characteristic) {
  Serial.print("Characteristic event, written by central: ");
  Serial.println(central.address());

  uint8_t characteristicValue[500]; // Buffer to store the characteristic value
  int bytesRead = characteristic.readValue(characteristicValue, sizeof(characteristicValue)); // Read the value from the characteristic

  Serial.print("Received bytes: ");
  for (int i = 0; i < bytesRead; i++) {
    Serial.print(characteristicValue[i], HEX); // Print each byte in hexadecimal format
    Serial.print(" ");
  }
  Serial.println();

  String receivedString = ""; // Initialize an empty string to store the received value
  for (int i = 0; i < bytesRead; i++) {
    receivedString += (char)characteristicValue[i]; // Convert each byte to a character and append to the string
  }

  Serial.println("Received value: " + receivedString); // Print the received string

  // Write a random string from the array to the characteristic
  String responseValue = dataArray[random(0, 4)];
  if (!characteristic.writeValue(responseValue.c_str())) {
    Serial.println("Error: Writing response value to characteristic failed!");
  } else {
    Serial.println("Response value written: " + responseValue);
  }
}