Passing different structs to a single function

I am developing a program to control the locks and latches on a number of vent doors. It's going well so far and I have completed the latch portion and am about to start on the locks.

I wrote init functions to initialize the I/O pins. The buttonInit() function will work for both locks and latches, but latchInit() as written will only work for latches as it takes a latch object as an input parameter. Since a lockInit() function would be identical it would be nice if I could have one function that could initialize the output pins for either a latch or lock object.

So my question is how do I get a function to accept either a latch or a (future)lock object as an input parameter?

// ================= GLOBAL VARIABLES ================

const uint8_t LATCH1_BUTTON_PIN = 12;
const uint8_t LATCH1_OUTPUT_PIN = 13;
const uint8_t LATCH_DISABLE_SENSOR_PIN = 11;

enum ButtonStates
{
  NO_CHANGE,      // No change in event status
  PRESS,          // Press event begins
  QUICK_PRESS,    // Event has ended as a quick press
  LONG_PRESS      // Event has ended as a long press
};


// ================= STRUCTS ================

struct Button
{
  ButtonStates buttonState;
  uint8_t ButtonPin;
  uint8_t ButtonBuffer = 0;
  bool IsButtonPressed = false;

  uint32_t ButtonTimerStart = 0;
  uint32_t ButtonTimerInterval = 10;

  uint8_t PressIntervals = 1;
  uint8_t ReleaseIntervals = 4;

  uint32_t PressStartTime = 0;
  static const uint32_t PressThresholdTime = 350;
  bool LongPressDetected = false;
};

struct Latch
{
  uint8_t LatchPin;
  bool LatchActive = false;
  uint32_t LatchStartTime = 0;
  static const uint32_t LATCH_ACTIVATION_INTERVAL = 500;
  static const uint32_t LATCH_REACTIVATION_DELAY = 250;
};


// ================= OBJECTS ================

Button Latch1Input;
Latch Latch1Output;


// ================================================================
//                      SETUP FUNCTIONS
// ================================================================

void setup()
{
  pinMode (LATCH_DISABLE_SENSOR_PIN, INPUT_PULLUP);

  buttonInit(Latch1Input, LATCH1_BUTTON_PIN);
  latchInit (Latch1Output, LATCH1_OUTPUT_PIN);
}


void buttonInit (Button &button, const uint8_t &BUTTON_PIN)
{
  button.ButtonPin = BUTTON_PIN;
  pinMode (button.ButtonPin, INPUT_PULLUP);
}


void latchInit (Latch &latch, const uint8_t &LATCH_PIN)/* */
{
  latch.LatchPin = LATCH_PIN;
  pinMode (latch.LatchPin, OUTPUT);
}


// ================================================================
//                             Loop
// ================================================================

void loop()
{
  updateLatch(Latch1Output, Latch1Input);
}


// ================================================================
//                          updateLatch
// ================================================================

void updateLatch (Latch &latch, Button &button)
{
  uint32_t currentTime = millis();

  if (latch.LatchActive)
  {
    if (currentTime - latch.LatchStartTime >= latch.LATCH_ACTIVATION_INTERVAL)
    {
      digitalWrite(latch.LatchPin, LOW);

      if (currentTime - latch.LatchStartTime >= (latch.LATCH_ACTIVATION_INTERVAL + latch.LATCH_REACTIVATION_DELAY))
      {
        latch.LatchActive = false;
      }
    }
  }
  else if (!digitalRead(LATCH_DISABLE_SENSOR_PIN))
  {
    if (updateButton(button, currentTime) == PRESS)
    {
      digitalWrite(latch.LatchPin, HIGH);
      latch.LatchStartTime = currentTime;
      latch.LatchActive = true;
    }
  }
}


// ================================================================
//                           updateButton
// ================================================================

