//////////////////////////////////////////////////////////////////////////////
/// @file main.cpp
/// @author Kai R. ()
/// @brief Exercise program for learning the "Mediator Design Pattern" using
/// the example of a traffic light control system with two traffic lights.
///
/// @date 2024-03-04
/// @version 0.9
///
//////////////////////////////////////////////////////////////////////////////
#include <Arduino.h>
#include <Streaming.h>
Print &cout {Serial};
//////////////////////////////////////////////////////////////////////////////
/// @brief Timerclass for millis()
///
//////////////////////////////////////////////////////////////////////////////
class Timer {
public:
void start() { timeStamp = millis(); }
bool operator()(const uint32_t duration) { return (millis() - timeStamp >= duration) ? true : false; }
private:
uint32_t timeStamp {0};
};
namespace Millis {
using Millis = decltype(millis());
constexpr Millis operator"" _ms(unsigned long long t) { return t; }
constexpr Millis operator"" _sec(unsigned long long t) { return t * 1000_ms; }
constexpr Millis operator"" _min(unsigned long long t) { return t * 60_sec; }
constexpr Millis operator"" _sec(double long t) { return t * 1000_ms; }
constexpr Millis operator"" _min(double long t) { return t * 60_sec; }
} // namespace Millis
namespace TL {
constexpr uint8_t MAX_STATES {5};
constexpr uint8_t BIT_MASK {0x4};
//////////////////////////////////////////////////////////////////////////////
/// @brief Structure for saving the data required for traffic light control
///
//////////////////////////////////////////////////////////////////////////////
struct Data {
const uint32_t duration[MAX_STATES];
const uint8_t ledPin[3];
const uint8_t mask[MAX_STATES];
};
enum class Role : uint8_t { primary, secondary };
enum class Phase : uint8_t { red, redWait, redYellow, green, yellow, wait }; // Definition of TLPhase
Phase &operator++(Phase &phase) {
return phase = (phase > Phase::green) ? Phase::red : static_cast<Phase>(static_cast<uint8_t>(phase) + 1);
};
class Device; // forward declaration
//////////////////////////////////////////////////////////////////////////////
/// @brief Base class for a mediator that enables communication
/// between the two traffic light classes. (Interface)
///
//////////////////////////////////////////////////////////////////////////////
class Mediator {
public:
virtual void notify(Device *sender, uint8_t id) const = 0;
};
//////////////////////////////////////////////////////////////////////////////
/// @brief Base class for the traffic light class. It is used to register
/// at the mediator.
///
//////////////////////////////////////////////////////////////////////////////
class DeviceBase {
public:
DeviceBase(Mediator *mediator = nullptr, uint8_t id = 0) : mediator(mediator), id(id) {}
void setMediator(Mediator *mediator, uint8_t id_) {
this->mediator = mediator;
id = id_;
}
protected:
Mediator *mediator;
uint8_t id;
};
//////////////////////////////////////////////////////////////////////////////
/// @brief The traffic light class required for LED control
///
//////////////////////////////////////////////////////////////////////////////
class Device : public DeviceBase {
public:
Device(Timer &timer_, const Data &data_, Role role_) : timer {timer_}, data {data_}, role {role_} {}
void begin(Phase p = Phase::redWait) {
pNow = p;
for (auto p : data.ledPin) { pinMode(p, OUTPUT); }
if (role == Role::primary) { isBlocked = false; }
}
void switchLight();
void check() {
if (timer(data.duration[idx]) == true) { pNow = pNext; }
}
Phase phase() const { return pNow; }
void block() { isBlocked = true; }
void release() { isBlocked = false; }
private:
Timer &timer;
const Data &data;
Role role;
Phase pNow;
Phase pNext;
uint8_t idx;
bool isBlocked {true};
};
void Device::switchLight() {
if (isBlocked == false) {
timer.start();
idx = static_cast<uint8_t>(pNow);
if (pNow == Phase::red) { mediator->notify(this, id); } // Message to mediator
pNext = ++pNow;
pNow = Phase::wait;
uint8_t i {0};
for (auto p : data.ledPin) {
((data.mask[idx] << i) & BIT_MASK) ? digitalWrite(p, HIGH) : digitalWrite(p, LOW);
++i;
}
}
}
//////////////////////////////////////////////////////////////////////////////
/// @brief The mediator class, which enables communication between the two
/// traffic light classes.
///
//////////////////////////////////////////////////////////////////////////////
class DeviceMediator : public Mediator {
private:
Device *device[2];
public:
DeviceMediator(Device *dev0, Device *dev1) : device {dev0, dev1} {
device[0]->setMediator(this, 0);
device[1]->setMediator(this, 1);
}
//////////////////////////////////////////////////////////////////////////////
/// @brief This controls when a traffic light can end its red phase (release()).
/// This happens reciprocally. When the program starts, the traffic light
/// with ID 1 is released first. It goes from the red phase to green
/// and then back to red. Then the traffic light with ID 0 is released
/// and the process is repeated.
///
/// In the redWait phase, both traffic lights remain red
/// for a while before the switching process starts at the
/// released traffic light.
///
/// If a red phase is not released, the traffic light remains permanently red.
///
/// @param sender
/// @param id
//////////////////////////////////////////////////////////////////////////////
void notify(Device *sender, uint8_t id) const override {
switch (id) {
case 0:
cout << "unblock Traffic Light 1 (top), block Traffic Light 0 (bottom)\n";
device[0]->block(); //
device[1]->release();
break;
case 1:
cout << "unblock Traffic Light 0 (bottom), block Traffic Light 1 (top)\n";
device[0]->release();
device[1]->block();
break;
}
}
};
} // namespace TL
//
// Global variables, objects
//
using namespace Millis;
Timer timer[2]; // Create timer objecte for the traffic lights
// Sequence: red, redWait, redYellow, green, yellow (MAX_PHASE)
// Field1 -> Length of the light phase in milliseconds
// Field2 -> Pins to which the LEDs are connected
// Field3 -> Bit pattern for switching the LEDs
const TL::Data tlData[2] {
{{15_sec, 5_sec, 1.5_sec, 15_sec, 3_sec}, {6, 5, 4}, {4, 4, 6, 1, 2}},
{{15_sec, 5_sec, 1.5_sec, 15_sec, 3_sec}, {10, 9, 8}, {4, 4, 6, 1, 2}}
};
// Create traffic light objects. One traffic light must be the primary (only one!)
TL::Device tLightA {timer[0], tlData[0], TL::Role::primary};
TL::Device tLightB {timer[1], tlData[1], TL::Role::secondary};
// Create Mediator, register traffic lights
TL::DeviceMediator devMed {&tLightA, &tLightB};
//
// Function(s)
//
//////////////////////////////////////////////////////////////////////////////
/// @brief Status machine to control the traffic light circuit
///
/// @param tl traffic light object
//////////////////////////////////////////////////////////////////////////////
void fstm(TL::Device &tl) {
switch (tl.phase()) {
case TL::Phase::red: [[fallthrough]];
case TL::Phase::redWait: [[fallthrough]];
case TL::Phase::redYellow: [[fallthrough]];
case TL::Phase::green: [[fallthrough]];
case TL::Phase::yellow: tl.switchLight(); break;
case TL::Phase::wait: tl.check(); break;
}
}
//
// Main Program
//
void setup() {
Serial.begin(115200);
tLightA.begin(TL::Phase::red);
tLightB.begin(TL::Phase::redWait);
}
void loop() {
fstm(tLightA);
fstm(tLightB);
}
red
red-wait
red-yellow
yellow
green
15 sec
5 sec
1,5 sec
3 sec
15 sec