Inheritance

I recently made my first class/library found here: Yet another button library

Now that the inputs were taken care of. I started on the outputs of my current project, controlling the windows, locks and latches on my car doors.
With 3 types of outputs and needing at least a couple of instances of each it seemed that derived classes were in order. I tackled the latches first as they were the simplest. I’m happy to report that it is working beautifully. code posted below.

Now for my problem. For both my lock and window functions(which are yet to be coded) I will need 2 buttons for each instance and my googling skills have not uncovered how to create 2 instances of the base class for each instance of a derived class. I could create separate objects for up/down or lock/unlock but that seems like a bodge.

Also I’ve been studying pointers and I’ve figured out how to use them But not really why or where I should use them. If you see anywhere in this sketch where they would be useful I would appreciate a clue.

Any other criticisms also welcome.

// ****** PINS ******
const uint8_t LATCH_BUTTON_PIN = 9;
const uint8_t LATCH_SOL_PIN = 13;
const uint8_t PARK_SENSOR_PIN = 8;

// ****** GLOBALS ******


// ******************  BEGIN DoorButton Class  ************************************

class DoorButton
{
  public:
    DoorButton(const uint8_t buttonPin);
    void buttonParameters(uint32_t interval, uint8_t pressIntervals, uint8_t releaseIntervals, uint32_t quickPressThreshold);
    
  protected:
    uint8_t updateButton(void);
    uint32_t _currentTime;
    enum ButtonStates_t
    {
      NO_CHANGE,
      PRESS,
      QUICK_RELEASE,
      LONG_RELEASE
    };

  private:
    const uint8_t _MIN = 0;
    const uint8_t _MAX = 7;
    ButtonStates_t _buttonState;

    uint8_t _buttonPin;
    bool _buttonIsNowPressed = false;

    uint8_t _buttonBuffer;  // ring buffer to store last 8 buttonReads
    uint8_t _pressDebounceMask = 1; // default value
    uint8_t _releaseDebounceMask = 2;  // default value

    uint32_t _intervalStartTime;  // button read interval Timer
    uint32_t _interval = 10;  // default value

    uint32_t _pressStartTime;  // press length Timer
    uint32_t _quickPressThreshold = 350;  // default value
};  // END DoorButton

DoorButton :: DoorButton(const uint8_t buttonPin)
  : _buttonPin (buttonPin)
{
}  // END constructor


void DoorButton :: buttonParameters(uint32_t interval, uint8_t pressIntervals, uint8_t releaseIntervals, uint32_t quickPressThreshold)
{
  _interval = interval;
  _quickPressThreshold = quickPressThreshold;
  pressIntervals = constrain (pressIntervals, _MIN, _MAX); // Acceptable values = 0(no debounce) - 7(max debounce)
  releaseIntervals = constrain (releaseIntervals, _MIN, _MAX);  // Acceptable values = 0(no debounce) - 7(max debounce)
  _pressDebounceMask = (1 << (pressIntervals + 1)) - 1;
  _releaseDebounceMask = (7 - releaseIntervals);
}  // END buttonParameters()


uint8_t DoorButton :: updateButton(void)
{
  _buttonState = NO_CHANGE;

  if (_currentTime - _intervalStartTime >= _interval)  // End button read interval Timer
  {
    _intervalStartTime += _interval;  // Reset interval Timer
    _buttonBuffer = ( _buttonBuffer << 1) + (!digitalRead (_buttonPin));  // discard oldest read and get new read

    if (_buttonIsNowPressed == false)
    {
      if ((_buttonBuffer & _pressDebounceMask) == _pressDebounceMask)  //  press debounce
      {
        _buttonIsNowPressed = true;
        _buttonState = PRESS;  // confirmed button press
        _pressStartTime = _currentTime;  // Start press length timer
      }
    }
    else
    {
      if ((_buttonBuffer << _releaseDebounceMask) == 0)  //  release debounce
      {
        _buttonIsNowPressed = false;

        if (_currentTime - _pressStartTime <= _quickPressThreshold)  // End press length Timer
        {
          _buttonState = QUICK_RELEASE;  // quick press
        }
        else
        {
          _buttonState = LONG_RELEASE;  // long press
        }
      }
    }
  }
  return _buttonState;
}  // END updateButton