ButtonStates updateButton (Button &button, const uint32_t &CURRENT_TIME)
{
  // ============== set default return value ==============
  button.buttonState = NO_CHANGE; 

  if (CURRENT_TIME - button.ButtonTimerStart >= button.ButtonTimerInterval)
  {
    // ============== reset timer ==============
    button.ButtonTimerStart += button.ButtonTimerInterval;

    // ============== limit intervals to max allowed by the buffer size ==============
    // ============== byte sized buffer allows max 7 intervals, int allows 15 ==============
    //const uint8_t MAX_INTERVALS = 7;
    const uint8_t MAX_INTERVALS = (sizeof(button.ButtonBuffer) <<3) -1; 
    if (button.PressIntervals > MAX_INTERVALS)
    {
      button.PressIntervals = MAX_INTERVALS;
    }
    if (button.ReleaseIntervals > MAX_INTERVALS)
    {
      button.ReleaseIntervals = MAX_INTERVALS;
    }

    // ============== shift oldest read out and add new read ==============
    button.ButtonBuffer = (button.ButtonBuffer << 1) + (!digitalRead (button.ButtonPin)); 

    if (button.IsButtonPressed == false)
    {
      uint8_t pressDebounceMask = (1 << (button.PressIntervals + 1)) - 1;

      if (button.ButtonBuffer & pressDebounceMask)
      {
        button.IsButtonPressed = true;
        button.buttonState = PRESS;
        button.PressStartTime = CURRENT_TIME;
      }
    }
    else // button.IsButtonPressed == true
    {
      uint8_t releaseDebounceMask = (MAX_INTERVALS - button.ReleaseIntervals); // Number of reads to ignore

      if (CURRENT_TIME - button.PressStartTime >= button.PressThresholdTime)
      {
        if (!button.LongPressDetected)
        {
          button.LongPressDetected = true;
        }
      }

      if (button.ButtonBuffer << releaseDebounceMask)
      {
        button.IsButtonPressed = false;

        if (button.LongPressDetected)
        {
          button.buttonState = LONG_PRESS;
          button.LongPressDetected = false;
        }
        else
        {
          button.buttonState = QUICK_PRESS;
        }
      }
    }
  }
  return button.buttonState;
}

I'm thinking you want to pull the Init() functions into your structures (and now call them classes) in the spirit of OOP.

It then might make sense (if there's enough commonality between them) to derive both the button and latch classes from a common abstract class. You could then take advantage of polymorphism with a single array of pointers to objects instantiated from these classes allowing you to call their methods without regard to them being buttons or latches.

Or use a virtual function. The base class allows the derived classes to define their own implementation of the function.

A good example is Serial and Serial1 on the Arduino Due. They use different hardware (one is a UART and the other is a USART) so .write() is actually different on each one. The base class several layers down provides .print() which is a pretty clever function. You don't want to re-write that one for every different hardware. So .print() must use .write() to actually send out each character.

If latch and lock will have the same structure, then just use one struct.

typedef struct
{
    uint8_t Pin;
    bool Active;
    uint32_t StartTime;
} latch_lock;

latch_lock latch1Output;
latch_lock latch2Output;
...
latch_lock lock1;
latch_lock lock2;
...

void updateLatch_Lock (latch_lock &.....

Avoid putting constants in a struct, it simply wastes space (but maybe optimized out). Declare them at the top of your code.

DKWatson:
Avoid putting constants in a struct, it simply wastes space (but maybe optimized out). Declare them at the top of your code.

If the constant is only used internally by the struct / class, then the OOP pillar of encapsulation dictates that it be private within the class. The code shown in the first post actually adheres to this:

static const uint32_t PressThresholdTime = 350;

PressThresholdTime is static to the class. So, there is only one instance of it, not one copy per instances of Button object.

Of course, since Button is defined as a struct and not a class, all its members are public by default. Depends on the application and what coding style you want to use: 'C-style' with 'C-structs' or move towards the OOP capabilities of C++.

So my question is how do I get a function to accept either a latch or a (future)lock object as an input parameter?

If you have one function that takes a pointer, you could completely defeat the intent of C and C++ by declaring the pointer type to be void. Then, you could pass a pointer to ANY kind of struct, or to anything else for that matter.

But, that function would need to know, by some other means, what you think the pointer points to, so it can have completely different code for each kind of pointer you pass it.

To avoid that function getting too large, you'd then refactor your code so you have a function that deals with only one kind of pointer. So, you might as well refactor your code FIRST, and just build separate functions for each kind of pointer.

In which case, you don't need to use a pointer at all, and you don't need the function that takes a pointer and a type and calls the right function.

Thanks All
@PaulS made it clear why I was barking up the wrong tree.

I had originally written this using classes to learn them with but in the end I guess I just liked it better using POD struct's.

@DKWatson Your right a lock and a latch are similar enough to use the same struct further reducing redundancy. I didn't even notice till you pointed it out.