Menus - but not for Arduboy

I was flailing around trying to put classes in other classes :roll_eyes: that I didn’t have anything remotely coherent to show.
Here’s where I have got it to, which works. My breakthrough was declaring the NumberInputSystem class in actions.h and then declaring the extern instantiations further down: lowerLimitAlarmInput, higherLimitAlarmInput and coolDownTargetInput.

The reason I thought I had this/needed this dependency was because I was using an instance in actions.cpp that had been initialised in main.cpp and otherwise couldn’t be seen by the compiler when it was compiling actions.cpp. I thought #include "numbersIn.h" would be needed in actions.h and because numbersIn.h already #includes actions.h this would result in cyclical dependency. I was also getting error: include files are nested too deeply and I think some of my guards were not near enough to the start of the file! I was also getting:

In file included from lib\actions\actions.cpp:11:0:
lib\actions\actions.h:18:19: error: aggregate 'NumberInputSystem lowerLimitAlarmInput' has incomplete type and cannot be defined
 NumberInputSystem lowerLimitAlarmInput;

And other people with this type of error reported issues with cyclic dependency were to blame.

That would indeed be a cyclic dependency.

And that is definitely a cyclic dependency. :P
(It’s the include version of a stack overflow.)


However you’ve got things somewhat backwards.
numbersIn.h doesn’t need to include actions.h because it doesn’t depend on anything in actions.h.

You’ve declared everything it references at the top.
In fact you’ve externed more than just the things it needs,
you’ve also declared some things it doesn’t presently use.

Were the function implementations in a .cpp file instead of a .h file you wouldn’t even need any of those.
Though with the right header setup it would be possible to have everything in a header.

For example, all the functions related to displaying information (e.g. printTemperatureDiv10, showOnOff) might be in one header, whislt everything relevant to temperature units (e.g. TempUnit, getUnitSymbol) might be in a different header.

And once you’ve eliminated the F issue via the method I mentioned earlier, the three NumberInputSystem variables could be migrated into actions.cpp.
Or better yet, they could be member variables of actions.
(After all, if the actions class doesn’t have any members, there’s not much point in it being a class, it may as well just be a namespace.)

1 Like

I managed to get this working here. Then I tried

Which I could get to work when the NumberInputSystem instances were initialised from main.cpp but I have spent a day trying to initialise them from the Actions class and am failing miserably. Please could you give some hints? All the examples I have found of initialising instances of other classes from a class have not helped.


Edit: I realise that not linking to the examples and providing failing code and error messages isn’t helpful but I have tried many different things and it seems pointless to go back and reproduce them. I’m mainly after a steer to a good example that demonstrates the kind of implementation you had in mind. Or a “variable not a class” type clarification. Thanks


Update - think I have cracked it, sort of. Gist to follow.

What exactly do you mean by “from the Actions class”?

Do you mean you want to make them part of the Actions class?
Or just initialise the global variables lowerLimitAlarmInput et cetera from actions.cpp?

Here’s the gist with

hopefully implemented as intended.

I hope the frustration and floundering over the last 36 hours and before serves as some sort of reinforcement in my learning. I’m wondering how to add the const char lowerLimitAlarmText[] to the class. When it sits outside the Actions class, it compiles fine but when it sits inside the class, it throws a warning:

In file included from lib\actions\actions.cpp:11:0:
lib\actions\actions.h:56:38: warning: initializer-string for array of chars is too long [-fpermissive]
   const char lowerLimitAlarmText[] = "MENU Alarm lower limit: ";

Something to do with declaring and initialising, no doubt.

Oh yes, I decided to take the messages out of flash, as the ATtiny 0 and 1 series chips don’t use progmem, AFAIK, so there’s not much point in adding in helper functions which wouldn’t be needed in the medium term.

What next? I’m still waiting for megaTinyCore to port to Platformio (apparently some point this week) but in service of my wider C++ education I’d relish more ideas on what to try next with this code.

PS, one of the main things which kept me persisting in trying to make these changes to admittedly workable code was @Pharap’s link to some advice:

Programming is really hard.

I’m not sure! Perhaps my gist will help you see what I did. I think the latter.

