A long time ago (Arduino 022), I developed a class library for a hardware device that uses a normal serial interface. At that time, there was something like a softserial (official) and a newsoftserial (better) library available, and of course a built-in hardware serial port on the early Arduinos. Back then, it wasn't possible to easily write one library that could allow for either type of interface to the device. In the intervening years, and with what used to be newsoftserial becoming softwareserial, has the situation improved? Is there some way to develop a class which derives from whichever type of serial interface the user has available, so that they can simply pass in a reference of some sort to the serial interface they wish to use to communicate with my supported device?
Yes, it's called "Stream". the Stream class is the parent class of many of the serial and serial-like devices:
$ grep -r ": public Stream" *
api/HardwareSerial.h:class HardwareSerial : public Stream
api/Udp.h:class UDP : public Stream {
api/Client.h:class Client : public Stream {
api/USBAPI.h:class Serial_ : public Stream
libraries/Wire/Wire.h:class TwoWire : public Stream
libraries/GSM/GSM3SMSService.h:class GSM3SMSService : public Stream
libraries/SD/SD.h:class File : public Stream {
libraries/SoftwareSerial/SoftwareSerial.h:class SoftwareSerial : public Stream
Thanks, majenko. Are you (or anyone) aware of any examples of libraries written that use Stream in this way? Mine can't, for instance, support all the child classes your search came up with, really just the hardware and software serial ones. If I derive mine from Stream, doesn't that leave me implementing all the things that the hardware and software serial classes provide, like setting baud rate, etc.? I want the user of my library to be able to pass in either a softwareserial interface (on pins 2 and 3 for example) or a hardware serial port (e.g. Serial2). If anyone is aware of such an example, I'd be greatly obliged!
Stream is a base part of Serial so you might access Serial through its own Stream data and function members, if they're not private. You have the sources for both anyway. Headers and functions.
Hmm... I don't seem to be explaining this well.
I have a serial device. It's simple enough to interface to the device either through a SoftwareSerial port, or a hardware one. But the device uses obscure commands, and I would like to hide the complexity from the user inside of a library. I would like the user to tell the library, probably in the constructor, how/where the device is connected. So the user should somehow pass to me some form of reference to a serial port. One user of the library might have an Arduino that has multiple hardware serial ports, and they might have the device connected to Serial2, so they should be allowed to pass that in to the constructor, e.g.
MyObject ( Serial2 );
But another user might only have the one hardware serial port, and might be using it for debugging, or talking to software on the computer, etc. So they might want to use a software serial port, e.g.:
SoftwareSerial ss1 ( 2, 3 );
MyObject ( ss1 );
How should I implement such a function, and what data type should it accept as its argument? I imagine I could implement 2 separate overloaded functions, one of which accepts a hardware serial name, and the other of which accepts a SoftwareSerial type. But then in the library code, everytime I want to access the serial port, I have to have an if:
if ( UsingSoftwareSerial ) DoTheSoftwareSerialThing ( );
else DoTheHardwareSerialThing ( );
I'm hoping to find a better way which appropriately hides the differences between hard and soft serial from me!
You should be able to address different Stream objects by their Stream roots.
Serial is derived from Stream and Stream pointers should be able to point at properly cast Serial objects.
So you set up your SoftwareSerial and Serial objects but point to them and use them AS Stream objects using Stream pointers. The objects handle the details. That's OOP.
Sub-class specific functions, like .begin(), have to be called by the user outside the class. Your class shouldn't care about things like the baud rate, etc - it is the user's responsibility to configure the communication device as it should be configured. You just then work with the device as configured.
class Foo : public Stream {
private:
Stream *_dev; // Somewhere to store the object's pointer
public:
Foo(Stream& dev) : _dev(&dev) {} // Store the pointer to the object
size_t write(uint8_t val) { return _dev->write(val); } // Call a function on the stored object pointer
};
Foo myFoo(Serial); // Create a new instance of the class with the Serial object as a parameter
void setup()
Serial.begin(9600); // Configure the Serial
}
void loop() {
foo.println(millis()); // Print something via your class.
delay(500);
}
Excellent! Thanks, majenko -- I will give that approach a try!
This is coming along nicely. Almost compiles, but getting an error that seems unrelated to this, which I will post separately.
And, I can confirm that it compiles and works! Thanks everyone!