Can somebody help me with "class"?

This may not be the place for it, but…

Let me say up front, I don’t want anybody to write code for me but I need a hand understanding “class” a little better. I’m mostly a hardware guy, but I’ve been coding for a while, and I do OK with straight up C. But I have been wanting to use the class features more, and have a pretty good project that fits.

I grasp that a “class” is much like a struct in C. But it seems to be a very abstract idea for me. I can’t seem to see how a “object” is created. (I think this is one reason I do badly on tests, the questions never seem clear cut enough for me to answer).

Anyway. I have a project that monitors the waste tanks in our RV. There is a sensor that indicates when the tank is full. There (hopefully) will be a sensor that indicates when it’s empty. Each tank has a valve that also has a sensor that indicates that valve is closed. Each valve is driven by a motor, two inputs, one drives the motor to close the valve, the other input drives the motor to close the valve. (bi-directional H-bridge drive, no sensors to indicate motor drive extreme, it just shuts off and you have to reverse power to drive it again)

I’m guessing “tanks” would be a class, since you can create an object of tank.
And I’m guessing the motor/valve combo would also be a class?

And the valve inherit from the tank class?

And the sensors, being just a single digital input, don’t need a class? Or maybe part of the “tank class”?

Or is everything one big class? Like “tank” with the level sensors and valve/motor part of that class?

Any help explaining the why’s of what’s what would be much appreciated.

Is the motor-operated valve used for filling or emptying the tanks?

Das ist “Tank.h”

#pragma once

#include <Arduino.h>

class Tank {
public:
    Tank();
    ~Tank();
    void init(byte pinTankFull, byte pinTankEmpty,byte pinMotorOpenValve, byte pinMotorCloseValve);
    bool getTankFull();
    bool getTankEmpty();
    void openValve();
    void closeValve();

    private:
    bool _tankSensorFull;
    bool _tankSensorEmpty;
    byte _pinTankFull;
    byte _pinTankEmpty;
    byte _pinMotorOpenValve;
    byte _pinMotorCloseValve;
};

Das ist “Tank.cpp”

#include <Arduino.h>
#include <Tank.h>

void Tank::init(byte pinTankFull, byte pinTankEmpty, byte pinMotorOpenValve, byte pinMotorCloseValve)
{
    _pinTankFull = pinTankFull; // Assign the pin numbers to your Arduino Uno, Nano, Mega, ESP32, ESP8266 pins
    _pinTankEmpty = pinTankEmpty;
    _pinMotorOpenValve = pinMotorOpenValve;
    _pinMotorCloseValve = pinMotorCloseValve;

    // I don't know how your sensors work.
    // You might need to change it to pinMode(_pinTankFull, INPUT_PULLUP)
    // if your sensors are normally closed and you want to use the internal pull-up resistors.
    pinMode(_pinTankFull, INPUT);         // Set the pin mode for the tank full sensor
    pinMode(_pinTankEmpty, INPUT);        // Set the pin mode for the tank empty sensor
    pinMode(_pinMotorOpenValve, OUTPUT);  // Set the pin mode for the motor open valve
    pinMode(_pinMotorCloseValve, OUTPUT); // Set the pin mode for the motor close valve

    digitalWrite(_pinMotorOpenValve, LOW);  // Initialize the motor open valve to LOW
    digitalWrite(_pinMotorCloseValve, LOW); // Initialize the motor close valve to LOW
}
bool Tank::getTankFull()
{
    // You need to adjust it: 
    // Depending on whether the sensor is normally closed or normally open.
    // For example, if the sensor is normally closed, you might want to invert the reading:
    // _tankSensorFull = !digitalRead(_pinTankFull);
    _tankSensorFull = digitalRead(_pinTankFull);
    return _tankSensorFull;
}
bool Tank::getTankEmpty()
{
    _tankSensorEmpty = digitalRead(_pinTankEmpty);
    // You need to adjust it,
    // depending on whether the sensor is normally closed or normally open.
    // For example, if the sensor is normally closed, you might want to invert the reading:
    // _tankSensorEmpty = !digitalRead(_pinTankEmpty);
    return _tankSensorEmpty;
}
void Tank::openValve()
{
    // Interlock to prevent simultaneous opening and closing operations.
    digitalWrite(_pinMotorOpenValve, HIGH);
    digitalWrite(_pinMotorCloseValve, LOW);
}
void Tank::closeValve()
{
    // Interlock to prevent simultaneous opening and closing operations.
    digitalWrite(_pinMotorOpenValve, LOW);
    digitalWrite(_pinMotorCloseValve, HIGH);
}

