MAC address could be of use to you, but remember that it can be spoofed. Aside from that Serial and Model numbers are not accessible from software layer.
Here is a simple implementation for fetching the MAC address
#include <WiFiNINA.h>
void setup() {
Serial.begin(9600);
while (!Serial);
if (WiFi.status() == WL_NO_MODULE) {
Serial.println("Communication with WiFi module failed!");
while (true);
}
byte mac[6];
WiFi.macAddress(mac);
Serial.print("MAC: ");
for (int i = 5; i >= 0; i--) {
if (mac[i] < 16) {
Serial.print("0");
}
Serial.print(mac[i], HEX);
if (i > 0) {
Serial.print(":");
}
}
Serial.println();
}
void loop() {}
Another solution that I can think of is writing your own unique identifier such as UUID to EEPROM memory for each board.
If you're generating a UUID in the context of a server or a device with more resources, there are libraries and tools that can create these for you based on various factors to ensure uniqueness (like timestamp, MAC address, etc.). However, with a microcontroller like the one on the Arduino MKR WiFi 1010, you have a more constrained environment, and the process can be more manual.
You can use an online UUID generator or can generate it using Python:
import uuid
print(uuid.uuid4())
After generating your UUID's, you can write them on EEPROM but make sure to not over-write EEPROM since it has very limited call boundaries.
Writing to EEPROM
#include <EEPROM.h>
const char* MY_UUID = "550e8400-e29b-41d4-a716-446655440000";
const int EEPROM_UUID_ADDRESS = 0; // starting address in EEPROM
void setup() {
Serial.begin(9600);
// Write UUID to EEPROM
for (int i = 0; i < strlen(MY_UUID); i++) {
EEPROM.write(EEPROM_UUID_ADDRESS + i, MY_UUID[i]);
}
Serial.println("UUID written to EEPROM");
}
void loop() {}
"starting address in EEPROM" is the address of the first box (or byte) where the program begins storing our UUID. By specifying const int EEPROM_UUID_ADDRESS = 0;
, we are saying we'll start storing the UUID from the very first byte (box) of the EEPROM. If you were storing other data in EEPROM, you would need to ensure that the addresses don't overlap. For example, if you had other data stored in the first 10 bytes, you might set EEPROM_UUID_ADDRESS
to 10 to start storing the UUID from the 11th byte.
Reading from the EEPROM:
#include <EEPROM.h>
const int UUID_LENGTH = 36; // Length of the UUID string
char readUUID[UUID_LENGTH + 1]; // +1 for the null terminator
const int EEPROM_UUID_ADDRESS = 0; // starting address in EEPROM
void setup() {
Serial.begin(9600);
// Read UUID from EEPROM
for (int i = 0; i < UUID_LENGTH; i++) {
readUUID[i] = EEPROM.read(EEPROM_UUID_ADDRESS + i);
}
readUUID[UUID_LENGTH] = '\0'; // Null terminate the string
Serial.print("UUID read from EEPROM: ");
Serial.println(readUUID);
}
void loop() {}
+1 for the null terminator:
In the C and C++ programming languages, strings are represented as an array of characters. The end of the string is marked by a special character known as the "null terminator" or "null character", which is represented by the value '\0'
(a byte with all bits set to 0).
When we read or write strings to/from memory (like EEPROM), we often need to account for this null terminator to ensure the string is properly terminated and can be safely used with string functions.
In the code, the UUID is 36 characters long. When we declare an array to hold this UUID with the line:
char readUUID[UUID_LENGTH + 1];
We're allocating space for the 36 characters of the UUID plus an additional character for the null terminator, hence UUID_LENGTH + 1
.
Later, after reading the UUID from EEPROM, we ensure this array is a properly terminated string by adding the null terminator:
readUUID[UUID_LENGTH] = '\0';
This ensures that when we use readUUID
in functions that expect a string, they'll recognize where the string ends. Without the null terminator, these functions wouldn't know where the string finishes, leading to unpredictable behavior or errors.
I hope that this is helpful.