Issues Switching Between BLE and WiFi on Arduino Nano 33 IoT for Home Automation Project

Hello everyone,

I'm working on a home automation project using an Arduino Nano 33 IoT and an Arduino Nano 33 BLE. The goal is to control a relay connected to the Nano BLE from the Nano IoT via BLE, and communicate with Home Assistant via WiFi using MQTT.

Why, you might ask:

Three reasons actually:

  • I am running Home assistant on raspberry pi, home assistant prefers the MQTT and MQTT only works on WiFi
  • I am planning to have another Arduino even further away, so this Nano IOT would be used in a way as a range extendor.
  • I have multiple Nano BLE boards I want to make use of.

Current Setup:

  • Arduino Nano 33 IoT:
    • Connects to Home Assistant via Wi-Fi using MQTT.
    • Switches to BLE to control the relay on the Nano BLE.
    • Switches back to Wi-Fi after controlling the relay or checking its status.
  • Arduino Nano 33 BLE:
    • Controls a relay.
    • Advertises a BLE service with a characteristic to control the relay.

The Issue:

Considering Nano IOT cannot run both communications with the same time, I am forced to switch. The goal is to have the Nano IOT connected on the Wi-Fi most of the time, and only connect via BLE whenever it receives a command from Home Assistant or every 1-5 minutes to check if the state changed.

I’m facing difficulties with reliably switching between BLE and Wi-Fi on the Arduino Nano 33 IoT. Although the Wi-Fi connection and MQTT communication work fine, the BLE connection to the Nano BLE fails with messages indicating an inability to connect.

Each of the communications work perfectly fine on its own, so I am suspecting the issue lies in reusing the antenna (switching between BLE and Wi-Fi)

Observations:

  • BLE scanning finds the peripheral, but connection attempts always fail.
  • The connection sequence seems to be correctly followed, but the BLE connection is unstable.

Example output:

WiFi connected
IP address:
192.168.64.117
Attempting MQTT connection... to broker: 192.168.64.115 at port: 1883
connected
Switching to BLE
Found 40:33:47:7e:d3:03 'NanoBLERelay' 19b10010-e8f2-537e-4f6c-d104768a1214
Attempting to connect to peripheral...
Failed to connect to peripheral

Here you can find the code for NANO IOT:
#include <WiFiNINA.h>
#include <PubSubClient.h>
#include <ArduinoBLE.h>
#include "utility/wifi_drv.h"

// WiFi credentials
const char* ssid = "************";
const char* password = "***************";

// MQTT broker details
const char* mqtt_server = "192.168.64.115";
const int mqtt_port = 1883;
const char* mqtt_user = "homeassistant-mosquitto";
const char* mqtt_password = "************";

// BLE UUIDs
#define BLE_UUID_RELAY_SERVICE               "19B10010-E8F2-537E-4F6C-D104768A1214"
#define BLE_UUID_RELAY_CHARACTERISTIC        "19B10011-E8F2-537E-4F6C-D104768A1214"

WiFiClient wifiClient;
PubSubClient client(wifiClient);

bool wifiConnected = false;
bool bleConnected = false;
bool checkBLE = false;
bool controlBLE = false;
bool relayCommand = false;

unsigned long lastSwitchTime = 0;
const unsigned long switchInterval = 60000; // 1 minute interval

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

  pinMode(LED_BUILTIN, OUTPUT);
  Serial.println("Starting...");

  // Initialize WiFi
  setup_wifi();

  // Initialize MQTT
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(mqttCallback);

  // Ensure MQTT client is connected
  reconnect();
}

void loop() {
  unsigned long currentTime = millis();

  if (checkBLE || (currentTime - lastSwitchTime > switchInterval)) {
    lastSwitchTime = currentTime;
    switchToBLE();
    checkRelayStatusBLE();
    checkBLE = false;
    switchToWiFi();
  }

  // Poll MQTT messages
  if (wifiConnected) {
    if (!client.connected()) {
      reconnect();
    }
    client.loop();
  }

  // Control BLE if instructed by MQTT
  if (controlBLE) {
    switchToBLE();
    controlRelayBLE();
    controlBLE = false;
    switchToWiFi();
  }
}