Tank::Tank()
{
    // Here, too, you need to specify the normal state of your sensors in the init method.
    // For example, if the sensors are normally closed, you might want to initialize them as true:
    _tankSensorFull = false;
    _tankSensorEmpty = false;
}
Tank::~Tank()
{
}

Das ist dein Arduino-Programm “main.cpp

#include <Arduino.h>
#include <Tank.h>

// das musst du für dein Projekt anpassen, je nachdem, wie du die Pins auf deinem Arduino Uno, Nano, Mega, ESP32, ESP8266 angeschlossen hast.

const byte pinTankFull = 2;        // Pin für den "Tank voll" Sensor
const byte pinTankEmpty = 3;       // Pin für den "Tank leer" Sensor
const byte pinMotorOpenValve = 4;  // Pin für das Öffnen des Ventils
const byte pinMotorCloseValve = 5; // Pin für das Schließen des Ventils

const byte pinTankFull2 = 6;        // Pin für den "Tank voll" Sensor
const byte pinTankEmpty2 = 7;       // Pin für den "Tank leer" Sensor
const byte pinMotorOpenValve2 = 8;  // Pin für das Öffnen des Ventils
const byte pinMotorCloseValve2 = 9; // Pin für das Schließen des Ventils

// Hier kannst du weitere Tanks hinzufügen, indem du weitere Instanzen der Tank-Klasse erstellst und die entsprechenden Pins zuweist.
Tank MyTank1;
Tank MyTank2;

void setup()
{
    Serial.begin(115200);                                                               // Starte die serielle Kommunikation mit 115200 Baudrate
    MyTank1.init(pinTankFull, pinTankEmpty, pinMotorOpenValve, pinMotorCloseValve);     // Initialisiere den ersten Tank mit den entsprechenden Pins
    MyTank2.init(pinTankFull2, pinTankEmpty2, pinMotorOpenValve2, pinMotorCloseValve2); // Initialisiere den zweiten Tank mit den entsprechenden Pins
    // Hier kannst du weitere Tanks initialisieren, indem du die init-Methode für jede Tank-Instanz aufrufst und die entsprechenden Pins übergibst.
}

void loop()
{
    // Hier kannst du die Logik implementieren, um die Sensoren zu überwachen und die Ventile entsprechend zu steuern.
    // Zum Beispiel:
    if (MyTank1.getTankFull())
    {
        MyTank1.closeValve(); // Schließe das Ventil, wenn der Tank voll ist
    }
    else if (MyTank1.getTankEmpty())
    {
        MyTank1.openValve(); // Öffne das Ventil, wenn der Tank leer ist
    }

    if (MyTank2.getTankFull())
    {
        MyTank2.closeValve(); // Schließe das Ventil, wenn der Tank voll ist
    }
    else if (MyTank2.getTankEmpty())
    {
        MyTank2.openValve(); // Öffne das Ventil, wenn der Tank leer ist
    }

    // Hier kannst du weitere Logik hinzufügen, um andere Tanks zu überwachen und zu steuern.

    delay(1000); // Warte 1 Sekunde bevor die Sensoren erneut überprüft werden
}

1 Like

There are German comments in “main.cpp”; I didn’t feel like translating anymore at 2:48 AM.

Greetings from Duisburg, Germany.

2 Likes

Ah! I love Germany! I used to live there when I was just a tike, Frankfurt (Frankfort? I was just a kid)

Thanks for that, I’ll have to look it over.

The valves are used for emptying out the tanks, sorry I left that out.

And I left out another part, there is a function for filling/flushing one of those three tanks. Valve opens, fills to a set number of gallons/liters, stops filling, then empties out the tank again. (long day)

It’s spelled “Frankfurt”.

Where are you from or do you live?

Since this function applies to only one tank, I wouldn’t include it in the class itself.

Does this cleaning process happen automatically?
Is the volume of water (liters or gallons) used for flushing measured?
In any case, for this specific task, I would simply add a small function in “main.cpp”.

Since the valves are used to empty the tanks, you can’t use the “main.cpp” code exactly as it stands.

But now you have your class, which allows you to conveniently query tank-specific states (like MyTank_XY.getFull and MyTank_XY.getEmpty) and control your motors (valves) accordingly.

I think the example if luckystriker68 is nice, but not great how to use OO.**
**
What I would change is that the Tank structure needs to get a other object that knows how to read tank state, currently it’s now just assumed that digitalRead magically gives the right answer.

so you would get something liket his:

// Knows how to read fuel quantity

class FuelSensor {
   //  code to communicate with hardware and 'knowns' about calibration
};

   //  code that knows how to control motoros, timings etc...
class MotorControl {
}

// Knows how to respond to fuel coantities
class TankControl {  
private:    
  FuelSensor fuelSensor;
  MotorControl motorControl;
public:
  TankControl(FuelSensor sensor, MotorControl control): fuelSensor(sensor), MotorControl(control) {..}
}

The above design they call ‘Composition’ over Inheritance. You could take this a step further how how a motor control or Fuel Sensor get’s it’s values (events, or direct measurement etc…)

In TankControl I would add a state machine that clearly defines what you can do what. This would prevent you from doing mistakes like. Run an engine, but have some values closed, which could clearly be an illegal move. Or when the tank is full, run an engines.
State machines allows you to very clearly defines what you can do in which situation, without making your if/then/else structures to be over complex. Ask a LLM, they are pretty good at it.

If you are in to it, by all means try to prevent blocking code. Don’t delay, but run things at times (FreeRTOS can handle here very well).

Perhaps you want to do 20 measurements (Tank Readings) average this, to get more accurate readings, whole other ‘tasks’ run much slower. If you don’t want to use FreeRTOS, there are also simple routines that can help with this. (Delay is usually a wast of time).

Hope that helps setting up structure..

Oh!

I “cobbled that together” in half an hour.
Of course, I know that (almost) any “delay” is one “delay” too many.

While I find your code example interesting, I think you misunderstood oldmicroguy’s problem.
He simply wanted to see how it could be done using a “class.”
There is no continuous (analog) measurement here—just two limit switches or float switches for “tank full” or “tank empty.”
He states himself that he isn’t very good at programming, yet you come along with this bloated solution.

Regarding the use of an RTOS:
I don’t know a direct English equivalent, but in Germany, we say “using cannons to shoot sparrows” I think you say something like this: “using a sledgehammer to crack a nut”.
An RTOS (Real-Time Operating System) is a specialized operating system designed to execute data and tasks with absolute reliability and predictability under extremely strict timing constraints.
Here, we’re dealing with a number of very primitive tanks, using very primitive limit switches and very primitive motors to actuate valves. The motors don’t even provide feedback on whether the valve has actually opened or closed.
I don’t think there is anything here that is time-critical or requires strict predictability.

But if you want, you can certainly write a doctoral thesis on tank filling and emptying.

You wrote, “Hope that helps setting up structure.”
I think you mostly caused confusion—but you certainly succeeded at that.

Wow, that was a LOT more than I expected.

I am using LVGL for the basic display (Waveshare 7" ESP32c3, I might try to go back to a 7" Elecrow). And everything is running with OTA updates and ESP-NOW running on core 0. (I was going to tackle FreeRTOS, but haven’t got there yet, but I kinda am since it’s running OTA on core 0)

And yes, again I forgot, the flow into the tank is measured. And the process of filling/emptying would be automatic with this project. (hopefully, it’ll keep me from having to go out there in the rain/wind/snow etc… and dump them manually)

I think the timing (of things “tank”) in the project is pretty well handled by either the lvgl timer (since that’s running anyway) or the measurement units sending a measurement back to the main display via ESP-NOW). So that’s basically interrupt driven when a measurement comes in. And that is handled by the measurement unit, measuring the tank every couple of minutes.

So, you’re basically saying that anything that has anything to do with the tank, digital inputs, outputs, “devices” should be included into the “tank class”. Then all, or almost all, functions having to do with the tank are included into the class. I don’t know if I would have ever have seen it that way, maybe after much frustration, trial and error.

I think my issue is that I have only recently discovered that I might be pretty low on the autism spectrum. So I think that makes it hard for me to simplify something like this. Like test taking, there are no “yes/no” answers for me. That may be why I was thinking everything should be it’s own “class”, I’m breaking everything down too much.