Most likely it should be static constexpr.
(E.g. static constexpr char lowerLimitAlarmText[] = "MENU Alarm lower limit: ";.)

I couldn’t say more about the warning without more context.

Have you got some references to some documentation for those?
If there isn’t any progmem/flash then that’s going to confuse matters a bit.

Most of the boards listed under ‘Supported Parts’ have flash/progmem.

Indeed it is.

That’s why it’s important to help people as much as possible,
and why it’s important to never be afraid of mistakes or failure.

That link has a lot of useful advice.

Though I disagree that the frustration doesn’t go away.
It depends on your mindset and how you tackle the problem.

I tend to find that frustration experienced when programming isn’t frustration at the difficulty of the problem,
it’s frustration with oneself for being unable to solve the problem.
Coming to terms with one’s limitations elminates a lot of the frustration involved, as do patience and determination.

You’ve made them part of the Actions class.
Specifically they are no longer global variables, they are now (non-static) member variables.

That’s somewhat sensible. (A class without member variables is typically a waste of time.)

When I’m next free to do so I’ll sit down and read the gist properly,
think over your problem once more and try to coax the program towards something more ideal.

Hopefully by then the flash issue will have been cleared up.

For now, here's a list of mostly superfluous things I thought of while skimming the gist...
  • Some of your class declaration (e.g. class Display; in actions.h is redundant becuase you already include thermDisplay.h) and possibly some of your externed variables are superflous. Try to figure out where your redundancies are, it might help you better understand why they are redundant.
  • Most of your externs could be moved into .cpp files instead
  • uint8_t actually comes from stdint.h, not Arduino.h. Arduino.h includes stdint.h somewhere along the line. Hence in some cases you could possibly just use stdint.h instead, but you do need Arduino.h for Serial and possible other things.
  • Try to break the habit of using contractions - e.g. avoid ‘therm’, ‘temp’ and ‘act’. Contractions harm readability.
  • Try to use more meaningful header names. E.g. numbersIn.h should probably be NumberInputSystem.h because the main reason for including it is to introduce the NumberInputSystem class.
  • If TempHelper never has any state, you’re better off leaving the functions as free functions or perhaps putting them in a namespace. Very rarely does a class without member variables make sense.
  • Rather than declaring an explicit default constructor for Actions, you can use default member initialisers. E.g. class Actions { ... NumberInputSystem lowerLimitAlarmInput { lowerLimitAlarmText, alarmLowSetting }; ... };
  • Regarding semicolon placement after curly braces: a semicolon is only necessary after a curly brace when the curly brace is either the main block of a type definition (e.g. class Name {};, enum class Name {};) or a usage of the ‘uniform variable initialiser’ syntax (e.g. int value { 5 };, SomeClass value { objectA, objectB };)
1 Like

Thanks, I’ll try that.

Sloppy language on my part. Here’s what I’m talking about, which results in no need for F() macro.

Thank you again.

Thanks, I’ll have a look at those.

1 Like

This resulted in:

C:\Users\...~1\AppData\Local\Temp\ccODpbls.ltrans0.ltrans.o: In function `global constructors keyed to 65535_0_main.cpp.o.2257':
<artificial>:(.text.startup+0x74): undefined reference to `Actions::lowerLimitAlarmText'
<artificial>:(.text.startup+0x76): undefined reference to `Actions::lowerLimitAlarmText'
<artificial>:(.text.startup+0x86): undefined reference to `Actions::higherLimitAlarmText'
<artificial>:(.text.startup+0x88): undefined reference to `Actions::higherLimitAlarmText'
<artificial>:(.text.startup+0x98): undefined reference to `Actions::coolDownTargetText'
<artificial>:(.text.startup+0x9a): undefined reference to `Actions::coolDownTargetText'

but static constexpr char* lowerLimitAlarmText = "MENU Alarm lower limit: "; seems to work fine. This is where I found my steer.

Ah, ok. It’s got a unified address space.
That makes sense, and that means there’s no need for my flash string library.

(That also means I might be able to set the code up so it compiles on desktop, which will make solving bugs easier.)

That’s unfortunate.
It’s a linker error rather than a compiler error so that one’s going to be a bit trickier to debug.

That’s strange…

Semantically the difference is where the string is actually stored.

I’ve done some brief testing and it seems there’s a similar issue on desktop.
I’ll investigate it in more depth later.

I know of an alternative solution to the issue but it’s probably fine to stick to the pointer solution for now.


Something I’ve very briefly established:

  • constexpr char array[] = "Some text"; - fine
  • static constexpr char array[] = "Some text"; - also fine
  • class Something { public: static constexpr char array[] = "Some text"; }; - “C2131: expression did not evaluate to a constant”

I don’t know why this is the case, I’ll investigate later, but the bottom line is that it seems that having a constexpr char array initialised from a string literal is fine for globals but not for static member variables.

1 Like

I’ve had a look and a think.

I can imagine many different ways to implement the system.
It’s entirely possible to implement the system using nothing but functions and variables, and even without any kind of central loop.
Conversely it’s possible to use hardly any global variables (possibly even no global variables at all) and to use only classes and member functions.
And of course there are solutions in between the two extremes.

I feel that I’ve perhaps been nudging you in different directions because I’ve been addressing your issues as they crop up without having an idea of what the final result ought to be.

So to know which direction to steer you in my question at this point becomes “what is your goal?”.

Are you going to implement the system that you’ve outlined and then never change it again?
Is the system secondary and all this is just a context in which to learn more about C++ and programming in general?
And how important is ‘good practice’ in the grand scheme of things?

(I may have asked these questions before and forgotten.)

1 Like

Yes, despite starting with good intentions and drawing state charts, I did derail things into a debugging tutorial.

I am making a temperature sensor, which I think would be quite useful to lots of people and might even be something I sold hardware for. Software would be FOSS, naturally, and intended to be hackable* for people to turn to their own application. I already started working on some code modules for this, such as lightweight OLED and temperature sensor drivers, when I realised this was probably the time to raise my coding game. ‘Good practice’ is important to the extent that others can see what’s going on in the code and modify it to suit their needs, and I don’t learn too many bad habits in my first foray into more intermediate :p programming.

At this stage I don’t think it’s more desirable to have everything hidden away in classes than any other way of doing things. *Realistically, my imagined user archetype is either going to use the device with the core firmware and be happy with a range of settings they can select through the menu, or they are going to want to use the sensor and display drivers to make their own thing work (I envisage being able to chain several sensors together to make an array controlled by one master thermometer). There’s probably no need for a meta class that they would use to achieve their own firmware. But then again, people put tetris on soldering irons, so who knows!

For my wider education, I would like to develop software I could extend ( e.g. by adding another menu setting or display option) and perhaps be able to use in my wider design of devices. So classes, or at least understanding good code design and structures to deal with menus, user inputs, sensor inputs and user displays, would be valuable to me. This project is a means to that end in a large way. I have a couple of prototype PCBs on the bench now and I’ve got temperature plotting nicely on the OLED (although it needs a lot of tidying up). I have been testing the menu stuff on the ATMEGA168 plus a couple of LEDs and buttons because that’s what I had to start with and it makes sense to keep with that until the hardware is further along (and megaTinyCore is ported to PIO). Some of the temperature graph plotting has been fun and infuriating too (autoscaling) but I’ve left that out as this thread is about menus.

I’m really grateful for all your help. Sorry if I have left you none the wiser about what I’m trying to do!

1 Like

Sorry for the delay in replying. (I thought I had replied and then realised that I hadn’t.)

Classes don’t really ‘hide things away’ as such.
(If you’ve read propaganda saying it does, it was probably written by a C programmer.)

Fundamentally classes are merely a way of combining data and functions into a single entity.
(That’s before getting into inheritance and virtual functions of course.)

‘Information hiding’ is a principle that sometimes plays into how classes are designed,
but the point of hiding certain information is to create a clear distinction between interface and implementation.
I.e. The implementation details are hidden so the users of the class do not come to rely on them, and then the implementation can change if it has to without breaking anything, hence it’s a form a future proofing.

That’s not unique to classes though.
The .h/.cpp split already enables that for regular functions and variables.
You can define functions and variables in .cpp/.c files that are then not visible to other files.

And this chain/array of sensors would just be ‘dumb’ sensors reporting a number back to the master controller?
They don’t have to do anything special or be programmed to do specific things?

I’m not sure what you mean by ‘meta class’.
(‘Meta class’ has different meanings in different contexts,
but usually it means ‘a class that describes other classes’.)

Interesting choice of music…

Strictly speaking all software is extensible when you have access to the source code, but not necessarily in an easy way. :P

There’s a bit of a problem in that extensibility and the earlier requirement of using few resources are conflicting requirements.

When it comes to resources there’s a sort of ‘inconsistent triad’ (as I like to call it) of memory, speed and flexibility.
Usually it’s impossible to improve one without sacrificing another,
so you have the balance the requirements and decide which to sacrifice and when.

Until then, you might want to give the Arduino extension a try.
As long as you stuff everything into src and have a token .ino file to forward into the src file it should mostly behave the same.

1 Like

Fair point. But I meant that they (or appear to me to) obscure implementation details and present the interface in a way which maps better to human mental models of the system. the key objective would be writing code, through classes or other ways, which naturally aligns with their mental model of a configurable thermometer.

They would not be dumb, in the sense that they would need a microcontroller between them and the main data bus because all the sensors have the same, fixed, I2C address. There is also a certain amount of configuration of the sensors that can be done, such as read resolution, but I don’t foresee that being critical in chained sensor applications. One of the reasons for using the low power microcontrollers is that it would keep the BOM price down for the nodes. But I’m getting ahead of myself a little bit here. One sensor, one display, two buttons and a battery is good enough for one device to start with.

I meant similar to how Arduboy2 then goes on to hold/link/refer to Arduboy2Core, Arduboy2Audio, SpritesB etc. I don’t think I need to provide a tidy API (I’m sure I’m misusing as a term here) like you do when you are making a library for people to use to write many different games.

Neat point about extensibility :D but I suppose that adding another case to an existing switch case can be tidier than introducing else if() and perhaps accompanying conditions if the code is initially written to make extension less of a burden.

I’m happy with “any two of three” choices - my extensions would be for when the target microcontrollers were more capable on other projects. In such a case I would like to reuse significant chunks of this code base, or at least the design approach.

I have been thinking over the weekend how to structure the code better. Some thoughts are:

  • There should be a file which keeps the switch case state machine, that would allow me to change the logic of the state machine with fewer loose ends.
  • The sensor driver and OLED driver should be separate files to everything else. There should be some kind of generic sensor read and display render interface for those, so for example you could easily switch between OLED and serial monitor. Just spit-balling and it may not be worth the effort.
  • There should perhaps be a button abstraction. At the moment there’s no debouncing, it relies on pin polling, it doesn’t allow for chording/multipress and the button does something different in each menu or display setting (almost). So something like buttons.action(incrementDigit, cycleDigit, menuState = Options::digits might handle what actions to take based on button 1, 2 or both being pressed and then released. But it’s perhaps more complicated than needed now.

I will stick everything back into Arduino if PIO isn’t ready.

Without any more context or examples the best I can offer as a short response is “it depends”.

People’s mental models don’t always map well to code.
Sometimes a system is better modelled in a particular way, and that way doesn’t always line up with how people imagine the situation.

This sounds to me like another problem for another time (most likely involving more diagrams).

What is ‘BOM’ in this context?

Usually when I see ‘BOM’ it means ‘byte order mark’.

Interestingly Arduboy2 has a very different relationship with each of those other classes.

Arduboy2 inherits Arduboy2Core. (This is ‘inheritance’.)
Arduboy2 has an intance of Arduboy2Audio. (This is ‘composition’.)
(Though it doesn’t actually need one because all the functions in Arduboy2Audio are static.)
SpritesB refers to Arduboy2, but Arduboy2 knows nothing about the existance of SpritesB.

Whichever of these relationships you’re talking about ‘meta class’ is the wrong term.
I believe it can have different meanings in different contexts but the typical meaning is something that C++ doesn’t have (yet…).

API is a very vague term.

Whether or not

Typically switch statements are hard to maintain once they grow past a certain size,
but most of the more flexible alternatives are more costly.

I agree.

The display side of things is a bit more complicated though,
because an OLED and a serial connection are structured differently.

An OLED is a rectangle of pixels,
whilst a serial connection is a stream of data,
and you have to essentially find a way of abstracting things in such a way that the two systems behave similarly.
Every solution has both upsides and downsides.

That aside, Arduino has already partly accomodated the rendering side of things via the Print class.

I was intending to mention the Print class soon anyway,
but I’ve been putting it off because it means we’re getting into the realm of inheritance and (subtype) polymorphism.

Understanding (subtype) polymorphism is a big milestone and it’s a somewhat complex topic.
But on top of which there’s the added issue of the cost of using it.
I’ve never dug behind the scenes enough to figure out precisely why,
but virtual functions are expensive on AVR.

If you need both OLED and serial capability without recompiling the program then virtual functions are one of the better options, but if having to recompile the program to switch between the two is acceptable then there are cheaper alternatives.

Most likely there should be.

There’s several possible approaches though.

By debouncing do you mean something similar to Arduboy2’s justPressed or actual debouncing?

Chording is difficult because you have to decide what the ‘grace period’ is for deciding what constitutes a chord versus a normal button press.

It’s probably easier to add a third button than to do chording.

That’s doable (or at least the first half is), but probably not ideal.


I’ll try to set aside some time to draw up a few options,
but my time’s been very split over the last week or so,
and I’m always less productive when the weather gets hot.

For now I think my suggestion would be to try to have a read of the Arduino Print class, and learn a bit about virtual functions.
(https://www.learncpp.com/ has a whole dedicated chapter on the ins and outs of them and this SO answer provides a simple example of the basics.)

Ordinarily I’d offer you an explanation but my brain’s a bit addled at the moment.

1 Like

Bill of Materials

2 Likes

@SimonMerrett, I’ve prepared an introduction to inheritance and polymorphism,
and a bit about how the two should be used should you wish to read it.
(Made possible thanks to a cool breeze, a drop in temperature and a heap of coffee.)

2 Likes

@Pharap wow thanks, I was keeping quiet as I would rather you rested in this weather (we’re in Devon so it’s pretty hot today). Yes please.

1 Like

I’m not a janner, but my weather’s pretty similar.
Fortunately there’s a breeze about.

Inheritance:

Inheritance is a relationship between two classes.
It is formed when one class elects another class to be its ‘parent’*,
thus making the electing class a ‘child’ class.

This relationship has several effects.
Firstly, all the member variables and member functions of the parent class are made part of the child class.
For example, given:

class Parent
{
public:
	int x;
	
	int getX() const
	{
		return this->x;
	}
};

class Child : public Parent
{
public:
	int y;
	
	int getY() const
	{
		return this->y;
	}
};

Child has both an x and a y variable, as well as member functions named getX() and getY().

I hasten to point out that although this might imply a means to reduce code duplication,
using inheritance to avoid code duplication is often a bad idea.
(Explaining why would require several examples, hence I’ll leave that for another time.)

The second effect afforded by inheritance is the ability for a child class to pretend to be its parent.
For example, given the above Parent and Child definitions:

void printX(const Parent & parent)
{
	std::cout << parent.getX();
}

void test()
{
	Child child;
	// printX only accepts references to Parents,
	// but Child 'is a' Parent due to inheritance
	printX(child);
}

This exemplifies why inheritance is sometimes called an ‘is a’ relationship.
A child class can be used as if it were an instance of its parent class.
However, this capability doesn’t make much sense until you factor in subtype polymorphism…

* Note:
Some programmers (and some languages) prefer the terms ‘subclass’ and ‘superclass’ to ‘child class’ and ‘parent class’.
I prefer ‘parent’ and ‘child’ for several reasons.
Sometimes the parent class is also referred to as the ‘base’ class.

(Subtype) Polymorphism:

Polymorphism, literally meaning “many shapes”,
refers to code that somehow appears to ‘change shape’ in some sense or another.

Firstly I’ll address the ‘subtype’ issue.
There are actually several different kinds of polymorphism.

The three kinds you’ll find in a programming language are:

  • Ad hoc polymorphism
  • Parametric polymorphism
  • Subtype polymorphism

You’ve probably experienced the first kind before, ad hoc polymorphism, in the form of overloaded functions.
I.e. functions that share the same name but handle different parameter types.
Serial.print is one such function, as it can accept int or char, or const char * (among others).
(This is also the key to how the F macro works and what const __FlashStringHelper * is.)

I won’t explain what parametric polymorphism is now, because that’s the realm of templates.
As much as I’d love to explain templates, they’re unlikely to be of much use to you at the moment.

Subtype polymorphism is the kind used ubiquitously in OOP.
Most of the time if an OOP programmer says ‘polymorphism’ they’re probably referring to subtype polymorphism,
despite the fact that ad hoc polymorphism is just as prevalent in most object oriented programs.

The fundamental idea is that a parent class defines functions that are marked ‘virtual’ whose behaviour can then be ‘overridden’ by inheriting (child) classes.
This combined with a child class’s ability to be used in place of the parent class gives rise to the ‘polymorphic’ behaviour.

Here’s a heavily simplified example:

class AnswerRetriever
{
public:
	virtual int retrieveAnswer()
	{
		return 42;
	}
};

class RandomAnswerRetriever : public AnswerRetriever
{
public:
	int retrieveAnswer() override
	{
		return std::rand();
	}
};

Here the default behaviour for an AnswerRetriever is to give an answer of ‘42’,
whereas the child class RandomAnswerRetriever returns a random result.

Combining this with the afforementioned ability for a child class to pretend to be its parent gives:

void printAnswer(AnswerRetriever & retriever)
{
	std::cout << retriever.retrieveAnswer();
}

void test()
{
	RandomAnswerRetriever child;
	// Will print a random number
	printAnswer(child);
}

(Be thankful, I have spared you from the god awful "Animal is the parent class" examples. :P)

The real purpose of inheritance

Many people who learn about inheritance start throwing it at every problem they come across.
I blame this partly on poor examples, and partly on not understanding certain principles behind the idea.

Before I get to the principle I’d like to discuss,
I’d like to provide some more realistic examples of when polymorphism should be used.

For my first example, let’s say you’re writing an address book program.
In this address book program, your contacts are represented by the following class:

// Note: horrendously simplified,
// don't use this as a realistic example.
struct Contact
{
	std::string name;
	std::optional<std::string> postcode;
	std::optional<std::string> telephoneNumber;
	std::optional<std::string> email;
};

And you want to be able to load contact lists stored in a variety of formats.
So you create something like this:

class ContactLoader
{
public:
	// = 0 means 'pure virtual',
	// which means the child class must provide an implementation.
	virtual std::vector<Contact> load(std::istream & inputStream) = 0;
};

And then create the following child classes:

class JsonContactLoader : public ContactLoader
{
private:
	JsonParser jsonParser;

public:
	std::vector<Contact> loadContacts(std::istream & inputStream) override
	{
		// Read contacts from a JSON file
	}
};

class XmlContactLoader : public ContactLoader
{
private:
	XmlParser xmlParser;

public:
	std::vector<Contact> loadContacts(std::istream & inputStream) override
	{
		// Read contacts from an XML file
	}
};

Hence the majority of the code simply refers to a ContactLoader and the actual specific type of loader only needs to be specified when the loader object is created.

This example exhibits something called the ‘Liskov substitution principle’,
the basic idea of which is that child classes should exhibit a similar enough behaviour to each other that you can subtitute any child class for the parent and the program still behaves correctly.
Or in other words, while child classes are allowed to change how something is done,
they should not change what is done.
If the parent class says it loads contacts, all the child classes must load contacts,
even if they do it in a different way or have slightly different specifics.

1 Like

What is wrong with:

class Dog : Animal() {

  public:
    int numberOfLegs() override {
      return 4;
    }

}

C’mon … its a classic!

“Britain braces for 27°C heatwave:sweat_smile: :rofl: