Variable Pulse Generator, interpreting a pattern string

This is something that I haven't seen before on the forums, so I thought I would share the technique.

Its a variable pulse generator, which has been used for the past 12 days to flash some Christmas lights.

I wanted a pulse generator that I could vary without reprogramming. The serial data is used to send a pattern string, and the main
loop interprets each step in the pattern string. The steps in the string can be a delay definition, or a change of output pins,
or termination.

For example, Pd33.1D2D3Dd55.D0Z delays for 33 ms, sets output 1 to on for the delay, sets output 2 to on for the same delay, sets both output 1 and 2 on for 33+55+55 ms, sets outputs to zero, and restarts.

Additional commands allow printing the current pattern in use (hence allowing the pattern to be edited, altered and retried), store the pattern into EEPROM, read back from EEPROM, a help command, and a command to reset the pattern to a known state.

As a safety precaution, the pattern is not read back from EPROM for 10 seconds after startup, and the read back can be aborted by giving a command. I'm not sure in retrospect that this was worth adding, but I did not want to have to reprogram if I managed to store a bad pattern.

#include <EEPROM.h>

/*
  Pulse gen, specify pattern output for two lines,
 pattern can be saved and restored from eeprom

 This version autorestores unless h is put down the serial line
 
 */

const byte ledpin13 =  13; 
const byte ledPin10 =  11;

enum COMMANDS { 
  SETOUT, DELAY, END };

struct CA {
  COMMANDS c;
  union  {
    unsigned  newout;
    unsigned  del;
  };
};


class c_last_delpos
{
public:
  c_last_delpos() { 
    _ca.c = DELAY; 
    _ca.del = 100; 
  } 
  ;
  void set(CA toset) { 
    _ca = toset; 
  };
  CA get() { 
    return _ca; 
  };
private:
  CA _ca;
};

boolean g_printflag = false;
boolean g_pattern_in = false;
boolean g_autorestoreflag = true;
int g_insertpos;
c_last_delpos last_delpos;
int last_del_printed;

int curpapos = 0;
const int pattmax = 50;
CA arr_ca[pattmax];

long previousMillis;        // will store last time LED was updated
long autorestoreMillis;
int g_autorestoreseconds = 10;

void store_pattern()
{
  Serial.print(sizeof(arr_ca));
  byte *bp = (byte *)&arr_ca;
  for (int i = 0; i < sizeof(arr_ca); i++)
  {
    EEPROM.write(0x20 + i, *bp);
    bp ++;
  }
  Serial.println("byte, store done");

}

void restore_pattern()
{
  Serial.print(sizeof(arr_ca));
  byte *bp = (byte *)&arr_ca;
  for (int i = 0; i < sizeof(arr_ca); i++)
  {
    *bp = EEPROM.read(0x20 + i);
    bp ++;
  }
  // could be rubbish in there though, so make sure last ele of array safe
  arr_ca[pattmax - 1].c = END;
  Serial.println(" bytes, retrieve done");

}


void reset_all_pattern_states()
{
  curpapos = 0;
  g_printflag = false;
  g_pattern_in = false;
  arr_ca[0].c = DELAY;
  arr_ca[0].del = 100;

  arr_ca[1].c = SETOUT;
  arr_ca[1].newout = '0';

  arr_ca[2].c = DELAY;
  arr_ca[2].del = 100;

  arr_ca[3].c = SETOUT;
  arr_ca[3].newout = '3';

  arr_ca[4].c = END;
}

void movepatternpointer()
{
  if (g_insertpos < pattmax)
  {  
    g_insertpos++;
  }
  else
  {
    Serial.print("No more pattern space");
  }
  arr_ca[g_insertpos].c = END;  
}


void setup() {
  // set the digital pin as output:
  pinMode(ledpin13, OUTPUT);
  pinMode(ledPin10, OUTPUT); 
  Serial.begin(9600); 
  reset_all_pattern_states();
  previousMillis = millis();
  autorestoreMillis = millis();
}


void loop()
{
  unsigned long currentMillis = millis();

  switch (arr_ca[curpapos].c)
  {

  case END:
    curpapos = 0;
    if (g_printflag)
    {
      Serial.println('Z');
      last_del_printed = 0;
    }
    break;

  case SETOUT:
    digitalWrite(ledpin13, 0x01 & arr_ca[curpapos].newout);
    digitalWrite(ledPin10, 0x02 & arr_ca[curpapos].newout);
    if (g_printflag)
    { 
      Serial.print((char)(arr_ca[curpapos].newout)); 
    }
    curpapos ++;
    break;

  case DELAY:
    if (currentMillis - previousMillis > (unsigned long)arr_ca[curpapos].del) {
      // time to move on
      previousMillis = currentMillis;
      if (g_printflag)
      {
        if (last_del_printed != arr_ca[curpapos].del)
        {
          Serial.print('d'); 
          Serial.print(arr_ca[curpapos].del); 
          Serial.print(".");
          last_del_printed = arr_ca[curpapos].del;
        }
        else
        {
          Serial.print('D');
        }
      }
      curpapos ++;
    }
    break;    
  } // end interpret pattern switch
  
  if (g_autorestoreflag && 
      currentMillis - autorestoreMillis > (unsigned long)1000)
  {
    g_autorestoreseconds -= 1;
    autorestoreMillis = currentMillis;
    Serial.print("To cancel press h, eeprom restore ");
    Serial.println(g_autorestoreseconds);
    
    if (g_autorestoreseconds < 1)
    {
      g_autorestoreflag = false;
      restore_pattern();
    }
    
  }


  if (Serial.available())
  {
    char c = Serial.read();
    switch (c)
    {

    case 'p':
      g_printflag = !g_printflag; // toggle printing flag
      last_del_printed = 0;
      break;


    case 'P':
      Serial.println("Enter new pattern [0123] or dn[nn]. or D or Z");
      g_pattern_in = true;
      g_insertpos = 0;
      arr_ca[g_insertpos].c = END; // always ensure the end position is an END
      break;

    case '0':
    case '1':
    case '2':
    case '3': // these are the bits for setting the digital lines out
      if (g_pattern_in)
      {
        arr_ca[g_insertpos].c = SETOUT;
        arr_ca[g_insertpos].newout = c;
        movepatternpointer();   
      }
      break;

    case 'd':
      if (g_pattern_in)
      {
        char c1 = '0';
        Serial.print(c);
        arr_ca[g_insertpos].c = DELAY;
        arr_ca[g_insertpos].del = 0;
        while(c1 != '.')
        {
          c1 = Serial.read();
          Serial.print(c1);
          if (c1 >= '0' && c1 <= '9')
          {           
            arr_ca[g_insertpos].del *= 10;
            arr_ca[g_insertpos].del += c1 - '0';
          }
        }
        last_delpos.set(arr_ca[g_insertpos]); // save the delay value for later with D command
        movepatternpointer();            
      }
      break;

    case 'D': // repeat last interval setting 
      if (g_pattern_in)
      {
        arr_ca[g_insertpos] = last_delpos.get();
        movepatternpointer();           
      }
      break;   

    case 'Z':
      g_pattern_in = false;
      Serial.println(c);
      break; 

    case 'r':
    case 'R':
      reset_all_pattern_states();
      Serial.println(c);
      break;

    case 'h':
    case 'H':
      g_autorestoreflag = false;
      Serial.println("h help, Ss Store/Retrieve, p print on/off\n P new pattern, Rr reset pattern to preset"); 
      break;

    case 'S':
      store_pattern();
      break;

    case 's':
      restore_pattern();
      break;
    } // end switch
  } // end if Serial.available()


} // end loop