/*
Ονοματεπώνυμο φοιτητή: Μπόντης Στυλιανός
Α.Μ.: ice21390148
Εργασία του μαθήματος Μηχατρονικής
Στο αρχείο αυτό παρουσιάζεται ο κώδικας του raspberry pi pico του simulation του wokwi.
Ο κώδικας είναι η χρήση και ο συνδυασμός διάφορων components με σκοπό την υλοποίηση ενός συστήματος συναγερμού για μία πολυκατοικία
Το σύστημα συναγερμού θα υλοποιεί μία μόνο κατάσταση lockdown. Αυτή ενεργοποιείται, όταν ο χρήστης πατήσει το κουμπί A στο Keypad.
Ο συναγερμός αρχικά βρίσκεται στην κατάσταση ηρεμίας όπου σε αυτήν την κατάσταση ο συναγερμός παρουσιάζει μερικές πληροφορίες στην οθόνη
*/
#include <Keypad.h> // Library used to be able to use the keypad
#include <LiquidCrystal_I2C.h> // Library used to be able to use the LCD 16x2 character screen I2C
#include <Wire.h> // This library is used to define the SCL and SDA pins
#include <RTClib.h> // This library helps us use the RTC. The RTC (Real Time Clock) module helps show the current Time and Date in real life.
#include <math.h>
// RTC defined here:
RTC_DS3231 rtc;
DateTime now; // Current one
DateTime prev; // Previous one
DateTime InitialTime; // The timer begins at this time.
DateTime FinishTime; // The timer ends at this time.
const int duration = 10;
double lastTick;
// SDA and SCL pins. Those are used to connect to the LCD 16x2 I2C display screen.
#define SDA_PIN 4
#define SCL_PIN 5
LiquidCrystal_I2C lcd(0x27,16,2); // Set the LCD address to 0x27 for a 16 chars and 2 line display
// Buzzer and Temperature sensor pins
#define BUZZER_PIN 2
#define TEMP_PIN 28
// PIR Motion Sensor pins(They have to be digital pins)
#define PIR1_PIN 18
#define PIR2_PIN 17
#define PIR3_PIN 16
// PIR State Variables
// When we start, we assume no motion detected
int PIR1STATE = LOW;
int PIR2STATE = LOW;
int PIR3STATE = LOW;
const float distancefromDoor1 = 18.0;
const float distancefromBalconyDoor1 = 18.0;
const float distancefromBalconyDoor2 = 18.0;
static unsigned long lastUltrasonic = 0;
int sensorIndex = 0;
float d1 = 400.0f;
float d2 = 400.0f;
float d3 = 400.0f;
// Ultrasonic sensors Pins(The pins are Trigger and Echo)
// Ultrasonic sensor #1
#define PINTRIG1 26
#define PINECHO1 27
// Ultrasonic sensor #2
#define PINTRIG2 20
#define PINECHO2 19
// Ultrasonic sensor #3
#define PINTRIG3 7
#define PINECHO3 6
// Switch pin to simulate the one window inside the apartment.
#define PINSWITCH 22
// State variable
int CurrentState = 0; // 0 it means that the alarm is on "Calm mode"
String InputPass = ""; // This string will be used as input of the user, when he needs to type the password to disable the alarm.
int Triggered = 0; // This is used in Lockdown state. It checks if a sensor got triggered or not.
// The following are for Keypad:
// Number of rows and columns of the keypad.
const uint8_t KEYPAD_ROWS = 4;
const uint8_t KEYPAD_COLS = 4;
// The keys or characters for each button
char KEYPAD_KEYS[KEYPAD_ROWS][KEYPAD_COLS] = {
{ '1', '2', '3', 'A' },
{ '4', '5', '6', 'B' },
{ '7', '8', '9', 'C' },
{ '*', '0', '#', 'D' }
};
// Pin assignment:
uint8_t ROW_PINS[KEYPAD_ROWS] = { 15, 14, 13, 12 }; // Pins connected to R1, R2, R3, R4 (Red Wire)
uint8_t COL_PINS[KEYPAD_COLS] = { 11, 10, 9 , 8 }; // Pins connected to C1, C2, C3, C4 (Orange Wire)
// Creating the keypad.
Keypad keypad = Keypad(makeKeymap(KEYPAD_KEYS), ROW_PINS, COL_PINS, KEYPAD_ROWS, KEYPAD_COLS);
char key;
const String code = "123"; // The password the user needs to add to disable the alarm. (For now for simplicity purposes it is 123)
// In setup:
// 1) We begin Serial1, rtc and initialize lcd
// 2) We set the type of pin for each sensor (if its input, output or input_pullup)
void setup() {
// put your setup code here, to run once:
Serial1.begin(115200);
//Serial.println("Hello, Raspberry Pi Pico!");
// Buzzer and temperature sensor pins.
pinMode(BUZZER_PIN, OUTPUT); // GP2
pinMode(TEMP_PIN, INPUT); // GP28
// PIR motion detectors pins:
pinMode(PIR1_PIN, INPUT); // GP18
pinMode(PIR2_PIN, INPUT); // GP17
pinMode(PIR3_PIN, INPUT); // GP16
// Ultrasonic sensors pins:
pinMode(PINTRIG1, OUTPUT); // GP26
pinMode(PINECHO1, INPUT); // GP27
pinMode(PINTRIG2, OUTPUT); // GP20
pinMode(PINECHO2, INPUT); // GP19
pinMode(PINTRIG3, OUTPUT); // GP7
pinMode(PINECHO3, INPUT); // GP6
// Switch Pin
pinMode(PINSWITCH, INPUT_PULLUP); // GP22
// Begin the RTC
rtc.begin();
// Adjusting the RTC
if (rtc.lostPower()) {
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
DateTime prev = rtc.now(); // Set the first time right now.
// Initialize the LCD
lcd.init();
// Print a message to the LCD.
lcd.backlight();
//lcd.setCursor(0,0); // Setting cursor on 0,0.
//lcd.print("Hello, world!");
// Printing also to the terminal.
Serial1.println("Alarm started!");
}
// This function edits a number and then displays it.
String displayDigit (int num) {
String number = ""; // The string containing the number is empty.
if (num < 10) {
number = "0" + String(num); // if the number is shorter than 10, add a 0 next to that number (if number is 1 then number in string form becomes 01 instead of 1).
}
else {
number = String(num);
}
return number;
}
// This function edits a float number and then displays it.
// This function is the same as the previous one but instead of an integer, it edits a float.
String displayDigit2(float num) {
String number;
if(num <= -10.0){
number = String(num);
}
else if ((num < 0.0) && (num > - 10.0)){
number = "-0" + String(-num);
}
else if((0.0 <= num) && (num < 10.0)) {
number = "00" + String(num);
}
else{
number = "0" + String(num);
}
return number;
}
// Print the time and date to the serial monitor and LCD.
void PrintCurrentDateAndTime(DateTime a, bool onlyserial){
char line[17];
// This is used to create a string that is up to 16 characters excluding the "\0"
// The string will have the Current Date and Time with the following format:
// <Current_Hour>:<Current_Minute> <Current_Day>-<Current_Month>-<Current_Year>
snprintf(
line,
sizeof(line),
"%02d:%02d %02d-%02d-%02d",
a.hour(),
a.minute(),
a.day(),
a.month(),
a.year()
);
// Printing Current Time and Date on LCD if onlyserial is false
if(!onlyserial){
lcd.print(line);
}
// Printing Current Time and Date on Serial1
Serial1.println(line);
}
// This function is used to receive information from the temperature sensor and print it on the LCD and on the terminal.
// Note: It only prints the temperature, if there has been any change in value to it. Otherwise, its not being printed.
void manageTemperature(){
const float BETA = 3950; // should match the Beta Coefficient of the thermistor
int analogValue = analogRead(TEMP_PIN);
float celsius;
if(analogValue != 0){
celsius = 1 / (log(1 / (1023. / analogValue - 1)) / BETA + 1.0 / 298.15) - 273.15;
}
else {
celsius = -273.15f;
}
// Write on the LCD screen about the temperature.
lcd.print(displayDigit2(celsius));
lcd.print((char)223);
lcd.print("C");
}
// In this state the alarm will print the current time and date on the LCD if its value has changed.
// If the user presses the A button, then the alarm will enter the preparation state.
void CalmState(){
// This will run every second
if (millis() - lastTick >= 1000) { // <- This is used to prevent calling the RTC multiple times.
lastTick = millis();
lcd.setCursor(0,1);
manageTemperature(); // This function is used to manage the temperature on the temperature sensor.
now = rtc.now(); // Get the current date and time.
if(now.minute() != prev.minute()){
Serial1.println("The alarm is currently in calm state mode.");
lcd.setCursor(0,0);
PrintCurrentDateAndTime(now,false); // This function is used to print the Current Date and Time on serial1 and LCD.
prev = now;
}
}
if (key != NO_KEY) {
if(key == 'A'){
Serial1.println("Entering Preparation State");
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Lockdown mode.");
CurrentState = 1;
// Old code for finding FinishTime
/*
int s = (InitialTime.second() + duration) % 60;
int m = (InitialTime.second() + duration) / 60;
int h = (InitialTime.minute() + m) / 60;
int d = (InitialTime.hour() + h) / 60;
int mon = (InitialTime.day() + d) / 60;
int y = (InitialTime.year() + mon) / 60;
FinishTime = DateTime(s, m, h, d, mon, y);
*/
InitialTime = rtc.now();
uint32_t tim = InitialTime.unixtime();
FinishTime = DateTime(tim + duration);
}
}
}
// In preparation State the alarm will start a timer. After the timer hits 0, the alarm will enter Lockdown State.
void PreperationState(){
static uint32_t lastRemaining = UINT32_MAX;
if (millis() - lastTick >= 100) { // <- This is used to prevent calling the RTC multiple times.
lastTick = millis();
now = rtc.now(); // Get the current date and time.
uint32_t n = now.unixtime();
uint32_t f = FinishTime.unixtime();
if(n < f){
uint32_t remaining = f - n;
if(remaining != lastRemaining){ // This exists cause sometimes in the console(and LCD) there would be duplicate numbers in timer
lastRemaining = remaining;
lcd.setCursor(0,0);
lcd.print("Lockdown in: ");
lcd.setCursor(0,1);
lcd.print(displayDigit(f-n));
Serial1.println(displayDigit(f-n));
tone(BUZZER_PIN, 400, 10); // Plays 400Hz tone for 0.010 seconds
}
}
else{
CurrentState = 2;
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Lockdown Started!");
Serial1.println("Alarm is now in lockdown mode.");
now = rtc.now(); // Get the current date and time.
PrintCurrentDateAndTime(now,false);
d1 = 400.0f;
d2 = 400.0f;
d3 = 400.0f;
PIR1STATE = LOW;
PIR2STATE = LOW;
PIR3STATE = LOW;
Triggered = 0;
}
}
}
// This function handles the PIR motion sensor and checks if it detects movement or not.
void CheckPIRMotion(int Pin, int* pinstate){
int pinval = digitalRead(Pin);
// Check if input is HIGH
if (pinval == HIGH){
if (*pinstate == LOW) {
*pinstate = HIGH;
}
}
else{
if (*pinstate == HIGH) {
*pinstate = LOW;
}
}
}
// This function handles the Ultra Sonic Sensor and returns the distance between the sensor and the object.
float CalculateDistance(int pintrig, int pinecho){
digitalWrite(pintrig, HIGH);
delayMicroseconds(10);
digitalWrite(pintrig, LOW);
return (float)pulseIn(pinecho, HIGH, 30000) / 58.0;
}
// This function is used to update all 3 of the ultrasonic sensors that the alarm is going to be using.
void UpdateUltrasonics(){
// These 2 if are used to reduce the lag in the simulation.
// This makes it so that the sensors of the ultrasonic sensors are being read less often
// and only one of them is being read at the time
if(millis() - lastUltrasonic >= 200) {
lastUltrasonic = millis();
if(sensorIndex == 0){
d1 = CalculateDistance(PINTRIG1, PINECHO1);
}
else if (sensorIndex == 1){
d2 = CalculateDistance(PINTRIG2, PINECHO2);
}
else if (sensorIndex == 2){
d3 = CalculateDistance(PINTRIG3, PINECHO3);
}
sensorIndex = (sensorIndex + 1) % 3;
}
}
// In this state the alarm is now in LockDownState. If it's in that state, then the apartment is in lockdown mode and
// in that state the alarm must use its sensors to detect if a person has entered the apartment.
// If it does, the alarm will enter the Triggered State.
// The sensors are:
// 1) 3 ultrasonic sensors for the door and the 2
// 2) 3 PIR motion detectors to check if there is any movement inside the rooms just in case he broke the door
// 3) A switch to simulate the window opening and closing.
void LockDownState(){
// This will run every second
if (millis() - lastTick >= 1000) { // <- This is used to prevent calling the RTC multiple times.
lastTick = millis();
now = rtc.now(); // Get the current date and time.
if(now.minute() != prev.minute()){
Serial1.println("The alarm is currently in Lockdown state mode.");
PrintCurrentDateAndTime(now, false); // This function is used to print the Current Date and Time on serial1 and LCD.
prev = now;
}
}
// Functions that handle the PIR's 1, 2 and 3 motion detection
CheckPIRMotion(PIR1_PIN, &PIR1STATE);
CheckPIRMotion(PIR2_PIN, &PIR2STATE);
CheckPIRMotion(PIR3_PIN, &PIR3STATE);
UpdateUltrasonics();
// this function updates the ultrasonic sensors and make them update the following variables:
// d1 distance between door and ultrasonic sensor #1
// d2 distance between balcony door #1 and ultrasonic sensor #2
// d3 distance between balcony door #2 and ultrasonic sensor #3
int SWITCHOUTPUT = digitalRead(PINSWITCH);
// This code is used for messages on the Serial1 regarding which sensor got triggered.
if(PIR1STATE == HIGH){
Triggered=1;
Serial1.println("PIR sensor #1 got triggered.");
}
else if (PIR2STATE == HIGH){
Triggered=1;
Serial1.println("PIR sensor #2 got triggered.");
}
else if(PIR3STATE == HIGH){
Triggered=1;
Serial1.println("PIR sensor #3 got triggered.");
}
else if(d1 < distancefromDoor1){
Triggered=1;
Serial1.println("Ultrasonic Sensor #1 got triggered.");
}
else if(d2 < distancefromBalconyDoor1){
Triggered=1;
Serial1.println("Ultrasonic Sensor #2 got triggered.");
}
else if(d3 < distancefromBalconyDoor2){
Triggered=1;
Serial1.println("Ultrasonic Sensor #3 got triggered.");
}
else if(SWITCHOUTPUT == LOW){
Triggered=1;
Serial1.println("The window is open.");
}
if(Triggered == 1){
CurrentState = 3;
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Timer: ");
Serial1.println("Alarm triggered! Entering Intruder State. Timer: ");
now = rtc.now(); // Get the current date and time.
PrintCurrentDateAndTime(now, true);
InputPass = "";
InitialTime = now;
uint32_t tim = InitialTime.unixtime();
FinishTime = DateTime(tim + duration);
}
}
// In this state, one of the sensors has been activated and the alarm is triggered.
// The alarm will start beeping and will await code for confirmation it's not an intruder.
// The code will be inputted via the keypad.
// If the code is incorrect or the timer before the code has been inputted has ended, the alarm will enter IntruderState.
void TriggeredState(){
static uint32_t lastRemaining = UINT32_MAX;
// This will run every half second
if (millis() - lastTick >= 100) { // <- This is used to prevent calling the RTC multiple times.
lastTick = millis();
now = rtc.now(); // Get the current date and time.
uint32_t n = now.unixtime();
uint32_t f = FinishTime.unixtime();
if(n < f){
uint32_t remaining = f - n;
if(remaining != lastRemaining){ // This exists cause sometimes in the console(and LCD) there would be duplicate numbers in timer
lastRemaining = remaining;
lcd.setCursor(0,0);
lcd.print("Timer: ");
lcd.print(displayDigit(f-n));
Serial1.println(displayDigit(f-n));
tone(BUZZER_PIN, 400, 10); // Plays 400Hz tone for 0.010 seconds
}
}
else{
lcd.clear();
CurrentState = 4;
Serial1.println("Alarm Hit on: ");
PrintCurrentDateAndTime(now, true);
}
}
if (key != NO_KEY) {
if(key == '*'){
InputPass = "";
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Timer: ");
}
else if (key == '#'){
if(InputPass == code){
// The code is correct
CurrentState = 0;
now = rtc.now();
lcd.setCursor(0,0);
PrintCurrentDateAndTime(now, true);
lcd.setCursor(0,1);
manageTemperature();
Serial1.println("Correct code. Alarm entering Calm state mode.");
}
else {
// The code is incorrect
lcd.clear();
CurrentState = 4;
now = rtc.now();
Serial1.println("Alarm Hit on: ");
PrintCurrentDateAndTime(now, true);
}
}
else{
InputPass += key; // Append character onto the input.
lcd.setCursor(0,1);
lcd.print("*");
}
}
}
// In this state, the intruder has been caught and it will inform the terminal about the intruder's entry.
// In order to return the alarm back to its original state. The B button on the keypad needs to be pressed.
void IntruderState(){
// This will run every second
if (millis() - lastTick >= 1000) { // <- This is used to prevent calling the RTC multiple times.
lastTick = millis();
tone(BUZZER_PIN, 800, 20); // Plays 500Hz tone for 0.010 seconds
}
if (key != NO_KEY) {
if(key == 'B'){ // If the user has pressed the B button, the alarm will return back to calm state.
CurrentState = 0;
now = rtc.now();
lcd.setCursor(0,0);
PrintCurrentDateAndTime(now, false);
lcd.setCursor(0,1);
manageTemperature();
Serial1.println("Alarm entering Calm state mode.");
}
}
}
// The loop function checks the value of the variable: CurrentState
// Depending on its value the loop function will execute a different function.
// If CurrentState = 0 for example, it will execute the Calm State function. This indicates that the alarm is in Calmstate.
// The CurrentState variable can get values between 0-4. If CurrentState variable has an unknown value, then it informs us about it(check the else part of the if statement).
// The Loop function also checks for any input the user has typed via the keypad.
void loop() {
// put your main code here, to run repeatedly:
// Receiving a key from the keypad and playing a sound with the buzzer if the user pressed a button
key = keypad.getKey();
if (key != NO_KEY) {
Serial1.print(key);
Serial1.println(" was pressed on the keypad of the alarm.");
//lcd.setCursor(13,0);
//lcd.print(key);
tone(BUZZER_PIN, 500, 10); // Plays 500Hz tone for 0.010 seconds
}
if(CurrentState == 0){
// The alarm is in Calm State.
CalmState();
}
else if (CurrentState == 1){
// The alarm is in Preparation State.
PreperationState();
}
else if(CurrentState == 2){
// The alarm is in LockDown State.
LockDownState();
}
else if(CurrentState == 3){
// The alarm is in Triggered State.
TriggeredState();
}
else if(CurrentState == 4){
// The alarm is in the Intruder State.
IntruderState();
}
else {
// The alarm is in an Unknown State.
lcd.setCursor(0,0);
lcd.print("UnknownState");
Serial1.println("Error Alarm is in an Unknown state.");
Serial1.println("UnknownState number: " + CurrentState);
}
delay(10);
}