// ******************  END DoorButton Class  ************************************

// ******************  BEGIN DoorLatch Class  ***********************************

class DoorLatch  : public DoorButton
{
  public:
    DoorLatch(const uint8_t latchPin, const uint8_t latchButtonpin);
    void updateLatch(void);

  private:
    uint8_t _latchPin;
    bool _latchActive;

    //    uint32_t _currentTime;
    uint32_t _startLatchTime;  // Latch active Timer
    const uint32_t _latchInterval = 500;
    const uint32_t _latchReactivationDelay = 250;
};  // END DoorLatch

DoorLatch :: DoorLatch(const uint8_t latchPin, const uint8_t latchButtonpin)
  : DoorButton(latchButtonpin), _latchPin (latchPin)
{
}  // END constructor

void DoorLatch :: updateLatch(void)
{
  _currentTime = millis();

  if (_latchActive)
  {
    if (_currentTime - _startLatchTime >= _latchInterval)
    {
      digitalWrite (_latchPin, LOW);
      if (_currentTime - _startLatchTime >= _latchInterval + _latchReactivationDelay)  // prevents new activation for a period of time after current activation ends
      {
        _latchActive = false;
      }
    }
  }
  else
  {
    if (updateButton() == PRESS)
    {
      digitalWrite (_latchPin, HIGH);
      _startLatchTime = _currentTime;
      _latchActive = true;
    }
  }
}  // End updateLatch

// ********************  END DoorLatch Class  ***********************************

// ****** OBJECTS ******
DoorLatch DRF_Latch (LATCH_SOL_PIN, LATCH_BUTTON_PIN);

void setup()
{
  Serial.begin (9600);
  DRF_Latch.buttonParameters (10, 0, 5, 350); // Interval ms, number of intervals for press debounce(Min/Max = 0/7), number of intervals for release debounce(Min/Max = 0/7, quick press threshold ms)
  pinMode (LATCH_BUTTON_PIN, INPUT_PULLUP);
  pinMode (LATCH_SOL_PIN, OUTPUT);
  pinMode (PARK_SENSOR_PIN, INPUT_PULLUP);
}  // END setup()


void loop()
{
  if(digitalRead (PARK_SENSOR_PIN))
  {
    DRF_Latch.updateLatch();
  }
}  // END loop()

If a doorlatch uses two buttons then it isn't really a more specialized version of a button. It is something new entirely which just happens to use some buttons. So it probably shouldn't inherit from button. It should be it's own base class and have two button members.

An example of where you might use inheritance would be if you had some buttons that also had lights and you have to toggle the indicator lights when the button is pressed. So there you would inherit from button so you don't have to rewrite how to be a button and just add the additional functionality to each button.

If a doorlatch uses two buttons then it isn't really a more specialized version of a button. It is something new entirely which just happens to use some buttons.

Ok, This agrees with what I've studied. I didn't realize a second button would make that much difference.

It should be it's own base class and have two button members

This sounds like code redundancy. Tho I'm likely misunderstanding your meaning.

Hutkikz:
Ok, This agrees with what I’ve studied. I didn’t realize a second button would make that much difference.
This sounds like code redundancy. Tho I’m likely misunderstanding your meaning.

if you think about a class as a container, then your new object (you said lock and window) will contain two buttons…

my car has four doors… kind of thing.

cars aren’t derived from doors

It seems like you should use slightly more generic names for your methods. Instead of updateButton() why not just call it update()? Then both your doorlatch and buttons can look the same to the outside code. Just call instanceName.update() on each one as required.

I wrote up a thingo that you might want to have a look at -

ArduinoTheOOWay.html

In that page, I demonstrate building things using inheritance and using composition by aggregation (not sure what to call it) and by reference. The example I'm working with is one of the threads on this board, so it's kinda a real-world example.

If both your lock and your window have two buttons, and much of the behaviour is identical (open/close), then yes you'd make a class that has two button objects in it, and you'd either subclass that for 'lock' and 'window' or you'd use an adapter interface. If the door and window behaviour are identical, then there's no need to subclass.

Another possibility is an event queue model. There's plenty of ways to skin a cat.

@BulldogLowell
It's starting to sink in, derived duh! :grin:
Actually I'm just getting back to a project you were helping me with 2 yrs ago.http://forum.arduino.cc/index.php?topic=391209.0

