I’m new to BLE and building a remote controlled sprinkler. I have two MKR1010 boards: one runs the sprinkler web server (the server) and the other acts as a BLE provisioning client (the client) for testing.
Objectives
BLE Pairing & Provisioning
The client scans for and connects to the selected server over BLE. Then the client writes the Wi Fi SSID and password into the server’s BLE characteristics.
Wi Fi HTTP Server Setup
The server uses those credentials to join my home network, then starts its HTTP server on port 80 and obtains an IP address (for example 192.168.1.42).
BLE Confirmation
The server writes its HTTP server IP into the BLE IP characteristic. The client reads that IP over BLE and then connects to the server’s HTTP host. Both devices then disconnect and close the BLE connection.
Sprinkler Control (next steps)
Once provisioned, the client will send HTTP commands (“START”, “STOP”, “CONFIG …”) to control the sprinkler zones.
Key UUIDs for quick reference
Provisioning Service: 12345678-1234-1234-1234-1234567890ab
SSID Characteristic: 0000ABCD-0000-1000-8000-00805F9B34FB
Password Characteristic: 0000EF01-0000-1000-8000-00805F9B34FB
IP Characteristic: 0000EF02-0000-1000-8000-00805F9B34FB
What I’ve checked so far
Matching UUID strings in client and server code
Characteristic permissions set to BLERead and BLEWrite
Timing loops for connection, discovery, and write operations
BLE stack initialization order on both sides
Questions
- Am I advertising the service correctly and is the client discovering it properly?
- Is my write/read sequence valid or should I be using notifications or a different API?
- Are there any BLE gotchas on the MKR1010 (for example, must I call BLE.poll() or BLE.advertise() differently)?
Client (Arduino) Serial Output
=== BLE Provisioning Client ===
Scanning for BLE devices for 6 seconds…
[0] c2:a2:a2:0e:be:5b ‘(no name)’
[1] b0:b2:1c:56:b0:b6 ‘MKR1010’
… (more devices listed) …
Enter device number (0–9):
You selected device number: 1
Connecting to b0:b2:1c:56:b0:b6
Connected
Discovering attributes (services/characteristics)…
Warning: discoverAttributes() returned false
ERROR: provisioning service not found
Server (MKR1010) Serial Output
=== MKR1010 Provisioning & Sprinkler ===
Advertising BLE provisioning service…
Waiting for BLE central to connect…
BLE central connected: b0:b2:1c:56:36:fa
Waiting for SSID and password…
Timeout waiting for credentials; restarting provisioning
Advertising BLE provisioning service…
Waiting for BLE central to connect…
… (repeats)
Problem Statement
The client sees and connects to the MKR1010 but can’t discover the provisioning service (UUID 12345678-1234-1234-1234-1234567890ab). On the server side it times out waiting for characteristics to be written, then resets advertising.
Server code
#include <SPI.h>
#include <ArduinoBLE.h>
#include <WiFiNINA.h>
// ───── UUID definitions ─────────────────────────────────────────────────────────
#define PROV_SERVICE_UUID "12345678-1234-1234-1234-1234567890ab"
#define SSID_CHAR_UUID "0000ABCD-0000-1000-8000-00805F9B34FB"
#define PASS_CHAR_UUID "0000EF01-0000-1000-8000-00805F9B34FB"
#define IP_CHAR_UUID "0000EF02-0000-1000-8000-00805F9B34FB"
// ───── BLE provisioning service & characteristics ───────────────────────────────
BLEService provService(PROV_SERVICE_UUID);
BLECharacteristic ssidChar(SSID_CHAR_UUID, BLERead | BLEWrite, 32);
BLECharacteristic passChar(PASS_CHAR_UUID, BLERead | BLEWrite, 64);
BLECharacteristic ipChar(IP_CHAR_UUID, BLERead, 16);
// ───── HTTP server on Wi Fi ─────────────────────────────────────────────────────
const int SERVER_PORT = 80;
WiFiServer httpServer(SERVER_PORT);
// ───── State variables ──────────────────────────────────────────────────────────
String ssid, pass;
bool clientPreviouslyConnected = false;
void setup() {
Serial.begin(115200);
while (!Serial);
Serial.println("\n=== MKR1010 Provisioning & Sprinkler ===");
// Initialize BLE
if (!BLE.begin()) {
Serial.println("ERROR: BLE.init() failed");
while (1) delay(1000);
}
delay(500);
// Set up provisioning service
provService.addCharacteristic(ssidChar);
provService.addCharacteristic(passChar);
provService.addCharacteristic(ipChar);
BLE.addService(provService);
BLE.setLocalName("MKR1010");
BLE.setAdvertisedService(provService);
// Run BLE provisioning with timeout logic
bleProvisioning();
// Start HTTP server after successful provisioning
httpServer.begin();
Serial.print("HTTP server live at http://");
Serial.print(WiFi.localIP());
Serial.print(':');
Serial.println(SERVER_PORT);
}
void loop() {
// Serve sprinkler commands over HTTP
WiFiClient client = httpServer.available();
bool isNowConnected = (bool)client;
if (isNowConnected && !clientPreviouslyConnected) {
Serial.println(">> HTTP client connected");
} else if (!isNowConnected && clientPreviouslyConnected) {
Serial.println(">> HTTP client disconnected");
}
clientPreviouslyConnected = isNowConnected;
if (!isNowConnected) return;
String cmd = client.readStringUntil('\n');
cmd.trim();
Serial.print("HTTP cmd → ");
Serial.println(cmd);
parseCommand(cmd, client);
client.stop();
}
// BLE provisioning: SSID/PW over BLE → Wi Fi → return IP over BLE
void bleProvisioning() {
while (true) {
// Advertise & wait for a central
BLE.advertise();
Serial.println("Advertising BLE provisioning service…");
Serial.println("Waiting for BLE central to connect…");
BLEDevice central;
do {
central = BLE.central();
delay(100);
} while (!central);
Serial.print("BLE central connected: ");
Serial.println(central.address());
// Wait for SSID & password writes with 10s timeout
bool gotSSID = false, gotPass = false;
unsigned long start = millis();
Serial.println("Waiting for SSID & password…");
while (!(gotSSID && gotPass)) {
if (millis() - start > 30000) {
Serial.println("⏱ Timeout waiting for credentials; restarting provisioning");
central.disconnect();
BLE.stopAdvertise();
delay(200);
break; // restart provisioning loop
}
if (!gotSSID && ssidChar.written()) {
char buf[33] = {0};
ssidChar.readValue((uint8_t*)buf, ssidChar.valueLength());
ssid = String(buf);
Serial.print(" • Got SSID: ");
Serial.println(ssid);
gotSSID = true;
}
if (!gotPass && passChar.written()) {
char buf[65] = {0};
passChar.readValue((uint8_t*)buf, passChar.valueLength());
pass = String(buf);
Serial.print(" • Got Password: ");
Serial.println(pass);
gotPass = true;
}
delay(100);
}
if (!(gotSSID && gotPass)) {
// Credentials not fully received in time → retry
continue;
}
// Join Wi Fi
Serial.println("Joining Wi Fi…");
if (!connectToWiFi()) {
Serial.println("ERROR: Wi Fi join failed");
while (1) delay(1000);
}
// Send IP back over BLE
String ip = WiFi.localIP().toString();
ipChar.writeValue((const uint8_t*)ip.c_str(), ip.length());
Serial.print("Wrote IP to BLE: ");
Serial.println(ip);
// Keep BLE alive for client to read IP
Serial.println("Keeping BLE alive for 5 s for IP read…");
delay(5000);
// Teardown and exit provisioning
BLE.stopAdvertise();
central.disconnect();
Serial.println("Provisioning complete!");
break;
}
}
// Wi Fi connection helper
bool connectToWiFi() {
WiFi.begin(ssid.c_str(), pass.c_str());
int retries = 0;
while (WiFi.status() != WL_CONNECTED && retries++ < 20) {
delay(500);
Serial.print('.');
}
Serial.println();
if (WiFi.status() == WL_CONNECTED) {
Serial.print("Connected! IP: ");
Serial.println(WiFi.localIP());
return true;
}
return false;
}
// Handle sprinkler commands over HTTP
void parseCommand(const String &cmd, WiFiClient &c) {
if (cmd.equalsIgnoreCase("START")) {
c.println("OK: Started");
Serial.println("Sprinkler STARTED");
}
else if (cmd.equalsIgnoreCase("STOP")) {
c.println("OK: Stopped");
Serial.println("Sprinkler STOPPED");
}
else if (cmd.startsWith("CONFIG ")) {
c.println("OK: Config updated");
Serial.print("Config → ");
Serial.println(cmd.substring(7));
}
else {
c.println("ERROR: Unknown command");
Serial.println("Unknown HTTP command");
}
}
Client code
#include <SPI.h>
#include <ArduinoBLE.h>
// ───── UUIDs ───────────────────────────────────────────────────────────────────
const char* PROV_SERVICE_UUID = "12345678-1234-1234-1234-1234567890ab";
const char* SSID_CHAR_UUID = "0000ABCD-0000-1000-8000-00805F9B34FB";
const char* PASS_CHAR_UUID = "0000EF01-0000-1000-8000-00805F9B34FB";
const char* IP_CHAR_UUID = "0000EF02-0000-1000-8000-00805F9B34FB";
// ───── Test credentials ─────────────────────────────────────────────────────────
const char* testSsid = "my ssid";
const char* testPass = "my password";
// ───── Scan settings ───────────────────────────────────────────────────────────
const unsigned long SCAN_DURATION = 6000; // scan for 6 seconds
const int MAX_DEVICES = 12;
void setup() {
Serial.begin(115200);
while (!Serial);
Serial.println("\n=== BLE Provisioning Client ===");
if (!BLE.begin()) {
Serial.println("ERROR: BLE.begin() failed");
while (1) delay(1000);
}
// 1) Scan for peripherals
Serial.print("Scanning for BLE devices for ");
Serial.print(SCAN_DURATION / 1000);
Serial.println(" seconds...");
BLE.scan();
BLEDevice devices[MAX_DEVICES];
int deviceCount = 0;
unsigned long scanStart = millis();
while (millis() - scanStart < SCAN_DURATION) {
BLEDevice dev = BLE.available();
if (dev && deviceCount < MAX_DEVICES) {
String addr = dev.address();
bool seen = false;
for (int i = 0; i < deviceCount; i++) {
if (devices[i].address() == addr) {
seen = true;
break;
}
}
if (!seen) {
devices[deviceCount] = dev;
String name = dev.localName();
if (name.length() == 0) name = "(no name)";
Serial.print("[");
Serial.print(deviceCount);
Serial.print("] ");
Serial.print(addr);
Serial.print(" '");
Serial.print(name);
Serial.println("'");
deviceCount++;
}
}
}
BLE.stopScan();
if (deviceCount == 0) {
Serial.println("No BLE devices found. Halting.");
while (1) delay(1000);
}
// 2) Prompt for selection
int selection = -1;
while (selection < 0 || selection >= deviceCount) {
Serial.print("Enter device number (0–");
Serial.print(deviceCount - 1);
Serial.print("): ");
while (!Serial.available()) delay(10);
selection = Serial.parseInt();
Serial.read(); // consume the newline
// Echo back the choice
Serial.print("You selected device number: ");
Serial.println(selection);
if (selection < 0 || selection >= deviceCount) {
Serial.println("Invalid selection, please try again.");
}
}
BLEDevice peripheral = devices[selection];
Serial.print("Connecting to ");
Serial.println(peripheral.address());
// ─── Fix: call connect() on the peripheral, not BLE
if (!peripheral.connect()) {
Serial.println("ERROR: Connection failed");
while (1) delay(1000);
}
Serial.println("Connected");
delay(200); // give BLE stack a moment
// 3) Discover attributes
Serial.println("Discovering attributes (services/characteristics)...");
if (!peripheral.discoverAttributes()) {
Serial.println("Warning: discoverAttributes() returned false");
}
delay(200);
// 4) Locate provisioning service
BLEService prov = peripheral.service(PROV_SERVICE_UUID);
unsigned long svcStart = millis();
while (!prov && millis() - svcStart < 5000) {
// sometimes it takes a bit to appear
delay(200);
prov = peripheral.service(PROV_SERVICE_UUID);
}
if (!prov) {
Serial.println("ERROR: provisioning service not found");
peripheral.disconnect();
while (1) delay(1000);
}
// 5) Locate characteristics
BLECharacteristic ssidChr = prov.characteristic(SSID_CHAR_UUID);
BLECharacteristic passChr = prov.characteristic(PASS_CHAR_UUID);
BLECharacteristic ipChr = prov.characteristic(IP_CHAR_UUID);
if (!ssidChr || !passChr || !ipChr) {
Serial.println("ERROR: missing one or more characteristics");
peripheral.disconnect();
while (1) delay(1000);
}
// 6) Write SSID
Serial.print("Writing SSID: ");
Serial.println(testSsid);
if (ssidChr.writeValue((const uint8_t*)testSsid, strlen(testSsid))) {
Serial.println(" → SSID written");
} else {
Serial.println(" → SSID write FAILED");
}
// 7) Write Password
Serial.print("Writing Password: ");
Serial.println(testPass);
if (passChr.writeValue((const uint8_t*)testPass, strlen(testPass))) {
Serial.println(" → Password written");
} else {
Serial.println(" → Password write FAILED");
}
// 8) Wait up to 10s for the server to join Wi Fi & publish its IP
Serial.println("Waiting up to 10 s for server to join Wi Fi & publish IP…");
unsigned long ipStart = millis();
String ip;
while (millis() - ipStart < 10000) {
int len = ipChr.valueLength();
if (len > 0) {
uint8_t buf[32] = {0};
int readLen = ipChr.readValue(buf, sizeof(buf) - 1);
if (readLen > 0) {
buf[readLen] = '\0';
ip = String((char*)buf);
break;
}
}
delay(200);
}
// 9) Report IP or timeout
if (ip.length()) {
Serial.print("Server IP: ");
Serial.println(ip);
} else {
Serial.println("ERROR: no IP received within timeout");
}
// 10) Disconnect and finish
peripheral.disconnect();
Serial.println("Provisioning client done.");
}
void loop() {
// nothing to do
}