Using a library within a library as well as main code (ESPAsyncWebServer)

Ok, so the title may sound confusing, but here's what I'm looking to do.

I want to write a library, that will use functions of other libraries. But I want to make sure some of the libraries can be used n an end-user's main code as well.

As an example, one of the things I want to do is create my own wifi manager library. I know there are a bunch out there, but not one of them has all the things I want and the look that I want. So I thought I'd try my hand at making one myself.

In making a wifi library, I would like to use ESPAsyncWebServer to server the configuration pages. However, it is likely that an end-user (myself or otherwise) may want to use ESPAsyncWebServer to server their own pages as well.

Obviously, if they were just simple html pages, you can just put files in the spiffs, ffat or whatever file system you are using and if the wifi manager has the servStatic function setting the root, then any files you upload to the partition in the right place will be served.

What I want to do it to be able to have some lines in the WiFi Library, let's say I want to server some sort of config page from PROGMEM with gzipped content.

	server.on("/config", HTTP_GET, [](AsyncWebServerRequest *request) {
		AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", configindex_htm_gz, configindex_htm_gz_len);
		response->addHeader("Content-Encoding", "gzip");
		request->send(response);
	}).setAuthentication(Config.AdminUser, Config.AdminPass);

But then I want the end-user to be able to add more "server.on" commands in their own code. I swear I remember a library that did just this in the past, though I can't remember what it was to look it up. It was more of an OTA Update thing, but had WiFi config in it too.

How would one go about doing something like this, whether for this library or any other one? I appreciate any help with this as I can't seem to find the right terms to search for and have never done this before. I tried including the ESPAsyncWebServer.h in my library code, which defines "AsyncWebServer server(80);" in the cpp file as well as setting the include in my main program, but two things happen.

  1. I try using server.on in my main code, outside of the library files, obviously this doesn't work. I get the message, "error: 'server' was not declared in this scope; did you mean 'Server'?"
  2. If I add the "AsyncWebServer server" declaration in the library as well as in the main program, of course, I get a message about multiple declaraions.

Is there a way around this, to have either the main program use the declaration from the library or the library use the declaration in the main program?

Something like this:

Main .ino:

#include "MyLibrary.h"

void setup() {
  MyLibrary::server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send(200, "text/plain", "Hello, world");
  });
  MyLibrary::server.begin();

}

void loop() {
}

MyLibrary.h:

#ifndef MY_LIBRARY
#define MY_LIBRARY

#include <Arduino.h>
#include "ESPAsyncWebServer.h"

class MyLibrary {
  public:
    MyLibrary() {}
    static AsyncWebServer server;
};

#endif

MyLibrary.cpp:

#include "MyLibrary.h"

AsyncWebServer MyLibrary::server(80);

I'm thinking you may also want your library to be a Singleton as it's unlikely there should ever be more than one instance of it.

1 Like

I had not thought about putting it into the class. That could work. Then whoever uses the library would just have to adjust their code for it being under the class. I'm still new to writing classes and figuring thing out. With that being said, I'm not familiar with the term singleton either, but I will definitely look that up shortly.

The other option is to instantiate the AsyncWebServer object in the user code and pass it by reference or pointer into the class.

That sounds like it might be easier for the end user (me for now) to write the main program.

I know this is probably a matter of opinion and everybody has one, but is there a preference between reference or pointer for something like this?

As I understand it, functionally they are almost (not really) the same. Except that a pointer takes up extra memory and can be changed to point to something else while a reference is just an alias of whatever you point it to.

In deciding which way to go on that (or getting help deciding) I had another thought. Perhaps I could have a parameter in the class to pass the pointer or reference, then when the class constructor (I think I got that right) is run, it could check if that was set and use the already instanced (in main code) AsyncWebServer, but if nothing is passed (default value of null) then it could be instanced in the constructor? I'm just thinking that if the user is not planning on serving web pages and doesn't include the AsyndWebServer library in their main code, there would be a fallback.

But, I am far from an expert on classes and don't know if this is possible.

Some personal preference, some application dependent.

There's no way to know that given the compiler's optimization capability and the fact that references are likely implemented using pointers under the hood.

That's possible.

This is exactly what I did in one of my library, which inherits the functions of ESPAsyncWebServer and adds features that I frequently use (such as ACE Editor, firmware update, setup webpage etc etc).

I did the same thing using the library included in the ESP32 core for Arduino. If you want to take a look for inspiration, you can find sources here:

Yes, inheritance is indeed another way. @kb1sph1, you basically need to decide which of the following statements is most true:

  • The library HAS an ESPAsyncWebServer object - the first example I presented.
  • The library KNOWS an ESPAsyncWebServer object - the server object is supplied by the user code.
  • The library IS a ESPAsyncWebServer object - use inheritance.

