I couldn't really find a good answer from the datasheet and googling, and at this point I'm a bit stuck on the implementation.
I want to be able to do a couple of things individually to make sure I understand them, and then put it all together.
I know the example of having a central's button control an LED on a peripheral. It's part of a built-in example. LEDControl / LED.
My next question would be how I can have the reverse happen - a button press on a peripheral is acknowledged by a central to do something. Is this possible on the Nano 33 IoT?
Finally, let's say I want to do both of these things, but extrapolate it to multiple peripherals on one central. From what I can tell this is a bit hairier. I've seen a few examples (that I cannot link because I made this account today to make this post) and that there are some problems the ArduinoBLE library doesn't handle - first I need to track my "connections" and differentiate them, and that the BLE Sense has a few issues, but the IoT does not. I'm using polldo's library changes and I think they're working? I also saw an example that just used an integer to track it (thank you Klaus K).
I also have no idea what .poll() is doing in the various examples, but I think I'm fundamentally misunderstanding something.
So correct my understanding. Is BLE / the IoT 33 actually forming connections when connecting to multiple devices? Or do I just need to make my services with the right characteristics? Are peripherals just posting up to a billboard for the central to check?
I'll go into a bit more detail here to help. I've got my Central. He's got a button and an LED. I've got one Peripheral, also with a button and an LED.
The Peripheral's button is the 'Requester,' pressing it will ask the central if it can light up it's LED to blink once. A Blink Request.
To indicate that a Peripheral is requesting to blink, an LED on the central will light up. If the button on central is hit, the LED on central will turn off, and the LED on the Peripheral will go ahead and blink.
I'll also post my (incomplete and very ugly) code for peripherals and centrals. Here's a picture of my little test bed.
Central Code
#define BUTTON_RED_PIN 19
#define BUTTON_YELLOW_PIN 17
#define BUTTON_GREEN_PIN 15
#define LED_RED_PIN 2
#define LED_YELLOW_PIN 5
#define LED_GREEN_PIN 8
#define MAX_BLINKERS 3
#include <Arduino.h>
#include <ArduinoBLE.h>
class blinker {
public:
blinker(int button, int led) {
button = buttonPin;
led = ledPin;
}
int getBtn();
int getLed();
//no setters for now, not going to be rewiring things during runtime
//Access point shouldn't be directly changing blinker parameters anyway
//also no default constructor as a blinker without connections is meaningless for now
private:
int buttonPin;
int ledPin;
//for now, pretend button is the host software giving the OK for blink authorization for that particular blinker
//and the LED is a request from a particular blinker for blink authorization
};
//getters, yes I'm shortening them because I'm lazy
int blinker::getBtn() {
return buttonPin;
}
int blinker::getLed() {
return ledPin;
}
// the lone getters makes the pin numbers, in effect, constant once set. You can only read them via the getters.
//create blinkers. For now, these are hard coded. If I were cooler I would loop this
blinker Red = blinker(BUTTON_RED_PIN, LED_RED_PIN);
blinker Yellow = blinker(BUTTON_YELLOW_PIN, LED_YELLOW_PIN);
blinker Green = blinker(BUTTON_GREEN_PIN, LED_GREEN_PIN);
//because I can't both set the led pin to be output to light it up, and input so the arduino can read it
//I have to set a state that tracks it, and just set the output to reflect that state every loop
int ledState[MAX_BLINKERS];
char blinkState;
void checkForNewConnections() {
// check if a peripheral has been discovered
if(BLE.peripheralCount() < MAX_BLINKERS) {
BLEDevice peripheral = BLE.available();
if (peripheral) {
// stop scanning
BLE.stopScan();
// discovered a peripheral, print out address, local name, and advertised service
Serial.print("Found ");
Serial.print(peripheral.address());
Serial.print(" '");
Serial.print(peripheral.localName());
Serial.print("' ");
Serial.print(peripheral.advertisedServiceUuid());
Serial.println();
if (peripheral.localName() != "Blinker") {
Serial.println("Not ours.");
return;
}
if(!peripheral.connect()) {
Serial.println("Failed to connect!");
return;
}
if (!peripheral.discoverAttributes()) {
Serial.println("Attribute discovery failed!");
peripheral.disconnect();
return;
}
if(peripheral.connected()) {
Serial.println("Connected!");
}
else {
Serial.println("Not connected.");
}
}
}
}
//one time on startup
void setup() {
for(int i = 0; i < MAX_BLINKERS; i++) {
ledState[i] = 0;
}
Serial.begin(115200);
pinMode(Red.getBtn(), INPUT);
pinMode(Yellow.getBtn(), INPUT);
pinMode(Green.getBtn(), INPUT);
pinMode(Red.getLed(), OUTPUT);
pinMode(Yellow.getLed(), OUTPUT);
pinMode(Green.getLed(), OUTPUT);
BLE.begin();
Serial.println("BLE Centeral - Blinker Access Point");
BLE.scanForUuid("<UUID #1>");
}
auto timeRef = millis();
//every frame
void loop() {
checkForNewConnections();
int periphCount = BLE.peripheralCount(); //thank you polldo
if (millis() - timeRef >= 5000) {
timeRef = millis();
Serial.print(" Peripheral connected: ");
Serial.println(periphCount);
if (periphCount < MAX_BLINKERS) {
Serial.println("Scanning for blinkers...");
}
}
// Loop through all connected peripheral
for (int periphIdx = 0; periphIdx < periphCount; periphIdx++) {
BLEDevice peripheral = BLE.peripheral(periphIdx);
if (peripheral) {
BLECharacteristic blinkStatusChar = peripheral.characteristic("<UUID #3>");
if (!blinkStatusChar) {
Serial.println("Peripheral does not have blink status characteristic!");
peripheral.disconnect();
}
else {
Serial.print("Peripheral connected, value: ");
blinkStatusChar.read();
Serial.println();
}
}
}
if (periphCount < MAX_BLINKERS) {
BLE.scanForName("Blinker");
}
//TODO: Logic for clearing the peripheral's blink request.
//Do we need to get a confirmation that they blinked? Do I need another service for that?
}
Peripheral Code
#define BUTTON_RED_PIN 19
#define BUTTON_YELLOW_PIN 17
#define BUTTON_GREEN_PIN 15
#define LED_RED_PIN 2
#define LED_YELLOW_PIN 5
#define LED_GREEN_PIN 8
#define MAX_BLINKERS 3
#include <Arduino.h>
#include <ArduinoBLE.h>
class blinker {
public:
blinker(int button, int led) {
button = buttonPin;
led = ledPin;
}
int getBtn();
int getLed();
//no setters for now, not going to be rewiring things during runtime
//Access point shouldn't be directly changing blinker parameters anyway
//also no default constructor as a blinker without connections is meaningless for now
private:
int buttonPin;
int ledPin;
//for now, pretend button is the host software giving the OK for blink authorization for that particular blinker
//and the LED is a request from a particular blinker for blink authorization
};
//getters, yes I'm shortening them because I'm lazy
int blinker::getBtn() {
return buttonPin;
}
int blinker::getLed() {
return ledPin;
}
// the lone getters makes the pin numbers, in effect, constant once set. You can only read them via the getters.
//create blinkers. For now, these are hard coded. If I were cooler I would loop this
blinker Red = blinker(BUTTON_RED_PIN, LED_RED_PIN);
blinker Yellow = blinker(BUTTON_YELLOW_PIN, LED_YELLOW_PIN);
blinker Green = blinker(BUTTON_GREEN_PIN, LED_GREEN_PIN);
PinStatus btnState[MAX_BLINKERS];
BLEService blinkRequestService("<UUID #1>");
BLEUnsignedCharCharacteristic blinkRequestChar("<UUID #2>", BLERead | BLEWrite | BLENotify);
BLEUnsignedCharCharacteristic blinkStatus("<UUID #3>", BLERead | BLEWrite | BLENotify);
void ConnectionHandler(BLEDevice central) {
Serial.print("Connected to Central: ");
Serial.println(central.address());
BLE.advertise();
}
void DisconnectionHandler(BLEDevice central) {
Serial.print("Disconnected from Central: ");
Serial.println(central.address());
BLE.advertise();
}
void setup() {
Serial.begin(115200);
for(int i = 0; i < MAX_BLINKERS; i++) {
btnState[i] = LOW;
}
pinMode(Red.getBtn(), INPUT);
pinMode(Yellow.getBtn(), INPUT);
pinMode(Green.getBtn(), INPUT);
pinMode(Red.getLed(), OUTPUT);
pinMode(Yellow.getLed(), OUTPUT);
pinMode(Green.getLed(), OUTPUT);
if (!BLE.begin()) {
Serial.println("starting BLE failed!");
while (1);
}
// Configure peripheral
BLE.setEventHandler(BLEConnected, ConnectionHandler);
BLE.setEventHandler(BLEDisconnected, DisconnectionHandler);
BLE.setLocalName("Blinker");
BLE.setAdvertisedService(blinkRequestService);
blinkRequestService.addCharacteristic(blinkRequestChar);
blinkRequestService.addCharacteristic(blinkStatus);
BLE.addService(blinkRequestService);
//initialize
blinkRequestChar.writeValue('I'); //I for idle
BLE.advertise();
Serial.println("peripheral configured - central connected: 0");
}
void loop() {
BLE.poll(); //still not sure what this line does
//BLEDevice central = BLE.central();
btnState[0] = digitalRead(Red.getBtn());
btnState[1] = digitalRead(Yellow.getBtn());
btnState[2] = digitalRead(Green.getBtn());
for(int i = 0; i < MAX_BLINKERS; i++) {
if(btnState[i]) {
blinkRequestChar.writeValue('R'); //R for request
}
}
//central will need to clear this when
//TODO: Code to actually blink the LED when central authorizes
}
Anyway, am I crazy for trying to get this to work, is it a fool's errand? Is this possible, or is it outside the realm of what the Arduino is capable of?
If your first question is 'why?' it's to understand it for myself to use for another project, and I want to see what the maximum connections are for the project. Also how extra connections will affect performance. I've got a high speed camera to measure delay the time between button press vs the LED blink.