Event driven programming

I'm thinking of writing some classes to make event-driven coding a bit easier, and I'm wondering if someone else has already done this.

I'm thinking that a Thing would listen to events and be able to fire events. There'd be a root object whose job it is to fire "loop() is being executed", and most of the basic components would listen to that.

The key to the system would be a listener list class. I'm not a C++ programmer, but something like:

class Listener {
  void onEvent(void *event, void *data){}
}

class ListenerList {
    void *data; // stuff that is about this listener

    void fireEvent(void *event) {
       for each of my listeners {
         listener.onEvent(event, data);
       }
    } 

    void addListener(Listener *listener) {}
    void removeListener(Listener *listener) {}

}

But with proper generic types (C++ calls 'em "templates"?)

So a "input button" would listen to the main loop, and would fire button down/button up events. A more complex version would fire click, double-click, click-and-hold and so on (implemented either as a subclass or a network of higher-level listeners). A timer object would listen to the loop and watch millis().

Perhaps a more interesting listener would be a general FSM. That is - finite state machines would be implemented over the top of the move basic event library.

Anyway - has someone already done this?

There is "SerialEvent()" in the current core. It doesn't seem to be very helpful :-(

Excellent idea. It really is a useful model for responding to events related to time and I/O. Don't forget timer events. I've done something like that but am not fully satisfied with the results. I don't feel it is as easy to use as I had hoped, but if you're comfortable with templates and inheritance I'm sure you could deal with it. I'd be happy to share my code if you thought it might be useful. (At least to see some of the pitfalls of the design decisions I made. ;) )

PaulMurrayCbr: I'm thinking of writing some classes to make event-driven coding a bit easier, and I'm wondering if someone else has already done this.

I can't help feeling that this stems from the experience of event-driven-programming on a PC.

What is not at all obvious on a PC is that there is a huge body of "invisible" code watching what is happening so it can "fire" the event calls at the right time.

If you want to do event-driven-programming on an Arduino you will first have to write that "invisible" or "background" code - and debug it thoroughly.

And when it is all done I suspect you will find that it is just using up RAM space and CPU cycles that would be better given over to something useful.

Another constraining feature (IMHO) is that no two people want to do the same thing with their Arduino which means the background code will have to cover a long list of possibilities - making it even bigger and slower.

And don't get me started on the problems for the user when s/he finds that it does not do what s/he thinks it should or if s/he encounters a bug.

The concepts in several things at a time are simple and transparent. Any other approach ultimately just does the same thing with more code.

...R

PaulMurrayCbr:
I’m thinking of writing some classes to make event-driven coding a bit easier, and I’m wondering if someone else has already done this.

I’m thinking that a Thing would listen to events and be able to fire events. There’d be a root object whose job it is to fire “loop() is being executed”, and most of the basic components would listen to that.

The key to the system would be a listener list class. I’m not a C++ programmer, but something like:

Anyway - has someone already done this?

Not just the way you outlined, that I know of. Other ways, yes sort of.

But generally you want to trim code on small machines and avoid going into Microsloth featurism.

I thought I was going to have use for patterning IO code starting this year but it’s not working out that way yet. What it would replace, works. I’d rather see it work on LESS hardware, maybe it will happen.

I did make a start with 1 output class and 1 input class. Haven’t finished more, might get back to it when weather improves and I’m not coughing my head off so much.

// ioclasses library, ongoing work Dec 21, 2014 by GoForSmoke

#ifndef ioclasses16_h
#define ioclasses16_h

class ioblinker
{
private:
  unsigned int startMillis;
  unsigned int waitMillis;
  unsigned char arduPin;
  char port;
  unsigned char pinsToggleMask;

public:
  ioblinker( unsigned int, unsigned char, char, unsigned char );
  void setBlinker( unsigned int wM ); // wM == 0UL turns the blink off
  void startBlinker( void );
  void setPin( unsigned char );
  void runBlinker( void ); // this runs every time loop() runs
  unsigned int getWait( void );
};

class iobutton
{
private:
  unsigned char arduPin;
  unsigned char stateHistory; // bit 0 = now, bit 1 = prev
  unsigned int startMillis;
  unsigned int debounceMillis; 
  char buttonOut; // 5-state as below
  char retButton;

public:
  iobutton( char, unsigned int ); // pin, debounce millis
  void startButton( void );
  void runButton( void );
  char readOutput( void ); // 5-state as below
// 5-state UNDECIDED = -1, OFF = 0, ON = 1, justOFF = 2, justON = 3
};

#endif
// ioclasses library, ongoing work Dec 21, 2014 by GoForSmoke

#include "Arduino.h"
#include "ioclasses16.h"

//    blinker =================================================