As you suggested, a hybrid approach of the first two is also possible.

Ok, so I made my class a singleton to make sure it isn't instantiated more than once. That seemed to be pretty easy once I looked things up. Thank you!

As far as the ESPAsyncWebServer, let me know if you think this would work or if there is a better method, or if I've done anything wrong.

MyClass.h

#pragma once

#ifndef MYCLASS_H_
#define MYCLASS_H_

#include <Arduino.h>
#include <ESPAsyncWebServer.h>

class MyClass {
private:
	MyClass();
	virtual ~MyClass();
	AsyncWebServer* server;
public:
	static MyClass &getInstance();
	MyClass(const MyClass &) = delete;
	MyClass &operator=(const MyClass &) = delete;
	void Begin(AsyncWebServer *server = nullptr);
};
extern MyClass ClassInstance;

#endif

MyClass.cpp

#include <Arduino.h>
#include <MyClass.h>

void MyClass::Begin(AsyncWebServer *asws) {
	if (asws == nullptr) { server = new AsyncWebServer(80); }
	else { server = asws; }
}

MyClass &MyClass::getInstance() {
	static MyClass instance;
	return instance;
}

MyClass &ClassInstance = ClassInstance.getInstance();

MyProgram.ino with AsyncWebServer instantiated

#include <Arduino.h>
#include <MyClass.h>
#include <ESPAsyncWebServer.h>

AsyncWebServer server(80);

void setup() {
	ClassInstance.Begin(&server);
}

void loop() {
}

MyProgram.ino without AsyncWebServer instantiated

#include <Arduino.h>
#include <MyClass.h>

AsyncWebServer server(80);

void setup() {
	ClassInstance.Begin();
}

void loop() {
}

Now obviously there would be more to this, like including WiFi.h and whatever else is needed, but I just wrote a condensed version here to see what you think. Keep in mind I've never done this before and I'm just going by examples I can find and what I've learned here.

Mostly what I'm looking at here is whether the ESPAsyncWebServer parts of this code are ok or should be done differently.

Lots of errors. It won't compile as-is. It appears you didn't try to. Corrections:
Main .ino:

//#include <ESPAsyncWebServer.h>   <---- No need for this, it comes with MyClass.h

MyClass.h:

#include <WiFi.h>  // <---- Need this
  //virtual ~MyClass();  <--- Needs an implementation in .h or .cpp file
  virtual ~MyClass() {}
  // MyClass();  <--- Needs an implementation in .h or .cpp file
  MyClass() {}
//#ifndef MYCLASS_H_  <---- get rid of this if using 'pragma once'
//#define MYCLASS_H_  <---- get rid of this if using 'pragma once'
//#endif  <---- get rid of this if using 'pragma once'
//extern MyClass ClassInstance; <--- ClassInstance needs to be a reference.
extern MyClass &ClassInstance;

MyClass.cpp:

//MyClass &ClassInstance = ClassInstance.getInstance();  <-- Not correct
MyClass &ClassInstance = MyClass::getInstance();

I did not actually write it into a program, just a concept only written here from someone who didn't know what they were doing. LOL

Fair enough, I've seen other examples that put the include in multiple files and didn't really think about it.

I legitimately forgot to put that into the .cpp part, but do have that in the program I have written that I was going by.

To be fair, I did do this in one of my other programs that I have compiled...don't know why I forgot it in this. Probaby just tired at the time I wrote it, but that part will definitely be needed and have some code in it when I actually write it into files.

Ahh, I did not realize I did not need this. Again, I've seen examples with both, but that will definitely condense code :slight_smile: Thank you. Is there a preference of one over the other as far as compiling? Or does it pretty much do the same thing?

Definitely missed that one :man_facepalming: but the singleton parts of the code I did actually put into my code and it compiled. Checking back, I did have the & and just forgot it here.

So, on this one, the example had had the period and not the colons. If I change it in my program to colons, it does not work. It shows a warning before even compiling that says 'getInstance' could not be resolved. And then if I try to compile, it says "error: 'ClassInstance' is not a class, namespace, or enumeration. Is there something I may have mistyped that causes this?

NOTE: I replaced it with ClassInstance to compare to my code above, but it has a different name in my program.

All that aside, what I was mostly concerned about was the actual EspAsyncWebServer parts. Removing the duplicate include and fixing the rest of it you've mentioned, do you think the way I plan on doing the ESPAsyncWebServer will work for "if it's already instantiated in main code, do this. If not, do this to instantiate it"?

I really do appreciate the help and advice. Thank you!