void setup_wifi() {
  delay(10);
  // Connect to WiFi
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  unsigned long startAttemptTime = millis();

  // Wait for connection with timeout
  while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 30000) { // 30 seconds timeout
    delay(500);
    Serial.print(".");
  }

  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("");
    Serial.println("WiFi connected");
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());
    wifiConnected = true;
  } else {
    Serial.println("");
    Serial.println("Failed to connect to WiFi");
    wifiConnected = false;
  }
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    Serial.print(" to broker: ");
    Serial.print(mqtt_server);
    Serial.print(" at port: ");
    Serial.println(mqtt_port);

    // Attempt to connect
    if (client.connect("ArduinoNanoIoT", mqtt_user, mqtt_password)) {
      Serial.println("connected");
      // Subscribe to control topic
      client.subscribe("home/relay_control");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void mqttCallback(char* topic, byte* payload, unsigned int length) {
  String incoming = "";

  for (unsigned int i = 0; i < length; i++) {
    incoming += (char)payload[i];
  }

  incoming.trim();

  // Control relay based on MQTT message
  if (String(topic) == "home/relay_control") {
    if (incoming == "ON") {
      relayCommand = true;
      controlBLE = true;
    } else if (incoming == "OFF") {
      relayCommand = false;
      controlBLE = true;
    }
  }
}

void switchToBLE() {
  Serial.println("Switching to BLE");

  if (WiFi.status() == WL_CONNECTED) {
    WiFi.disconnect();
    WiFi.end();
    wifiConnected = false;
  }

  // Initialize BLE
  if (!BLE.begin()) {
    Serial.println("Starting BLE failed!");
    while (1);
  }

  // Set advertised service and add characteristics
  BLE.setDeviceName("Arduino Nano 33 IoT");
  BLE.setLocalName("Arduino Nano 33 IoT");
  BLE.setAdvertisedServiceUuid(BLE_UUID_RELAY_SERVICE);

  BLE.scanForUuid(BLE_UUID_RELAY_SERVICE);
  delay(5000); // Ensure BLE is ready and scanning
  bleConnected = true;
}

void switchToWiFi() {
  Serial.println("Switching to WiFi");

  if (BLE.connected()) {
    BLE.disconnect();
  }
  BLE.end();
  bleConnected = false;

  setup_wifi();

  if (wifiConnected) {
    reconnect();
  }
}

void controlRelayBLE() {
  BLEDevice peripheral = connectToPeripheral();

  if (peripheral) {
    BLECharacteristic relayCharacteristic = peripheral.characteristic(BLE_UUID_RELAY_CHARACTERISTIC);

    if (relayCharacteristic) {
      Serial.println("Relay characteristic found");
      if (relayCommand) {
        relayCharacteristic.writeValue((uint8_t)0x01);
        Serial.println("Relay turned ON via BLE");
      } else {
        relayCharacteristic.writeValue((uint8_t)0x00);
        Serial.println("Relay turned OFF via BLE");
      }
      delay(5000); // Ensure the command is executed
    }
    peripheral.disconnect();
  }
}

void checkRelayStatusBLE() {
  BLEDevice peripheral = connectToPeripheral();

  if (peripheral) {
    BLECharacteristic relayCharacteristic = peripheral.characteristic(BLE_UUID_RELAY_CHARACTERISTIC);

    if (relayCharacteristic) {
      Serial.println("Relay characteristic found");
      uint8_t relayState;
      relayCharacteristic.readValue(relayState);
      Serial.print("Relay state: ");
      Serial.println(relayState);
      delay(5000); // Ensure the state is read
    }
    peripheral.disconnect();
  }
}

BLEDevice connectToPeripheral() {
  BLEDevice peripheral;

  // Retry connecting to peripheral to ensure connection is established
  for (int attempt = 0; attempt < 5; attempt++) {
    BLE.scanForUuid(BLE_UUID_RELAY_SERVICE); // Ensure scan is active
    delay(2000); // Wait for scan results
    peripheral = BLE.available();
    if (peripheral) {
      Serial.print("Found ");
      Serial.print(peripheral.address());
      Serial.print(" '");
      Serial.print(peripheral.localName());
      Serial.print("' ");
      Serial.print(peripheral.advertisedServiceUuid());
      Serial.println();

      for (int connAttempt = 0; connAttempt < 3; connAttempt++) {
        Serial.println("Attempting to connect to peripheral...");
        if (peripheral.connect()) {
          Serial.println("Connected to peripheral");

          if (peripheral.discoverAttributes()) {
            Serial.println("Attributes discovered");
            return peripheral;
          } else {
            Serial.println("Attribute discovery failed!");
            peripheral.disconnect();
          }
        } else {
          Serial.println("Failed to connect to peripheral");
        }
        delay(5000); // Delay between connection attempts
      }
    } else {
      Serial.println("Peripheral not found in scan results.");
    }
    delay(2000); // Delay between scan attempts
  }

  return BLEDevice();
}

and the code for NANO BLE:
#include <ArduinoBLE.h>

const int relayPin = 2; // Relay pin
const int buttonPin = 3; // Button pin
const int ledPin = LED_BUILTIN; // Built-in LED for BLE connection status
bool relayState = false;
unsigned long lastDebounceTime = 0;
const unsigned long debounceDelay = 50; // Debounce delay
const unsigned long relayDelay = 1000; // 1 second delay between relay state changes
unsigned long lastRelayTime = 0;

BLEService relayService("19B10010-E8F2-537E-4F6C-D104768A1214"); // Relay service

// Relay characteristic
BLEByteCharacteristic relayCharacteristic("19B10011-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite);

void setup() {
  Serial.begin(9600);
  pinMode(relayPin, OUTPUT);
  pinMode(buttonPin, INPUT_PULLUP);
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);

  // Begin initialization
  if (!BLE.begin()) {
    Serial.println("Starting BLE failed!");
    while (1);
  }

  // Set local name and advertised service
  BLE.setLocalName("NanoBLERelay");
  BLE.setAdvertisedService(relayService);

  // Add characteristics to service
  relayService.addCharacteristic(relayCharacteristic);

  // Add service
  BLE.addService(relayService);

  // Initialize characteristic values
  relayCharacteristic.writeValue(0);

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

void loop() {
  BLEDevice central = BLE.central();

  if (central) {
    Serial.print("Connected to central: ");
    Serial.println(central.address());
    digitalWrite(ledPin, HIGH);

    while (central.connected()) {
      BLE.poll();

      // Check button press
      checkButton();

      // Handle relay state change from BLE
      if (relayCharacteristic.written()) {
        relayState = relayCharacteristic.value();
        digitalWrite(relayPin, relayState ? HIGH : LOW);
        Serial.print("Relay state changed via BLE to: ");
        Serial.println(relayState ? "ON" : "OFF");
      }
    }

    Serial.print("Disconnected from central: ");
    Serial.println(central.address());
    digitalWrite(ledPin, LOW);
  } else {
    // Ensure relay control is available even when BLE is not connected
    checkButton();
  }
}

void checkButton() {
  unsigned long currentTime = millis();
  int buttonState = digitalRead(buttonPin);

  if (buttonState == LOW && (currentTime - lastDebounceTime) > debounceDelay) {
    if ((currentTime - lastRelayTime) > relayDelay) {
      relayState = !relayState;
      relayCharacteristic.writeValue(relayState);
      digitalWrite(relayPin, relayState ? HIGH : LOW);
      Serial.print("Relay state toggled via button to: ");
      Serial.println(relayState ? "ON" : "OFF");
      lastRelayTime = currentTime;
    }
    lastDebounceTime = currentTime;
  }
}

Thanks for all the help!

Hi @n3jk0

I recommend taking a look at the code provided here to see if you can spot some significant difference between your code and their code (which was reported as working fine):


Another forum user posted a sketch that demonstrates switching back and forth between the two radios here:

That one is a bit older and a bit less relevant for your application because it also has code for using the "ArduinoIoTCloud" library (which was the specific application under discussion in that thread). So I would focus on the code at the first link, but thought I would mention this one as well in case it might be useful in the end.

1 Like

Hey! Thank you for pointing this thread out. I have tested the example and it indeed works. This example unfortunately only sets up a BLE peripheral, which I can connect to via LightBlue, but I believe the issue I am encountering is trying to read the data from another BLE.
I know in theory it shouldn't be that much of a difference, but I can't seem to get it to work. More trying I guess.

I also found this library:

Will keep you updated if I have any luck.

I'm sorry to hear that. I was aware that your specific usage was different from that in the other demonstration sketches, but hoped that the difference in application was irrelevant to the problem. My hypothesis that the solution might be simply a matter of using the right procedure for switching between the two radio modes. I assumed that procedure would be fairly application independent, but didn't actually look into it.

Thanks! I'm sure others who have similar problems and find this thread during their research will be very grateful for any information you share.