I live in North Alabama (again), close to Rocket City (Huntsville). My dad was in the service, so that’s why I was in Germany. Some of my best memories are of that time period. We traveled/saw a lot. Barvaria, Eagles Nest, Holland and so much more. The country side was so beautiful and the people were so kind.

Perhaps I did point out the delay incorrectly..
Speaking of cannon to shoot a Sparrow (not advisable), Arduino already comes with FreeRTOS on the ESP32. It’s available to you so why not use it?

”doctoral thesis on tank filling and emptying.” It’s not about Tank Filling, bit it is about safety.. Now, I don;t mind if the Waste Tanks are overfilled, but perhaps **oldmicroguy.
**
Thanks for teh good and Friendly comments!

So, you’re basically saying that anything that has anything to do with the tank, digital inputs, outputs, “devices” should be included into the “tank class”. Then all, or almost all, functions having to do with the tank are included into the class.

Yes, that’s exactly what I mean.

In a way, it’s broken down into its individual components within the “class.” The class structure goes all the way down to the smallest data type—you can’t get smaller than a “bool.” I’m not sure if that bothers you given your autism or not.

Anyway, I hope I’ve been able to help you in some way.
You wanted to know something about classes; that was the short version.
Do keep in mind my comments regarding the limit switches (normally closed/normally open) and the valves (are they at the top for filling or at the bottom for emptying?). I didn’t know those details beforehand.

I think rvt might have gone a bit overboard; I don’t see any time-critical aspects in your setup.

As you might have noticed, I included <Arduino.h> everywhere. I do that because I work in VS Code using the PlatformIO plugin.

You’re from Alabama? I don’t know the place personally—I only know the song “Sweet Home Alabama.”

Are Google’s translations actually any good? I do know English (well enough), but with this much text, I use Google; it makes things easier.

Greetings from Duisburg (home of “TKS” – Thyssen Krupp Steel)

Sorry!

I sometimes (often) have a tendency to write with a very sharp tongue.

Yes, you were a great help. I’m sure at some point I’ll have one of those “Ah Ha!” moments and it’ll come to me (I love it when you get those).

I think I learned a lot from both. I get the state machine, and precautions.

Tank my word for it, you do not, absolutely, under any circumstances, want to over fill the black waste tank. Seriously bad things happen. I’ll leave it to you to infer what sort of things from the name of the tank. Gray, not so bad, black, bad, really bad.

I’m guessing the translations are working pretty good. I don’t know what you’re saying vs. what the translation is, but everything comes out pretty good.
(I hope Kurpp is doing better over there, I see Krupp toasters and stuff here, and think “how far they must have fallen”. I used to work for Siemens Energy and Automation, our division was the armpit of the Siemens empire.)

I’m going to work on this class stuff. I have the Complete C/C++ reference, 3rd edition, I keep reading the chapters on “class” and it makes perfect sense when I read it and I feel like I understand it, then I look at the screen and suddenly nothing makes sense. (Good book, heavy though, I wish I had a more recent edition)

Thanks again.

These OOP related arduino tutorials may help a bit more.

Thanks, I’ll check them out also.

So far, it’s making sense, it always does when I read it. I won’t know until I go back to the coding part, right now I’m wiring up the interface board. (hardwire, no PCBs) Another day of that and then I can verify it, wire it in to the main board (actual motor controllers) and then go back to code.

Not sure how far along you have gotten, but some comments:

  • While hardly a complex program, something “simple” is a great way to get started with OOP.
  • Typically, you can get started by correlating objects in code with physical objects – it’s easy to think that way – but rvt has a good suggestion, which is to consider the control algorithm as an object. While it’s unlikely you will be swapping different algorithms in and out, doing so is a good way to structure code. My suggestion is to put the logic in the main loop to start, and then “challenge” yourself with expanding beyond physical objects. (“Challenge” in quotes because I don’t mean to insult your intelligence – it might be simple for you.)
  • More important is the paradigm that you use for control. Depending on how your motors/valves work, the code provided by luckystriker above suffers from a common flaw, which is that it will command the motors over and over, which may lead to wear on the motors. Better is to use event-driven programming, where you look for the events that cause actions. A good start is here: http://design.stanford.edu/spdl/ee118/pdf_files/EventDrivenProg.pdf