No way to tell. Perhaps if you posted the actual code and the actual error messages verbatim instead of paraphrased versions.

Okay, well I hadn't written it yet because I was looking for advice on how to implement it, not debugging a program that's already written. Therefore, there was no actual code or error messages to report. And I did specifically say, "Mostly what I'm looking at here is whether the ESPAsyncWebServer parts of this code are ok or should be done differently."

But to give a more complete version of the code for you to look at with the corrections, here is what I have.

MyClass.h

#ifndef MYCLASS_H_
#define MYCLASS_H_

#include <Arduino.h>
#include <WiFi.h>
#include <ESPAsyncWebServer.h>

class MyClass {
private:
	MyClass();
	virtual ~MyClass();
	AsyncWebServer* server = nullptr;
public:
	static MyClass &getInstance();
	MyClass(const MyClass &) = delete;
	MyClass &operator=(const MyClass &) = delete;
	void Begin(AsyncWebServer *asws = nullptr);
};

extern MyClass &ClassInstance;

#endif

MyClass.cpp

#include <Arduino.h>
#include "MyClass.h"

MyClass::MyClass() {
}

MyClass::~MyClass() {
}

void MyClass::Begin(AsyncWebServer *asws) {
	if (asws == nullptr) { server = new AsyncWebServer(80); }
	else { server = asws; }
}

MyClass &MyClass::getInstance() {
	static MyClass instance;
	return instance;
}

MyClass &ClassInstance = ClassInstance.getInstance();

MyProgram.ino with AsyncWebServer instantiated

#include <Arduino.h>
#include <MyClass.h>

AsyncWebServer server(80);

void setup() {
	ClassInstance.Begin(&server);
}

void loop() {
}

MyProgram.ino without AsyncWebServer instantiated

#include <Arduino.h>
#include <MyClass.h>

void setup() {
	ClassInstance.Begin();
}

void loop() {
}

And, yes, this does compile as I just tried it. But the question is more of, "will it work properly and is this the best way to do it?" Again, more concerned about the AsyncWebServer part than anything else.

EDIT: Also went with #ifndef instead of pragma once because I read that pragma once is non-standard. And, while it did compile, my development environment did show a warning about it and made some things look like there were errors, even though the compiler had no problem with it. So, to be more standard and make the annoying warnings in the editor go away, I went with #ifndef.

It doesn't for me. As in my previous reply, I had to change this:

MyClass &ClassInstance = ClassInstance.getInstance();

to this:

MyClass &ClassInstance = MyClass::getInstance();

Perhaps you're running a different version of the ESP32 Arduino Core? I compiled this with v3.0.7. Regardless, IMO, the second version is preferable because it makes clear that you're calling a Class Function rather than an Instance Function.

Besides that, I see no obvious reason why it wouldn't work. But the devil is in the details, so you'll have to flesh it out and try it.

Stylistically, I'd make a couple tweaks in the begin() function:

  • Pass a reference to an AsyncWebServer object rather than a pointer. That way the user doesn't need to take its address in the function call.
  • Guard against the user calling begin() more than once.

I'd also delete the dynamically-created AsyncWebServer object in the destructor. Granted I can't see how the destructor will ever be called given this global singleton structure. But, deleting the object at least shows you're cognizant of preventing memory leaks.

Myclass.h:

#ifndef MYCLASS_H_
#define MYCLASS_H_

#include <Arduino.h>
#include <WiFi.h>
#include <ESPAsyncWebServer.h>

class MyClass {
private:
  MyClass();
  virtual ~MyClass();
  AsyncWebServer* server = nullptr;
  bool internalServer {false};
public:
  static MyClass &getInstance();
  MyClass(const MyClass &) = delete;
  MyClass &operator=(const MyClass &) = delete;
  
  bool begin(AsyncWebServer &asws);
  bool begin();
};

extern MyClass &ClassInstance;

#endif

MyClass.cpp:

#include <Arduino.h>
#include "MyClass.h"

MyClass::MyClass() {
}

MyClass::~MyClass() {
  if (internalServer) {
    delete server;
  }
}

bool MyClass::begin(AsyncWebServer &asws) {
  if (server != nullptr) {
    return false;
  }
  server = &asws;
  return true;
}

bool MyClass::begin() {
  if (server != nullptr) {
    return false;
  }
  server = new AsyncWebServer(80);
  internalServer = true;
  return true;
}

MyClass &MyClass::getInstance() {
  static MyClass instance;
  return instance;
}

MyClass &ClassInstance = MyClass::getInstance();

Main .ino:

#include <Arduino.h>
#include <MyClass.h>

AsyncWebServer server(80);

void setup() {
  ClassInstance.begin(server);
}

void loop() {
}

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.