A smart STEM powered library that advocates for inclusivity for all kids and adolescents!
Software apps and online services
INTRODUCTION
Nowadays, children are distracted by and attached to electronics and current technology. With the development of new technologies, it is inevitable for children to not become dependent on short-lived doses of digital entertainment. A short visit to the library is becoming less enjoyable and interesting for even younger children. On the other hand, sometimes community libraries can be far away, causing a short trip to the library to be a hassle and reducing the access children have to their local libraries. Hopefully, with the implementation of our easy-access, community library, we can give children a better access to a library, making it convenient for every age. Additionally, by incorporating smart technology, this will appeal to children more and demonstrate that reading books can be rewarding. This will help children develop their literacy and overarching education efficiently, while making it suitable for parents who are busy.
The main purpose of this project is to incorporate STEM technology and support into the build of a Little Free Library. Readers will interact with the library by answering historical questions that eventually unlocks the library when you respond with the correct answer. To support the operation and functionality of the library, a solar panel is attached to the roof of it. With anticipation, we hope that through the immersive, interactive experience, an increase of readers will be compelled to visit the library more often.
The materials used to build the library are all purchased from Little Free Library, a non-profit organization. The add-on electronics are mainly purchased from DFRobot, while the software is based from Arduino IDE.
FEATURES
1. A 3.5" LCD to display welcome messages, questions, and respond to readers' answers.
2. A speaker to announce welcome messages, questions, and respond to readers' answers.
3. An interactable keypad for the reader to type in their responses.
4. An electronic lock that unlocks the library when the reader answers the question correctly.
5. A distance sensor to detect empty shelves that later notifies the library manager.
6. A solar panel and battery to power the library.
7. A light with a sensor to indicate when to turn on.
8. A light strip to light up the library during night time.
HOW IT WORKS
The LCD and speaker plays the welcome message and waits for the reader's input. Once an input is detected, a question asking what the U.S. President's inauguration year was appears. If the reader responds with the correct answer, the library will be unlocked. If not, the reader will continuously receive new questions until they answer correctly. The questions are completely randomized. However, if the reader cannot input an answer within the time frame, the screen will return to its welcome message and the whole process restarts again.
FRONT
FRONT
HUMAN INTERFACE
DISPLAY, KEYPAD, and SPEAKER
CONTROL PANEL
DFROBOT ESP32-E
DECORE
D1 CODERS
POWER SUPPLY
SOLAR
SOLAR and BATTERY
DISPLAY HIGHLIGHT
DISPLAY
NIGHT LIGHT
LIGHT WITH SENSORS
EMPTY SENSOR
EMPTY SENSOR
Schematics
LFL
#include "DFRobot_GDL.h"
#include "Wire.h"
#include "I2CKeyPad.h"
#include "DFRobotDFPlayerMini.h"
#include "DFRobot_EnvironmentalSensor.h"
const int p_rows = 45;
const int p_columns = 30;
const int y_columns = 5;
static char p_2d [p_rows][p_columns] = {
{"George Washington"},
{"John Adams"},
{"Thomas Jefferson"},
{"James Madison"},
{"James Monroe"},
{"John Quincy Adams"},
{"Andrew Jackson"},
{"Martin Van Buren"},
{"William Henry Harrison"},
{"John Tyler"},
{"James Knox Polk"},
{"Zachary Taylor"},
{"Millard Fillmore"},
{"Franklin Pierce"},
{"James Buchanan"},
{"Abraham Lincoln"},
{"Andrew Johnson"},
{"Ulysses S. Grant"},
{"Rutherford B. Hayes"},
{"James A. Garfield"},
{"Chester Arthur"},
{"Grover Cleveland"},
{"Benjamin Harrison"},
{"William McKinley"},
{"Theodore Roosevelt"},
{"William Howard Taft"},
{"Woodrow Wilson"},
{"Warren G. Harding"},
{"Calvin Coolidge"},
{"Herbert Hoover"},
{"Franklin D. Roosevelt"},
{"Harry S. Truman"},
{"Dwight D. Eisenhower"},
{"John F. Kennedy"},
{"Lyndon B. Johnson"},
{"Richard M. Nixon"},
{"Gerald R. Ford"},
{"Jimmy Carter"},
{"Ronald Reagan"},
{"George Bush"},
{"William J. Clinton"},
{"George W. Bush"},
{"Barack Obama"},
{"Donald J. Trump"},
{"Joe Biden"}};
static char p_year [p_rows][y_columns] = {
"1789", //{"George Washington"}, 1
"1797", //{"John Adams"}, 2
"1801", //{"Thomas Jefferson"}, 3
"1809", //{"James Madison"}, 4
"1817", //{"James Monroe"}, 5
"1825", //{"John Quincy Adams"}, 6
"1829", //{"Andrew Jackson"}, 7
"1837", //{"Martin Van Buren"}, 8
"1841", //{"William Henry Harrison"}, 9
"1841", //{"John Tyler"}, 10
"1845", //{"James Knox Polk"}, 11
"1849", //{"Zachary Taylor"}, 12
"1850", //{"Millard Fillmore"}, 13
"1853", //{"Franklin Pierce"}, 14
"1857", //{"James Buchanan"}, 15
"1861", //{"Abraham Lincoln"}, 16
"1865", //{"Andrew Johnson"}, 17
"1869", //{"Ulysses S. Grant"},
"1877", //{"Rutherford B. Hayes"},
"1881", //{"James A. Garfield"},
"1881", //{"Chester Arthur"},
"1885", //{"Grover Cleveland"},
"1889", //{"Benjamin Harrison"},
"1897", //{"William McKinley"},
"1901", //{"Theodore Roosevelt"},
"1909", //{"William Howard Taft"},
"1913", //{"Woodrow Wilson"},
"1921", //{"Warren G. Harding"},
"1923", //{"Calvin Coolidge"},
"1929", //{"Herbert Hoover"},
"1933", //{"Franklin D. Roosevelt"},
"1945", //{"Harry S. Truman"},
"1953", //{"Dwight D. Eisenhower"},
"1961", //{"John F. Kennedy"},
"1963", //{"Lyndon B. Johnson"},
"1969", //{"Richard M. Nixon"},
"1974", //{"Gerald R. Ford"},
"1977", //{"Jimmy Carter"},
"1981", //{"Ronald Reagan"},
"1989", //{"George H. W. Bush"},
"1993", //{"William J. Clinton"},
"2001", //{"George W. Bush"},
"2009", //{"Barack Obama"},
"2017", //{"Donald J. Trump"},
"2021" //{"Joe Biden"}
};
//folder 01
//001 - welcome
//002 - unlock
//003 - what year_did
//004 - become_pre
//005 - example
//006 - press any key to start
//folder 02
//001 to 009
//folder 03
//001gw ... 0045jb
#define ESP32
//LCD SCREEN
/*M0*/
#if defined ARDUINO_SAM_ZERO
#define TFT_DC 7
#define TFT_CS 5
#define TFT_RST 6
/*ESP32 and ESP8266*/
#elif defined(ESP32) || defined(ESP8266)
#define TFT_DC D2
#define TFT_CS D6
#define TFT_RST D3
/*AVR series mainboard*/
#else
#define TFT_DC 2
#define TFT_CS 3
#define TFT_RST 4
#endif
/**
* @brief Constructor Constructor of hardware SPI communication
* @param dc Command/data line pin for SPI communication
* @param cs Chip select pin for SPI communication
* @param rst reset pin of the screen
*/
//DFRobot_ST7789_240x240_HW_SPI screen(/*dc=*/TFT_DC,/*cs=*/TFT_CS,/*rst=*/TFT_RST);
//DFRobot_ST7789_240x320_HW_SPI screen(/*dc=*/TFT_DC,/*cs=*/TFT_CS,/*rst=*/TFT_RST);
//DFRobot_ILI9341_240x320_HW_SPI screen(/*dc=*/TFT_DC,/*cs=*/TFT_CS,/*rst=*/TFT_RST);
DFRobot_ILI9488_320x480_HW_SPI screen(/*dc=*/TFT_DC,/*cs=*/TFT_CS,/*rst=*/TFT_RST);
/* M0 mainboard DMA transfer */
//DFRobot_ST7789_240x240_DMA_SPI screen(/*dc=*/TFT_DC,/*cs=*/TFT_CS,/*rst=*/TFT_RST);
//DFRobot_ST7789_240x320_DMA_SPI screen(/*dc=*/TFT_DC,/*cs=*/TFT_CS,/*rst=*/TFT_RST);
//DFRobot_ILI9341_240x320_DMA_SPI screen(/*dc=*/TFT_DC,/*cs=*/TFT_CS,/*rst=*/TFT_RST);
//DFRobot_ILI9488_320x480_DMA_SPI screen(/*dc=*/TFT_DC,/*cs=*/TFT_CS,/*rst=*/TFT_RST);
/*
*User-selectable macro definition color
*COLOR_RGB565_BLACK COLOR_RGB565_NAVY COLOR_RGB565_DGREEN COLOR_RGB565_DCYAN
*COLOR_RGB565_MAROON COLOR_RGB565_PURPLE COLOR_RGB565_OLIVE COLOR_RGB565_LGRAY
*COLOR_RGB565_DGRAY COLOR_RGB565_BLUE COLOR_RGB565_GREEN COLOR_RGB565_CYAN
*COLOR_RGB565_RED COLOR_RGB565_MAGENTA COLOR_RGB565_YELLOW COLOR_RGB565_ORANGE
*COLOR_RGB565_WHITE
*/
//MP3 UART
#if (defined(ARDUINO_AVR_UNO) || defined(ESP8266)) // Using a soft serial port
#include <SoftwareSerial.h>
SoftwareSerial softSerial(/*rx =*/4, /*tx =*/5);
#define FPSerial softSerial
#else
#define FPSerial Serial2 //TX - Pin17 D10; RX - Pin16 D11
#endif
DFRobotDFPlayerMini myDFPlayer;
//I2C
#define I2C_KEYPAD_4x3 43
const uint8_t KEYPAD_ADDRESS = 0x20;
I2CKeyPad keyPad(KEYPAD_ADDRESS);
uint32_t start, stop;
uint32_t lastKeyPressed = 0;
bool no_print1 = true;
bool no_print2 = true;
bool no_print3 = true;
bool no_print4 = true;
DFRobot_EnvironmentalSensor environment(/*addr = */SEN050X_DEFAULT_DEVICE_ADDRESS, /*pWire = */&Wire);
// defines variables
long duration;
int distance;
//#define bluePin D7
//#define redPin D2
//#define trigPin D5
//#define echoPin D12
#define mp3txPin D10 //17
#define mp3rxPin D11 //16
#define lockPin D7
#define MAX_RANG (520)//the max measurement vaule of the module is 520cm(a little bit longer than effective max range)
#define ADC_SOLUTION (1023.0)//ADC accuracy of Arduino UNO is 10bit
#define sensityPin A0 // select the input pin
float dist_t, sensity_t;
void setup() {
//Serial.begin(115200);
Serial.begin(9600);
Serial.println("start");
screen.begin();
//Set font size to 4, font range 1-4
screen.setTextSize(2.5);
//Set screen color
screen.fillScreen(COLOR_RGB565_BLACK);
screen.setFont(&FreeSansBold9pt7b);//Set font to FreeMono12pt7b
screen.setRotation(3);
screen.setCursor(/*x=*/0,/*y=*/30);
//Set text color
screen.setTextColor(COLOR_RGB565_YELLOW);
//Set to text auto-wrap mode, true=Auto-wrap, false=No auto-wrap
screen.setTextWrap(true);
Serial.println("start done");
#if (defined ESP32)
FPSerial.begin(9600, SERIAL_8N1, /*rx =*/mp3rxPin, /*tx =*/mp3txPin);
#else
FPSerial.begin(9600);
#endif
Serial.println();
Serial.println(F("DFRobot DFPlayer Mini Demo"));
Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)"));
if (!myDFPlayer.begin(FPSerial, /*isACK = */false, /*doReset = */true)) { //Use serial to communicate with mp3.
Serial.println(F("Unable to begin:"));
Serial.println(F("1.Please recheck the connection!"));
Serial.println(F("2.Please insert the SD card!"));
}
else {
Serial.println(F("DFPlayer Mini online."));
}
myDFPlayer.volume(20); //Set volume value. From 0 to 30
//myDFPlayer.loop(1); //Play the first mp3Wire.begin();
//I2C keypad
Wire.setClock(400000);
if (keyPad.begin() == false)
{
Serial.println("\nERROR: cannot communicate to keypad.\nPlease reboot.\n");
while(1);
}
keyPad.setKeyPadMode(I2C_KEYPAD_4x3);
//pinMode(trigPin, OUTPUT); // Sets the trigPin as an Output
//pinMode(echoPin, INPUT); // Sets the echoPin as an Input
pinMode(sensityPin, INPUT);
pinMode(lockPin, OUTPUT); //set Lock
}
void loop(){
char keys[] = "123A456B789C*0#DNF"; // N = NoKey, F = Fail
char keyin[5];
char *pos;
uint8_t cnt;
unsigned long timer = millis();
int term = random(p_rows);
int color = COLOR_RGB565_LGRAY;
int song = 0;
bool empty = false;
screen.fillScreen(COLOR_RGB565_BLACK);
//screen.drawLine(screen.width, 30, 0, 30, color);
//1. Print welcome message
while (!keyPad.isPressed()) {
//screen.fillScreen(COLOR_RGB565_BLACK);
//if(millis() - timer > 20000) {
// timer = millis();
//myDFPlayer.next();
//myDFPlayer.play(1);
//myDFPlayer.play(56);
//delay(10);
//}
//myDFPlayer.playLargeFolder(2, 999);
//myDFPlayer.playFolder(1, 1);
//for (int cnt = 1; cnt <= 10; cnt++) {
//screen.setCursor(/*x=*/0,/*y=*/35);
//screen.setTextColor(COLOR_RGB565_YELLOW);
//screen.println(cnt);
//screen.println();
// myDFPlayer.playFolder(2, cnt);
// Serial.print(cnt);
// delay(3000);
//}
//myDFPlayer.play(1);
screen.setCursor(/*x=*/0,/*y=*/50);
screen.setTextColor(COLOR_RGB565_YELLOW);
screen.println("WELCOME TO THE LITTLE FREE STEM LIBRARY");
//myDFPlayer.playFolder(1, 1);
if(millis() - timer > 3000) {
timer = millis();
if (song == 0) {
myDFPlayer.playFolder(1, 1);
song = 1;
} else {
myDFPlayer.playFolder(1, 6);
song = 0;
}
}
//delay(3000);
screen.println();
screen.setTextColor(color);
//screen.setTextWrap(true);
//screen.setTextSize(1);
screen.println("Press any key to start");
//myDFPlayer.playFolder(1, 6);
//delay(3000);
}
//screen.fillScreen(COLOR_RGB565_BLACK);
//screen.setCursor(/*x=*/0,/*y=*/25);
//screen.println("Let's start");
//myDFPlayer.next(); //Play next mp3 every 3 second.
screen.fillScreen(COLOR_RGB565_BLACK);
screen.setCursor(/*x=*/0,/*y=*/50);
screen.setTextColor(COLOR_RGB565_LGRAY);
screen.println("please answer the following question to unlock");
myDFPlayer.playFolder(1, 2);
delay(3000);
//myDFPlayer.next(); //Play next mp3 every 3 second.
//myDFPlayer.play(57);
screen.fillScreen(COLOR_RGB565_BLACK);
screen.setTextColor(COLOR_RGB565_GREEN);
screen.setCursor(/*x=*/0,/*y=*/30);
//screen.println(term);
screen.println("In which year did ");
myDFPlayer.playFolder(1, 3);
delay(2000);
screen.setTextColor(COLOR_RGB565_LGRAY);
screen.println(p_2d[term]);
//for (int i = 1; i <=46; i++){
// screen.println(i);
// myDFPlayer.playFolder(3, i);
// delay(3000);
//}
myDFPlayer.playFolder(3, (term+1));
delay(2000);
screen.setTextColor(COLOR_RGB565_GREEN);
screen.print("become President?");
myDFPlayer.playFolder(1, 4);
delay(2000);
Serial.println(p_year[term]);
screen.println();
screen.println();
screen.setTextColor(COLOR_RGB565_CYAN);
screen.println("Please enter the year and the '#' sign at the end, ex: 1948#");
myDFPlayer.playFolder(1, 5);
//delay(6000);
char buf[5];
int result = readKeyPadUntil('#', buf, 5, 100000);
if (result == 0)
{
Serial.println(buf);
//screen.fillScreen(COLOR_RGB565_BLACK);
screen.fillScreen(COLOR_RGB565_BLACK);
screen.setCursor(/*x=*/0,/*y=*/50);
screen.println(buf);
//if(strcmp(buf, "1234") == 0) {
if(strcmp(buf, p_year[term]) == 0) {
//while (Serial.available ()==0){}
screen.setTextColor(COLOR_RGB565_GREEN);
myDFPlayer.playFolder(1, 7);
screen.println("YEAH!!! CORRECT!!!");
screen.println("UNLOCKED!!");
//unlock
digitalWrite(lockPin, HIGH);
delay(20000);
digitalWrite(lockPin, LOW);
//myDFPlayer.playFolder(1, 6); //please close the door once you are done
delay(30000);
} else {
screen.setTextColor(COLOR_RGB565_RED);
myDFPlayer.playFolder(1, 8);
screen.print("Wrong!! Please try again!!");
delay(2000);
}
//adhoc codes
//check Ambient light range: 0~120 klx
//Print the data obtained from sensor
Serial.println("-------------------------------");
Serial.print("Temp: ");
Serial.print(environment.getTemperature(TEMP_F));
Serial.println(" ℉");
Serial.print("LuminousIntensity: ");
Serial.println(environment.getLuminousIntensity());
Serial.println("-------------------------------");
//check if library is empty
// read the value from the sensor:
sensity_t = analogRead(sensityPin);
dist_t = sensity_t * MAX_RANG / ADC_SOLUTION;//
Serial.print(dist_t,0);
Serial.println("cm");
if (dist_t > 24) {
empty = true;
myDFPlayer.playFolder(1, 9);
screen.fillScreen(COLOR_RGB565_BLACK);
screen.setCursor(/*x=*/0,/*y=*/50);
screen.setTextColor(COLOR_RGB565_YELLOW);
screen.println("Refill!! Library is empty!!");
Serial.println("Library is empty");
delay(5000);
}
else {
empty = false;
}
}
if (result == -1)
{
Serial.print("FAILURE: ");
Serial.println(buf);
}
if (result == -2)
{
Serial.print("TIMEOUT: ");
Serial.println(buf);
}
if (result == -3)
{
Serial.print("OVERFLW: ");
Serial.println(buf);
}
//delay(1000);
}
//
// until = end character
// buffer = buffer to fill
// length = length of buffer (including '\0'
// timeout = timeout in milliseconds
// returns 0 = success
// -1 = keyPad fail
// -2 = timeout
// -3 = buffer overflow
int readKeyPadUntil(char until, char * buffer, uint8_t length, uint16_t timeout)
{
char keymap[19] = "123A456B789C*0#DNF"; // ... NoKey Fail }
uint8_t bufferIndex = 0;
uint32_t start = millis();
// empty buffer
buffer[bufferIndex] = 0;
while (true)
{
// while no key is pressed wait
while (keymap[keyPad.getKey()] == 'N')
{
delay(1);
yield();
if (millis() - start > timeout) return -2;
}
// get the key pressed
uint8_t raw = keyPad.getLastKey();
// process key pressed
uint8_t key = keymap[raw];
// handle end conditions
if ( key == until) return 0;
if ( key == 'F') return -1; // failed to read;
if (bufferIndex == length) return -3;
// add key to buffer
buffer[bufferIndex++] = key;
buffer[bufferIndex] = 0;
// while key is pressed wait
while (keymap[keyPad.getKey()] == key)
{
yield();
if (millis() - start > timeout) return -2;
}
}
}
The article was first published in hackster, November 20, 2023
cr: https://www.hackster.io/d1-coders/little-free-stem-library-e5345e
author: Darren Shen&Evelyn Shen