Help keep your IoT pet alive via the Internet!
Story
If you had an IoT pet, what would it eat? WiFi SSIDs, of course!
The Nerd is a wireless electronic pet that survives by collecting WiFi SSIDs along with some rest and sunlight.
In order for it to thrive, you must balance offline and online mode with light and darkness to ensure that it has a proper daily eat-sleep-Nerd routine.
If it is out of WiFi for too long, it will communicate an SOS in Morse code using its built-in piezo speaker. The longer it is offline, the more it will beep.
In a Nutshell
The Nerd will wake up each half hour to scan the networks around it. If it detects new networks, it will store them and go back to sleep in low power mode (to save battery life). Otherwise it will complain by making noise with the buzzer until you either feed it or put it in the dark.
It will also understand when it’s at home by connecting to your wifi network. When at home the Nerd will be able to connect to internet and get the current time and date.
If not fed for more than two days, it will die dramatically, making a lot of noise.
Components
- RGB LED
- Phototransistor
- Buzzer
- Battery
- 220 Ohm resistor
Learning Goals
- Managing full WiFi functionalities
- Storing data in Flash Memory
- Managing time and Real Time Clock
- Managing Low Power mode
Want to Know More?
This tutorial is part of a series of experiments that familiarise you with the MKR1000 and IoT. All experiments can be built using the components contained in the MKR IoT Bundle.
- The Nerd
Set up the Board
In order to implement all the functionalities we are going to use the following libraries:
- WiFi101 // to connect to internet and scan the networks
- FlashStorage // to save values so that they don’t get erased at each reboot
- RTCZero // to manage time triggered events
- ArduinoLowPower // to save battery power
- WiFiUdp // to get the time and date from internet
You can download them from the library manager as explained in this guide.
Scanning WiFi Networks
The Nerd is hungry for networks!
Scanning network is pretty easy, just upload this example sketch or go to > examples > WiFi101 > ScanNetworksAdvanced for a more extended version.
#include <SPI.h>
#include <WiFi101.h>
void setup() {
//Initialize serial and wait for port to open:
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
// scan for existing networks:
Serial.println();
Serial.println("Scanning available networks...");
listNetworks();
}
void loop() {
delay(10000);
// scan for existing networks:
Serial.println("Scanning available networks...");
listNetworks();
}
void listNetworks() {
// scan for nearby networks:
Serial.println("** Scan Networks **");
int numSsid = WiFi.scanNetworks();
if (numSsid == -1)
{
Serial.println("Couldn't get a WiFi connection");
while (true);
}
// print the list of networks seen:
Serial.print("number of available networks: ");
Serial.println(numSsid);
// print the network number and name for each network found:
for (int thisNet = 0; thisNet < numSsid; thisNet++) {
Serial.print(thisNet + 1);
Serial.print(" SSID: ");
Serial.println(WiFi.SSID(thisNet));
Serial.flush();
}
Serial.println();
}
Store Values in Flash Memory
Surely you don’t want the Nerd to die each time it goes out of battery!
To avoid this behaviour we will save some variables (as the food amount) in the Flash memory so that they can be retrieved even after the board has been turned off and turned on again.
You can understand the basic functionalities by using :
example > FlashStorage > FlashStoreAndRetrieve
We will save the names of the networks so that the Nerd will eat them only once, also we will use the array in which these SSIDs are saved to count the amount of food it ate during the day.
Values saved on the Flash memory will survive a reset of the board but not the upload of a new sketch. Each time you upload a new sketch the flash memory will be emptied as well.
This sketch will scan the networks and save the SSID in the flash memory.
#include <SPI.h>
#include <WiFi101.h>
#include <FlashStorage.h>
#define MAGIC_NUMBER 0x7423 // arbitrary number to double check the saved SSID
#define MaxNet 30 // max amount of network to be saved
int PosToBeSaved = 0; // Variable used to navigate the array of networks
int daily_amount_of_food = 12; // The amount of food per day needed to survive
// Struct of variable to be saved in flash memory
typedef struct {
int magic;
boolean valid[MaxNet];
char SSIDs[MaxNet][100];
} Networks;
FlashStorage(my_flash_store, Networks);
Networks values;
void setup() {
Serial.begin(115200);
while(!Serial); // wait until the Serial montior has be opened
delay(2000);
values = my_flash_store.read(); // Read values from flash memory
if (values.magic == MAGIC_NUMBER) { // If token is correct print saved networks
Serial.println("saved data:");
Serial.println("");
for (int a = 0; a < MaxNet; a++) {
if (values.valid[a]) {
Serial.println(values.SSIDs[a]);
} else {
PosToBeSaved = a;
}
}
}
}
void loop() {
// Temporarly save the number of networks
int networks_already_saved = PosToBeSaved;
getNetwork();
if (PosToBeSaved >= daily_amount_of_food) {
Serial.println("Enough food for today");
}
delay(5000);
}
// Feed the Nerd with networks's SSID
void getNetwork() {
// scan for nearby networks:
Serial.println("\n*Scan Networks*\n");
int numSsid = WiFi.scanNetworks();
delay(1000);
if (numSsid == -1)
{
Serial.println("There are no WiFi networks here..");
} else {
Serial.print("number of available networks: ");
Serial.println(numSsid);
// print the network number and name for each network found:
for (int thisNet = 0; thisNet < numSsid; thisNet++) {
Serial.print("SSID: ");
Serial.println(WiFi.SSID(thisNet));
delay(500);
char* net = WiFi.SSID(thisNet);
bool canBeSaved = true;
// check if the network has already been saved
for (int a = 0; a < PosToBeSaved ; a++) {
if (values.valid[a]) {
if (strncmp(net, values.SSIDs[a], 100) == 0 || strnlen(net, 100) == 0) {
Serial.println("Not saved");
canBeSaved = false;
}
}
}
// Store ssid name
if (canBeSaved && PosToBeSaved < MaxNet) {
if (strlen(net) + 1 < 100 && strlen(net) > 0) { // check if the SSID name fits 100 bytes
memset(values.SSIDs[PosToBeSaved], 0, sizeof(values.SSIDs[PosToBeSaved])); // set all characters to zero
memcpy(values.SSIDs[PosToBeSaved], net, strlen(net) + 1); // copy "net" to values.SSDs[thisNet]
values.valid[PosToBeSaved] = true;
values.magic = MAGIC_NUMBER;
my_flash_store.write(values);
Serial.println(String(values.SSIDs[PosToBeSaved]) + " saved in position " + String(PosToBeSaved));
PosToBeSaved ++;
}
else {
Serial.println(" network skipped");
}
}
}
}
}
Note how we defined a maximum amount of networks that can be saved, so to avoid memory space issues:
#define MaxNet 30
int PosToBeSaved = 0;
has been used to navigate the array in which networks names are saved and to measure the amount of food already eaten.
Managing Time
We can combine the functionalities of the Real Time Clock (RTC) with the WiFi to get the current time and date and then set the inner clock of the board. In this way we can trigger time based events in a long timeframe without using the millis()
function which can be tricky when you have to convert milliseconds into days.
We will get the time from a Network Time Protocol (NTP) time server and then set the RTC with it. Note that the time received is in epoch format, which is the amount of seconds since January 1th 1970.
Read more: The Nerd