@MorganS
There is an updateButton and a updateLatch. updateButton is not available in the main sketch(protected). I guess reading @PaulS. posts drilled into me the value of explicit naming. And now that I'm using notepad++ long names are a lot easier.

@PaulMurrayCbr
I have looked at that page several times, guess it's time to look again.

make a class that has two button objects in it

That's what I'm looking for! looking at friend classes now. Am I on the right track?

Pointers are really bugging me. I've learned how to use them, but I can't see where they would be useful in this sketch.

Yes, you could use a friend class but I don't think that's going to be a good idea here. The button should be self-contained. There's a defined interface for how to use a button. The window and door classes should not be digging into the button like friends. They should be treating the button as a black box with a defined interface.

Doors and windows will have upButton and downButton. Those classes could be replaced by anything that behaves like a button. Maybe it's a button on a web page. They should not be doing anything inside the button class.

Pointers are useful when you're trying to tell another function about an object without copying the entire object every time. "Hey you! Here's my button! Do something with it!" Then you can hand that function a pointer to a Button object, which might be your upButton or your downButton. The function doesn't know which one. It never gets to see the name of the button.

If you are getting into the OO features like inheritance then pointers are less useful. Much of the stuff that you would use pointers for is done by the objects anyway.

Hutkikz:
This sounds like code redundancy. Tho I'm likely misunderstanding your meaning.

I think you are.

You've written button code. Now there are two ways to use it to save you from having to write button code again. If you have some object that uses buttons then it can have members that are button objects. It doesn't need any code to read the button or work the button because that is all part of the button class.

The second case is if you have some new object that is some kind of button. It can inherit the button class. I like the java term "extend" the class. Add some more functionality to the button. But it still is a button of some kind.

There's a fine line and there's a whole study of all the grey area cases where you can really use both.

But in your case, a latch is not a button. It is not like a button. It is a system that has some buttons in it. So it shouldn't be represented as some sort of button but as some object that has two buttons.

looking at friend classes now. Am I on the right track?

No, you don't need a friend for this. That's a whole separate ball of wax. Here, you just need the latch to have two button objects as members.

.... More to come

Say your button class looks like this:

class Button {
     
       byte pin;
       boolean state;
       
       Button(byte b):pin(b), state(false){};
       boolean checkButton();
};

Or whatever, I'm just saying you have some class that models a button.

Then you have a latch. A latch has several things. It has some sort of solenoid that makes it close or open. And it might have a button or two to work it. In this case, two buttons, one to close and one to open. Now we need to model that whole behavior.

Let's say we have another class sitting somewhere that models the solenoid itself. Let's say there's a solenoid class somewhere that has a .close() and .open() methods and a constructor that takes a pin number. Yes I see that we'd need more but I'm just illustrating.

Now we can have our DoorLock class.

class DoorLock {
    

     Solenoid sol;
     Button lock;
     Button unlock;

     DoorLock(byte sPin, byte lPin, byte uPin):sol(sPin), lock(lPin), unlock(uPin){};

     void handleLocks() {
           if (lock.checkButton()){
                   sol.close();
           }
           if (unlock.checkButton()){
                   sol.open();
           }
      }
};

Or whatever, but you get the picture how the lock system uses a solenoid and two buttons and has whatever code to use them together.

It is possible to make the DoorLock inherit the function of a button. You could even have an extra Button object as a member. But as soon as you do it starts to smell like bad code. A Door Lock isn't a button. It has a button.

Here's another example. I'm working on the arm controller for my robot tonight. It has a Joint class that models each joint of the arm. Each joint is essentially a servo with some extra functionality to make it work in an arm. So Joint inherits Servo since each Joint IS a Servo.

But the Arm_Class does not inherit Servo. The arm is not a Servo. The arm HAS some Servos.

I think I’m starting to understand. your talking about composition right? And for that I just need to create 2 instances of DoorButton within the lock class and that’s it.

I’ll give a shot tomorrow, right now it’s time I crashed.
Thanks All

Well “tomorrow” turned into a very busy week so I just finally got back on this.
So far I have converted my DoorLatch class to use composition instead of inheritance. Seems to be working, but I still am getting warnings I don’t know how to fix(see pic below).

