/*
react on "alarm"
increase warnings by time
use one common logic to blink a LED, to honk a horn, to beep an buzzer, to blink a character on the LCD
https://forum.arduino.cc/t/faster-digitalwrite/1097554/55
2023-03-05 by noiasca
don't deleted: will be used on HP
*/
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 20, 4);
const uint8_t alarmStartPin = A0; // "an external event"
const uint8_t alarmStopPin = A1; // "the right code"
const uint8_t ignitionPin = 3; // GPIO to kill the ignition
uint32_t alarmPreviousMillis = 0;
uint32_t alarmState = 0;
// a generic blink class to toggle a blinking effect on an elemet
class Blink {
protected :
uint32_t previousMillis = 0; // time management for "blinking"
uint16_t interval = 500; // default interval
uint8_t state = 0; // 0 off, 1 OnPulsephase, 2 offPulsePhase
bool preStop = false; // flag to stop after next active phase
public:
Buzzer () {}
virtual void hwWrite(uint8_t level); // a low level hwWrite must be implemented in the derived class
void setInterval(uint16_t interval) {
this->interval = interval;
}
void off() {
hwWrite(LOW);
state = 0;
}
//let the last beep run out
void offSmooth() {
if (state == 2)
off();
else
preStop = true;
}
void on() {
hwWrite(HIGH);
previousMillis = millis();
state = 1;
preStop = false;
}
void on(uint16_t interval) {
setInterval(interval);
on();
}
void update(uint32_t currentMillis = millis()) {
if (state && currentMillis - previousMillis > interval) {
previousMillis = currentMillis;
if (state == 1) {
hwWrite(LOW); // switch off - abstracted from the hardware
if (preStop) off(); else state = 2;
}
else {
hwWrite(HIGH); // switch on - abstracted from the hardware
state = 1;
}
}
}
};
class DiscretePin : public Blink {
protected:
const uint8_t pin; // the GPIO to be switched on/off
public:
DiscretePin(uint8_t pin) : pin(pin) {}
void begin() { // discrete output pins need a pinMode in setup()
pinMode(pin, OUTPUT);
}
void hwWrite(uint8_t level) override {
digitalWrite(pin, level); // just a simple pass through for the digitalWrite
}
};
class Buzzer : public Blink {
protected :
const uint8_t pin; // the GPIO for a buzzer
const uint16_t freq; // the frequency to beep
public:
Buzzer (uint8_t pin, uint16_t freq = 600) : pin(pin), freq(freq) {}
void hwWrite(uint8_t level) override {
if (level == LOW)
noTone(pin);
else
tone(pin, freq);
}
};
// an indicator on the display
// for usual you would also handover the reference to a LCD object, but lets keep it simple
class Indicator : public Blink {
protected :
const uint16_t col; // on which column
const uint8_t row; // on which row
char onChar = '-'; // a sign/char to be shown
const char offChar = ' '; // a sign/char if not shown (idle)
public:
Indicator (uint8_t col, uint8_t row, char onChar) : col(col), row(row), onChar(onChar) {}
// output one character to the LCD on the designated place
void hwWrite(uint8_t level) override {
lcd.setCursor(col, row); // col, row
if (level == LOW)
lcd.write(offChar);
else
lcd.write(onChar);
}
// modifiy the on character
void setOnChar(char onChar) {
this->onChar = onChar;
}
};
DiscretePin alarmLed(2); // pin
DiscretePin horn(8); // pin
Buzzer buzzer(7); // pin, frequeny
Indicator alarmIndicator(15, 1, 'S'); // col, row, character
// just to show a "parallel" task
void updateDisplay(uint32_t currentMillis = millis()) {
static uint32_t previousMillis = 0;
if (currentMillis - previousMillis > 100) {
previousMillis = currentMillis;
lcd.setCursor(0, 0);
lcd.print((millis() / 1000.0), 1);
}
}
// ask the key pad if the right code was entered
bool disarmAlarm () {
if (digitalRead(alarmStopPin) == LOW) return true;
return false;
}
// action when alarm should be stopped
void alarmStop() {
alarmLed.off(); // optional offSmooth();
buzzer.offSmooth();
horn.off(); // optional offSmooth();
alarmIndicator.off();
digitalWrite(ignitionPin, LOW); // release ignition killer
}
void alarmFSM(uint32_t currentMillis = millis()) {
switch (alarmState) {
case 0 :
if (digitalRead(alarmStartPin) == LOW) {
alarmState++;
Serial.print(F("silent alarmState=")); Serial.println(alarmState);
alarmPreviousMillis = currentMillis;
alarmLed.on(1000);
alarmIndicator.setOnChar('s');
alarmIndicator.on(400);
}
break;
case 1: // 0..15
if (disarmAlarm()) {
Serial.print(F("alarmOff")); Serial.println(alarmState);
alarmStop();
alarmState = 0;
}
if (currentMillis - alarmPreviousMillis > 15000UL) {
alarmState++;
Serial.print(F("warn alarmState=")); Serial.println(alarmState);
alarmLed.on(500);
buzzer.on(1000);
alarmIndicator.setOnChar('w');
}
break;
case 2: // 15..25
if (disarmAlarm()) {
Serial.print(F("alarmOff")); Serial.println(alarmState);
alarmStop();
alarmState = 0;
}
if (currentMillis - alarmPreviousMillis > 25000UL) {
alarmState++;
Serial.print(F("urgent alarmState=")); Serial.println(alarmState);
alarmLed.on(300);
buzzer.on(500);
alarmIndicator.setOnChar('u');
}
break;
case 3: // 25..30
if (disarmAlarm()) {
Serial.print(F("alarmOff in ")); Serial.println(alarmState);
alarmStop();
alarmState = 0;
}
if (currentMillis - alarmPreviousMillis > 30000UL) {
alarmState++;
Serial.print(F("cut off alarmState=")); Serial.println(alarmState);
alarmLed.on(200);
buzzer.offSmooth();
horn.on(); // instead
alarmIndicator.setOnChar('x');
digitalWrite(ignitionPin, HIGH); // ignition kill
}
break;
case 4: // after
if (disarmAlarm()) {
Serial.print(F("alarmOff in ")); Serial.println(alarmState);
alarmStop();
alarmState = 0;
}
break;
}
}
void setup() {
Serial.begin(115200);
Serial.println(F("Ready"));
lcd.init();
lcd.backlight();
lcd.setCursor(9, 0); // col, row
lcd.print(("Seconds"));
alarmLed.begin();
horn.begin();
pinMode(ignitionPin, OUTPUT);
pinMode(alarmStartPin, INPUT_PULLUP);
pinMode(alarmStopPin, INPUT_PULLUP);
//Serial.println(millis()); // that's just because wokwi is different to a real arduino.
}
void loop() {
uint32_t currentMillis = millis(); // instead of multiple calls of millis() we use the same timestamp for one loop iteration
alarmFSM(currentMillis);
updateDisplay(currentMillis);
// each object needs a tick/timeslice to be able to check if there is something to do
alarmLed.update(currentMillis);
buzzer.update(currentMillis);
horn.update(currentMillis);
alarmIndicator.update(currentMillis);
}
// eof