Hi, just wanted to demonstrate that it's possible to write code for arduino more naturally using classes and events. The working sketch is here PinEvent - Wokwi ESP32, STM32, Arduino Simulator
Below is the code. It creates a class to represent a DigitalPin. This class has an event OnChange. A Button class is simply a DigitalPin in INPUT_PULLUP mode. An Led is a digital pin in OUTPUT mode. Led "listens" to the Button event and lights up if the button is pressed.
#include "Eventfun.h"
////////////////////////////////////
// definition for DigitalPin class
///////////////////////////////////
// define a class representing digital pin
template<typename T>
class DigitalPin {
protected:
int _pin; // pin number
bool _value; // last value read from the pin
unsigned long _msDebounce;
unsigned long _ms;
bool _debouncing;
public:
// declare event
typedef EventDelegate<T, bool> ChangeEvent;
ChangeEvent OnChange;
public:
// initialize DigitalPin object
DigitalPin(int pin, int pin_mode, int msDebounce = 0)
: _pin(pin), _msDebounce(msDebounce), _debouncing(false) {
pinMode(_pin, pin_mode);
if (pin_mode != OUTPUT) {
_value = digitalRead(_pin);
}
}
// read the pin value and only trigger the event if there is a change
void Update() {
auto value = digitalRead(_pin);
if (value != _value) {
if (_debouncing && millis() - _ms > _msDebounce)
{
_debouncing = false;
_value = value; // update to the new value
OnChange((T*)this, _value); // trigger event
}
else if (!_debouncing) {
_ms = millis();
_debouncing = true;
}
}
}
// accessor for _pin
int Pin() {
return _pin;
}
// for convenience overload () operator to set value
operator()(bool value) {
_value = value;
digitalWrite(_pin, value);
}
// for convenience overload bool operator to return value
operator bool() {
return _value;
}
};
// now that we have a class representing a digital pin
// we can create classes that use digital pin
// a button
class Button: public DigitalPin<Button> {
public:
Button(int pin) : DigitalPin(pin, INPUT_PULLUP, 100) {
}
};
// a LED
class Led: public DigitalPin<Led> {
public:
Led(int pin, Button& button) : DigitalPin(pin, OUTPUT) {
button.OnChange = Button::ChangeEvent(this, &Led::onButtonClick);
}
void onButtonClick(Button* sender, bool value) {
Serial.println("hello");
operator()(!value);
}
};
// it may look like too much for a simple project but when you have many
// interconnected components that need to react to changes and interact with one another
// then using classes with events may simplify your program:
//////////////////
// main program
/////////////////
Button redButton(12);
Button greenButton(2);
Led redLed(redButton.Pin() + 1, redButton);
Led greenLed(greenButton.Pin() + 1, greenButton);
void setup() {
Serial.begin(115200);;
}
void loop() {
redButton.Update();
greenButton.Update();
}
You are not using C++ at all. You use structural programming instead of OOP and you don’t use eventing paradigm. So your example is just and old way of doing things… Of course it still works
Yes it is pretty comprehensive, with debouncing too. But I think the starting point is creating a class that represents an IO pin, then Update it in the main loop and let it trigger events to which other objects subscribe.. I wanted to show that this could be a design for an event driven sketch..
While I applaud the effort and skills.Speaking as someone who believes I am at the tipping point. I think this library is too advanced for beginners and not needed by those advanced enough to utilize it.
Hello @Hutkikz and others - I struggled (and still struggle) with event timers. Is this (below) event timer too complicated for beginners? It shows two events of different periods that meet over a few cycles, then repeat. The simulation is here.
// https://wokwi.com/projects/366159375120487425
// each character on this timing graph represents 50ms.
// interval[0] flops between 0 and 1, interval[1] flops between 2 and 3, simultaneous gets a 4
// interval[0] = 200ms |---*---*---*---*---*---*---*---*---*---*---*---*---*---*---*...
// interval[1] = 500ms |---------*---------*---------*---------*---------*---------*...
// timer = i0*i1=1000ms |---0---1-2-0---1---4---1---0-2-1---0---4---0---1-2-0---1---4...
unsigned long interval[] = {200, 500}; // set two event intervals
unsigned long previousTime[] = {0, 0}; // clear two event timers
unsigned long currentTime; // use for all intervals
bool intervalEvent[2]; // two events
bool intervalOccurred[2]; // two events happened
int intervalCount[2] = {0, 2}; // two event starting values
void setup() {
Serial.begin(115200);
welcome();
}
void loop() {
unsigned long currentTime = millis(); // update "now" time for all intervals and timers
if (currentTime - previousTime[0] >= interval[0]) { // 200ms interval
previousTime[0] = currentTime; // event occurred, set new start time
intervalOccurred[0] = 1; // flag for simultaneous events
}
if (currentTime - previousTime[1] >= interval[1]) { // 500ms interval
previousTime[1] = currentTime; // event occurred, set new start time
intervalOccurred[1] = 1; // flag for simultaneous events
}
if (intervalOccurred[0] && intervalOccurred[1]) { // 200ms and 500ms intervals together
intervalOccurred[0] = 0; // clear flag for 200ms event
if (intervalCount[0] == 0) { // if 200ms count is 0
intervalCount[0]++; // increase count - if it is 0 change it to 1
}
else if (intervalCount[0] == 1) { // if 200 count is 1
intervalCount[0]--; // decrease count - if it is 1 change it to 0
}
intervalOccurred[1] = 0; // clear flag for 500ms event
if (intervalCount[1] == 2) { // if 500 count is 2
intervalCount[1]++; // increase count
}
else if (intervalCount[1] == 3) { // if 500 count is 3
intervalCount[1]--; // decrease count
}
Serial.print(4);
}
if (intervalOccurred[0]) { // 200ms event has occurred
intervalOccurred[0] = 0; // clear flag
if (intervalCount[0] == 0) { // if count is 0
Serial.print(0); // print count
intervalCount[0]++; // increase count
}
else if (intervalCount[0] == 1) { // if count is 1
Serial.print(1); // print count
intervalCount[0]--; // decrease count
}
}
if (intervalOccurred[1]) { // 500ms event has occurred
intervalOccurred[1] = 0; // clear flag
if (intervalCount[1] == 2) { // if count is 2
Serial.print(2); // print count
intervalCount[1]++; // increase count
}
else if (intervalCount[1] == 3) { // if count is 3
Serial.print(3); // print count
intervalCount[1]--; // decrease count
}
}
}
void welcome() {
Serial.println("Each character on this timing graph represents 50ms.");
Serial.println("interval[0] flops 0 and 1 every 200ms |---*---*---*---*---*---*---*---*---*---*---*---*---*---*---*...");
Serial.println("interval[1] flops 2 and 3 every 500ms |---------*---------*---------*---------*---------*---------*...");
Serial.println("interval[0] & interval[1] = 4 at 1000ms |---0---1-2-0---1---4---1---0-2-1---0---4---0---1-2-0---1---4...");
}
Looks like some specific algorithm to solve a task. Maybe even a design pattern for similar algorithms. But it’s something different from event delegates. You can trigger an event via a delegate in your code too though with Eventfun
A delegate is just a fancy function pointer. While a function pointer is a direct memory address a delegate is a wrapper class around the function call. In this case the reason for the wrapper class is mainly to allow not just functions but class methods to be called as callbacks which is not straightforward in C++. And it’s specifically called event delegates just because it’s used for the purpose of setting up callback connections between objects and typically these callbacks are called events because of how they are used. So I think the word event is used in different contexts here..