I also am not sure of the best way to deal with the DoorButton::doorParameters() method. I’d rather not pass that many parameters thru the constructor, but maybe that’s the way to go. The only other way I can think of is to write a method in the DoorLatch class to pass them to DoorButton::doorParameters().

Of course if you have any other critiques I’d love to see them.
Thanks
ERRORS.jpg

// Example Sketch




// ****** PINS ******
const uint8_t LATCH_BUTTON_PIN = 9;
const uint8_t LATCH_SOL_PIN = 13;
const uint8_t PARK_SENSOR_PIN = 8;

// ****** GLOBALS ******


// ******************  BEGIN DoorButton Class  ************************************

class DoorButton
{
  public:
    DoorButton(const uint8_t buttonPin);
    void buttonParameters(uint32_t interval, uint8_t pressIntervals, uint8_t releaseIntervals, uint32_t quickPressThreshold);
    uint8_t updateButton(uint32_t _currentTime);
    enum ButtonStates_t
    {
      NO_CHANGE,
      PRESS,
      QUICK_RELEASE,
      LONG_RELEASE
    };

  protected:

  private:
    const uint8_t _MIN = 0;
    const uint8_t _MAX = 7;

    ButtonStates_t _buttonState;
    uint8_t _buttonPin;
    bool _buttonIsNowPressed = false;
    uint8_t _buttonBuffer;  // ring buffer to store last 8 buttonReads
    uint8_t _pressDebounceMask = 1; // default value
    uint8_t _releaseDebounceMask = 2;  // default value

    uint32_t _intervalStartTime;  // button read interval Timer
    uint32_t _interval = 10;  // default value

    uint32_t _pressStartTime;  // press length Timer
    uint32_t _quickPressThreshold = 350;  // default value
};  // END DoorButton

DoorButton :: DoorButton(const uint8_t buttonPin)
  : _buttonPin (buttonPin)
{
}  // END constructor


void DoorButton :: buttonParameters(uint32_t interval, uint8_t pressIntervals, uint8_t releaseIntervals, uint32_t quickPressThreshold)
{
  _interval = interval;
  _quickPressThreshold = quickPressThreshold;
  pressIntervals = constrain (pressIntervals, _MIN, _MAX);
  releaseIntervals = constrain (releaseIntervals, _MIN, _MAX);
  _pressDebounceMask = (1 << (pressIntervals + 1)) - 1; // Acceptable values = 0(no debounce) - 7(max debounce)
  _releaseDebounceMask = (7 - releaseIntervals);  // Acceptable values = 0(no debounce) - 7(max debounce)
}  // END buttonParameters()


uint8_t DoorButton :: updateButton(uint32_t _currentTime)
{
  _buttonState = NO_CHANGE;

  if (_currentTime - _intervalStartTime >= _interval)  // End button read interval Timer
  {
    _intervalStartTime += _interval;  // Reset interval Timer
    _buttonBuffer = ( _buttonBuffer << 1) + (!digitalRead (_buttonPin));  // discard oldest read and get new read

    if (_buttonIsNowPressed == false)
    {
      if ((_buttonBuffer & _pressDebounceMask) == _pressDebounceMask)  //  press debounce
      {
        _buttonIsNowPressed = true;
        _buttonState = PRESS;  // confirmed button press
        _pressStartTime = _currentTime;  // Start press length timer
      }
    }
    else
    {
      if ((_buttonBuffer << _releaseDebounceMask) == 0)  //  release debounce
      {
        _buttonIsNowPressed = false;

        if (_currentTime - _pressStartTime <= _quickPressThreshold)  // End press length Timer
        {
          _buttonState = QUICK_RELEASE;  // quick press
        }
        else
        {
          _buttonState = LONG_RELEASE;  // long press
        }
      }
    }
  }
  return _buttonState;
}  // END updateButton

// ******************  END DoorButton Class  ************************************

// ******************  BEGIN DoorLatch Class  ***********************************

class DoorLatch 
{
  public:
    DoorLatch(const uint8_t latchPin, const uint8_t parkSensorPin, const uint8_t latchButtonpin);
    void updateLatch(void);

  private:
    DoorButton Button;
    uint8_t _parkSensorPin;
    uint8_t _latchPin;
    bool _latchActive;

    uint32_t _currentTime;
    uint32_t _startLatchTime;  // Latch active Timer
    const uint32_t _latchInterval = 500;
    const uint32_t _latchReactivationDelay = 250;
};  // END DoorLatch

DoorLatch :: DoorLatch(const uint8_t latchPin, const uint8_t parkSensorPin, const uint8_t latchButtonPin)
  : Button(latchButtonPin), _latchPin (latchPin), _parkSensorPin (parkSensorPin)
{
}  // END constructor

void DoorLatch :: updateLatch(void)
{
  _currentTime = millis();

  if (_latchActive)
  {
    if (_currentTime - _startLatchTime >= _latchInterval)
    {
      digitalWrite (_latchPin, LOW);
      if (_currentTime - _startLatchTime >= _latchInterval + _latchReactivationDelay)  // prevents new activation for a period of time after current activation ends
      {
        _latchActive = false;
      }
    }
  }
  else if (digitalRead(_parkSensorPin)) // do not allow activation if not in park
  {
    if (Button.updateButton(_currentTime) == Button.PRESS)
    {
      digitalWrite (_latchPin, HIGH);
      _startLatchTime = _currentTime;
      _latchActive = true;
    }
  }
}  // End updateLatch

// ********************  END DoorLatch Class  ***********************************


// ****** OBJECTS ******

DoorLatch DrF_Latch (LATCH_SOL_PIN, PARK_SENSOR_PIN, LATCH_BUTTON_PIN);

void setup()
{
  Serial.begin (9600);
//  DrF_Latch.buttonParameters (10, 0, 5, 350); // Interval ms, number of intervals for press debounce(Min/Max = 0/7), number of intervals for release debounce(Min/Max = 0/7, quick press threshold ms)
  pinMode (PARK_SENSOR_PIN, INPUT_PULLUP);
  pinMode (LATCH_BUTTON_PIN, INPUT_PULLUP);
  pinMode (LATCH_SOL_PIN, OUTPUT);
}  // END setup()


void loop()
{
  DrF_Latch.updateLatch();  
}  // END loop()

Pictures of text? Really? Post something I can read if you want help. Copy and paste.

Its not an error so it appears I can't copy/paste. please let me know how and I will.

It will copy and paste the same as any error. OR you could just highlight the text and use control-c / control-v That usually works with any program. If you can’t figure out how to copy and paste text then class inheritance may be a bit beyond your abilities.

With an error there is a button to copy that isn't there for warnings and you can't right-click so it isn't the same as for errors.
however control c did work.

C:\Users\Huh\Documents\Arduino\sketch_feb17a\sketch_feb17a.ino: In constructor 'DoorLatch::DoorLatch(uint8_t, uint8_t, uint8_t)':

C:\Users\Huh\Documents\Arduino\sketch_feb17a\sketch_feb17a.ino:118:13: warning: 'DoorLatch::_latchPin' will be initialized after [-Wreorder]

     uint8_t _latchPin;

             ^

C:\Users\Huh\Documents\Arduino\sketch_feb17a\sketch_feb17a.ino:117:13: warning:   'uint8_t DoorLatch::_parkSensorPin' [-Wreorder]

     uint8_t _parkSensorPin;

             ^

C:\Users\Huh\Documents\Arduino\sketch_feb17a\sketch_feb17a.ino:127:1: warning:   when initialized here [-Wreorder]

 DoorLatch :: DoorLatch(const uint8_t latchPin, const uint8_t parkSensorPin, const uint8_t latchButtonPin)

It’s just complaining that you aren’t initializing your class variables in the same order you listed them. Not really one I get too worked up over, but if you want to make it go away just reorder how you have your variables listed in the class.

Hutkikz:
but I still am getting warnings I don't know how to fix(see pic below).

DoorLatch :: DoorLatch(const uint8_t latchPin, const uint8_t parkSensorPin, const uint8_t latchButtonPin)
  : Button(latchButtonPin), _latchPin (latchPin), _parkSensorPin (parkSensorPin)
{
}  // END constructor

you get that warning when initializers are not in the same order as the order that they appear in the class...

try this:

 private:
    uint8_t _latchPin;
    uint8_t _parkSensorPin;
    DoorButton Button;
    bool _latchActive;

Ahh [-Wreorder] got it. Thanks