/*
Solenoid cycle
activate output relay 1
Delay 500ms
Activate output relay 2
Delay 500ms
Activate output relay 3
Delay 1500ms
Deactivate output relay 3
Delay 2500ms
Deactivate output relay 2
The circuit:
pushbutton attached to the pin 2 and to the ground
solenoid #1 attached to pin 4
solenoid #2 attached to pin 5
solenoid #3 attached to pin 6
rework to OOP
older Version:
2022-12-06 https://forum.arduino.cc/t/activate-relays-in-sequence/1061969
*/
// set pin numbers:
const uint8_t buttonPin = A0; // the number of the pushbutton pin
const uint8_t solenoidAPin = 13; // solenoid 1 pin
const uint8_t solenoidBPin = 5; // solenoid 2 pin
const uint8_t solenoidCPin = 6; // solenoid 3 pin
class Output {
const uint8_t pin; // the GPIO
const uint8_t active; // HIGH or LOW active
uint8_t state = 0; // 0 off, 1 on, 2 blinkOnPhase, 3 blinkOffPhase
uint16_t onInterval = 100;
uint16_t offInterval = 300;
uint32_t previousMillis = 0; // time management for the internal blink
public:
Output (uint8_t pin, uint8_t active = HIGH) : pin(pin), active(active) {}
void begin() {
pinMode(pin, OUTPUT);
}
void on() {
digitalWrite(pin, active);
state = 1;
}
void off() {
digitalWrite(pin, !active); // tbd
state = 0;
}
void blink() {
if (state <= 1) state = 3;
}
void update(uint32_t currentMillis = millis()) {
if (state == 2 && currentMillis - previousMillis > onInterval) {
previousMillis = currentMillis;
state = 3;
digitalWrite(pin, !active);
}
if (state == 3 && currentMillis - previousMillis > offInterval) {
previousMillis = currentMillis;
state = 2;
digitalWrite(pin, active);
}
}
};
Output outputA(solenoidAPin);
Output outputB(solenoidBPin);
Output outputC(solenoidCPin);
// inspired by https://isocpp.org/wiki/faq/pointers-to-members
typedef void (Output::*OutputMemFn)();
#define CALL_MEMBER_FN(object, ptrToMember) ((object).*(ptrToMember))
class Sequence {
public:
uint32_t interval; // waiting time before start
Output &output; // the effected output (object)
OutputMemFn outputMemFn; // the member function to be called - the action
};
// a sequence of several steps describing what should happen when
Sequence sequence[] {
// ms, object, &object.memberfunction
{0, outputA, &Output::blink}, // immidiately switch blinking output
{1300, outputB, &Output::on}, // wait some time and then switch on next output
{1400, outputC, &Output::on},
{1500, outputC, &Output::off},
{1600, outputB, &Output::off}
};
constexpr size_t noOfSequence = sizeof(sequence) / sizeof(sequence[0]); // number of elements in the sequence
class Controller {
size_t index = 0; // current step
uint8_t running = 0; // currently running? 0 idle, 1 running, 2 finished
uint32_t previousMillis = 0; // time management
bool isEndless = false; // endloos loop of sequence
Sequence *sequence; // a pointer to the seuqnce to be processed
size_t noOfSequence = 0; // the size of the sequence to be processed
public:
void start() // start sequence
{
if (!running)
{
running = 1;
index = 0;
}
}
void stop() // stop sequence
{
running = 0;
}
/*
bool isRunning() {
return running >= 1 ? true : false;
}
*/
void setSequence(Sequence *sequence, size_t size) // assign the sequence array to be processed
{
this->sequence = sequence;
this->noOfSequence = size;
index = 0; // reset in case of new assignement
stop(); // stop the current processing
}
void update(uint32_t currentMillis = millis()) // call the run function in loop()
{
if (sequence != nullptr && running == 1 && currentMillis - previousMillis > sequence[index].interval)
{
Serial.print(F("execute step ")); Serial.println(index);
previousMillis = currentMillis;
//CALL_MEMBER_FN(test, a[0]) (); // no error!
CALL_MEMBER_FN(sequence[index].output, sequence[index].outputMemFn) ();
index++;
if (index >= noOfSequence)
{
Serial.println(F("end"));
if (isEndless)
index = 0;
else
running = 2;
}
}
}
};
Controller controller; // create the controller
class Button {
const uint8_t pin; // the GPIO of the button
const uint8_t active; // HIGH or LOW active
static constexpr byte debounceDelay = 30; // the debounce time; increase if the output flickers. Static because we only need one value for all buttons
uint8_t lastButtonState = LOW; // last button state
uint32_t previousMillis = 0; // time management debouncing
void (*cbOnPress)(); // gets called if button was pressed "rising"
void (*cbOnRelease)(); // gets called if button was released "falling"
public:
Button(uint8_t pin, uint8_t active = LOW) : pin(pin), active(active) {}
void begin() // call this in setup()
{
if (active == LOW)
pinMode(pin, INPUT_PULLUP);
else
pinMode(pin, INPUT);
}
void setOnPress(void( *cbOnPress)()) // set callback function for on press
{
(*this).cbOnPress = cbOnPress;
}
void setOnRelease(void( *cbOnRelease)()) // set callback function for on release
{
(*this).cbOnRelease = cbOnRelease;
}
void update(uint32_t currentMillis = millis()) // call the run function in loop()
{
byte reading = LOW; // "translated" state of button LOW = released, HIGH = pressed, despite the electrical state of the input pint
if (digitalRead(buttonPin) == active) reading = HIGH; // if we are using INPUT_PULLUP we are checking invers to LOW Pin
if (currentMillis - previousMillis > debounceDelay) // If the switch changed, AFTER any pressing or noise
{
previousMillis = currentMillis;
if (reading == LOW && lastButtonState == HIGH)
{
Serial.println(F("release"));
if (cbOnRelease) cbOnRelease();
}
else if (reading == HIGH && lastButtonState == LOW)
{
Serial.println(F("press"));
if (cbOnPress) cbOnPress();
}
lastButtonState = reading;
}
}
};
Button button(buttonPin);
void cbOnPress() {
controller.start();
}
void cbOnRelease() {
controller.stop();
outputA.off();
outputB.off();
outputC.off();
}
void setup() {
Serial.begin(115200);
button.begin();
button.setOnPress(cbOnPress); // assign the callback for on press
button.setOnRelease(cbOnRelease); // assign the callback for on release
outputA.begin();
outputB.begin();
outputC.begin();
// set initial outputs state
//outputA.on();
controller.setSequence(sequence, noOfSequence); // assign the sequence to be processed by the controller
//controller.start();
}
void loop() {
uint32_t currentMillis = millis();
controller.update(currentMillis); // give the controler some time
outputA.update(currentMillis); // call outputs which might need an update
button.update(currentMillis); // give the switch some time to read
}