ioblinker::ioblinker( unsigned int w, unsigned char a, char p, unsigned char m )
{
  startMillis = 0;
  waitMillis = w; 
  arduPin = a;
  port = p;
  pinsToggleMask = m;
};

void ioblinker::setBlinker( unsigned int w )
{
  waitMillis = w; 
};

void ioblinker::startBlinker( void )
{
  pinMode( arduPin, OUTPUT );
  startMillis = millis();
};

void ioblinker::setPin( unsigned char s )
{
  waitMillis = 0;
  digitalWrite( arduPin, s );
};


void ioblinker::runBlinker( void )
{
  if ( !waitMillis )  return;

  if (( millis() & 0xFFFF ) - startMillis >= waitMillis )
  {
    switch ( port | 32 )
    {
    case 'b' :
      PINB = pinsToggleMask;  // writes 1 to each bit in the PINSB register that will get toggled
      break;
    case 'c' :
      PINC = pinsToggleMask;  // writes 1 to each bit in the PINSB register that will get toggled
      break;
    case 'd' :
      PIND = pinsToggleMask;  // writes 1 to each bit in the PINSB register that will get toggled
      break;
    default :
      waitMillis = 0UL;
    }
    startMillis += waitMillis;
  }
};

unsigned int ioblinker::getWait( void )
{
  return waitMillis;
};

//    end blinker =============================================

//    button ================================================

iobutton::iobutton( char ap, unsigned int dbm )
{
  arduPin = ap;
  debounceMillis = dbm;
  buttonOut = -1;
};

void iobutton::startButton( void )
{
  pinMode( arduPin, INPUT_PULLUP );
}

void iobutton::runButton( void )
{
  stateHistory &= 1;  // clears all but the last read
  stateHistory <<= 1; // shifts last read to bit 1
  stateHistory += digitalRead( arduPin ); // current state to bit 0

  switch ( stateHistory ) // set for INPUT_PULLUP 
  {
  case 0 : // low - low     pressed
  case 3 : // high - high   released
    if ( buttonOut < 0 )
    {
      if ( millis() - startMillis >= debounceMillis )
      {
        if ( stateHistory == 0 )
        {
          buttonOut = 3; // button pressed - just changed
        }
        else
        {
          buttonOut = 2; // button released - just changed
        }
      }
    }
    break;
  case 1 : // high - low   if state change, debounce!
  case 2 : // low - high 
    buttonOut = -1;
    startMillis = millis() & 0xFF;
  }
};

char iobutton::readOutput( void )
{
  if ( buttonOut < 0 )  return buttonOut;
  retButton = buttonOut;
  if ( buttonOut > 1 )  buttonOut -= 2; // see change only once 
  return retButton; 
};

//    end button ============================================

Robin2: I can't help feeling that this stems from the experience of event-driven-programming on a PC. ...

In my case it stems from decades of experience developing embedded systems. However having done that when Windows (and OS/2) came around I was quite comfortable with event driven programming and could work in those environments as well.

If you have, for example, a lot of inputs or perhaps need to do a number of things based on time it is quite useful to abstract out the common parts of the code and get them working correctly and then apply to the entire class of events.

I find it easier to debug it thoroughly once and apply rather than rewrite the same code over and over and over again and have to debug each copy.

On a small memory constrained machine higher level programming techniques are less powerful because of all the memory they chew up, alas - 2k RAM is only 1k pointers to handlers, and that's shared with stack and heap as well. So this is probably going to be most useful on the Mega or Due which have more RAM, than an Uno.

One issue with event-driven frameworks is that loop() won't be called as often, and without adding a pre-emptive multitasking framework that can be a show-stopper for many applications where real-time response is needed.

So perhaps best to think state-machines and loop()-driven coding when resources are constrained - you are closer to the hardware, and its hard to ignore this.

PaulMurrayCbr: I'm thinking of writing some classes to make event-driven coding a bit easier, and I'm wondering if someone else has already done this

You might be interested in an excellent alternative framework called Cosa. There's a thread down here where the author keeps us up-to-date on new features. It's really impressive how many devices and protocols he has implemented.

It's not really templatized; event listeners are derived classes. I was used to "adding a callback", but in RAM-constrained environments, that's not the right approach. The base classes are the "invisible" code that know how to handle system devices. They publish and emit higher-level events in an abstract way. The main loop can be just the event dispatcher.

Cosa is some of the cleanest code I've ever seen, which really helps with optimizations, both speed and size. It's usually faster and smaller than anything created in the Arduino IDE. It's a true OO design in C++, so it's not for beginners, but there are lots of example apps.

Cheers, /dev

Oooh, sounds interesting, I'll try and find time to take a look.