Trying to replace SerialSoftware in a library

I've been exploring Arduino for a year, but am new to the dark arts of library development. I have spent some time looking at tutorials on writing libraries, but adapting one is beyond me at the moment.
The project is using a MonkMakes Plant Monitor with an Adafruit ESP32-S3 feather board.

The challenge is that the plant monitor has Software Serial coded into the library, and it does not compile for the ESP32. The board has rx & tx pins available as Serial1 - I hope those are the right terms!

Trying to replace SoftwareSerial in the library throws compilation errors - currently: Compilation error: no matching function for call to 'PlantMonitor::PlantMonitor(HardwareSerial*)'

The attempted library adaptions are:
PlantMonitorRob.cpp

#include "Arduino.h"
#include <HardwareSerial.h>           //    added by Rob
#include "PlantMonitorRob.h"

/*            original
PlantMonitor::PlantMonitor(uint8_t rxPin, uint8_t txPin) : _sensor(rxPin, txPin) {
  _sensor = SoftwareSerial(rxPin, txPin);
  _sensor.begin(9600);
}
*/

//  various attempts
//PlantMonitor::PlantMonitor(uint8_t rxPin, uint8_t txPin) : _sensor(rxPin, txPin) {
//  _sensor = Serial1(rxPin, txPin);
PlantMonitor::PlantMonitor(&Serial1) : _sensor(&Serial1) {
  _sensor = HardwareSerial(&Serial1);
  _sensor.begin(9600);
}

int PlantMonitor::getWater() {
  _sensor.print("w");
  while (! _sensor.read() == '=') {};
  return _sensor.parseInt();
}

float PlantMonitor::getTemp() {
  _sensor.print("t");
  while (! _sensor.read() == '=') {};
  return _sensor.parseFloat();
}

float PlantMonitor::getHumidity() {
  _sensor.print("h");
  while (! _sensor.read() == '=') {};
  return _sensor.parseFloat();
}

void PlantMonitor::ledOn() {
  _sensor.print("L");
}

void PlantMonitor::ledOff() {
  _sensor.print("l");
}

PlantMonitorRob.h

/*
  PlantMonitor.h - Library for the MonkMakes 
  Plant Monitor
  https://monkmakes.com/pmon
  MIT License
*/

#ifndef PlantMonitorRob_h         //  updated to avoid library conflicts
#define PlantMonitorRob_h

#include "Arduino.h"
#include <HardwareSerial.h>           //    replaced  #include <SoftwareSerial.h>

/*      original
class PlantMonitor
{
  public:
    PlantMonitor(uint8_t rxPin, uint8_t txPin);
    int getWater();
    float getTemp();
    float getHumidity();
    void ledOn();
    void ledOff();
  private:
    SoftwareSerial _sensor;
};
*/
class PlantMonitor
{
  public:
    PlantMonitor(&Serial1);
    int getWater();
    float getTemp();
    float getHumidity();
    void ledOn();
    void ledOff();
  private:
    HardwareSerial _sensor;
//    Serial1 _sensor;
};

#endif

and the sketch

#include "PlantMonitorRob.h"
#include "Arduino.h"
#include <HardwareSerial.h>

PlantMonitor pm = PlantMonitor(&Serial1);

void setup() {
  Serial.begin(115200);
  pm.begin(9600);
}

void loop() {
  if (pm.available()) {
    char cmd = pm.read();
    if (cmd == 'l') {
      pm.ledOff();
    }
    else if (cmd == 'L') {
      pm.ledOn();
    }
  }
  report();
  delay(1000);
}

void report() {
  Serial.print("Wetness: ");
  Serial.print(pm.getWater());
  Serial.print(" temp (C): ");
  Serial.print(pm.getTemp());
  Serial.print(" humidity: ");
  Serial.println(pm.getHumidity());
}

I hope someone can help that would enable me to learn more.
Thanks

Here is an example of declaring/defining serial port 2, assigning RX, TX pins and echoing to Serial on the ESP32.

Plenty of other examples (for Serial1, etc.) can be found on the web (e.g. search for "define serial ports ESP32"). Just about any free GPIO pins can be used for TX and RX.

//DEV MODULE selected for ESP32-CAM
// works as expected with UART2, RX=14, TX=15
#include <HardwareSerial.h>
HardwareSerial SerialPort(2); // use UART2
void setup()  
{
  Serial.begin(115200);
  while (!Serial);
  Serial.println("Serial test");
  SerialPort.begin(115200, SERIAL_8N1, 14, 15); 
} 
void loop()  
{ 
  while (Serial.available()) {
    SerialPort.write(Serial.read());
  }
  while (SerialPort.available()) {
    Serial.write(SerialPort.read());
  }
}

use a reference to the Stream class.

a private/protected reference to the object

  Stream &stream;                                        // a reference to the serial interface used to communicate with this device

and handover the Stream (for example Serial1) in the constructor in a initializer list.

PlantMonitor(Stream &stream) : stream(stream) {}

use stream instead of "_sensor" in the .cpp.

Stream is the parent class for HW Serial and SoftSerial - so you will still be able to use both with the new version.

2 Likes

Thank you @Delta_G and @noiasca I think I'm almost there. After compiling the following error is flagged:

In file included from D:\Data\Arduino\sketches\plant monitor\20240901_simple_v01\20240901_simple_v01.ino:1:
d:\Data\Arduino\libraries\plant_monitor/PlantMonitorRob.h: In constructor 'PlantMonitor::PlantMonitor(Stream&)':
d:\Data\Arduino\libraries\plant_monitor/PlantMonitorRob.h:25:48: error: expected '{' at end of input
   25 |   PlantMonitor(Stream &stream) : stream(stream);
      |                                                ^
Using library plant_monitor in folder: D:\Data\Arduino\libraries\plant_monitor (legacy)
exit status 1
Compilation error: exit status 1

The PlantMonitorRob,h file is:

#ifndef PlantMonitorRob_h         //  updated to avoid library conflicts
#define PlantMonitorRob_h

#include "Arduino.h"

class PlantMonitor
{
  private:
    Stream& stream;
  public:
    int getWater();
    float getTemp();
    float getHumidity();
    void ledOn();
    void ledOff();
    void begin();    // Add a begin method that you can call from setup

  PlantMonitor(Stream &stream) : stream(stream);  
};

#endif

The PlantMonitorRob.cpp file is:

#include "Arduino.h"
#include "PlantMonitorRob.h"

/*
PlantMonitor::PlantMonitor(&Serial1) : stream(&Serial1) {
  stream = HardwareSerial(&Serial1);
  stream.begin(9600);
}
*/

int PlantMonitor::getWater() {
  stream.print("w");
  while (! stream.read() == '=') {};
  return stream.parseInt();
}

float PlantMonitor::getTemp() {
  stream.print("t");
  while (! stream.read() == '=') {};
  return stream.parseFloat();
}

float PlantMonitor::getHumidity() {
  stream.print("h");
  while (! stream.read() == '=') {};
  return stream.parseFloat();
}

void PlantMonitor::ledOn() {
  stream.print("L");
}

void PlantMonitor::ledOff() {
  stream.print("l");
}

void PlantMonitor::begin() {
    stream.begin(115200); 
}

and the sketch is:

#include "PlantMonitorRob.h"
#include "Arduino.h"

PlantMonitor pm(Serial1);

void setup() {
  Serial.begin(115200);
  pm.begin();
}

void loop() {
  if (Serial1.available()) {
    char cmd = Serial1.read();
    if (cmd == 'l') {
      pm.ledOff();
    }
    else if (cmd == 'L') {
      pm.ledOn();
    }
  }
  report();
  delay(1000);
}

void report() {
  Serial.print("Wetness: ");
  Serial.print(pm.getWater());
  Serial.print(" temp (C): ");
  Serial.print(pm.getTemp());
  Serial.print(" humidity: ");
  Serial.println(pm.getHumidity());
}

I'm hoping it is simply a matter of minor typos.

@robpacker

you need to start Serial1 in maintab:

#include "PlantMonitorRob.h"
#include "Arduino.h"

PlantMonitor pm(Serial1);

void setup() {
  Serial.begin(115200);
  Serial1.begin(115200);
  pm.begin();
}

void loop() {
  if (Serial1.available()) {
    char cmd = Serial1.read();
    if (cmd == 'l') {
      pm.ledOff();
    }
    else if (cmd == 'L') {
      pm.ledOn();
    }
  }
  report();
  delay(1000);
}

void report() {
  Serial.print("Wetness: ");
  Serial.print(pm.getWater());
  Serial.print(" temp (C): ");
  Serial.print(pm.getTemp());
  Serial.print(" humidity: ");
  Serial.println(pm.getHumidity());
}

you have to correct the constructor in the .h:

// based on https://github.com/monkmakes/mm_plant_monitor
#ifndef PlantMonitorRob_h         //  updated to avoid library conflicts
#define PlantMonitorRob_h

#include "Arduino.h"

class PlantMonitor
{
  private:
    Stream& stream;
  public:
    int getWater();
    float getTemp();
    float getHumidity();
    void ledOn();
    void ledOff();
    void begin();    // Add a begin method that you can call from setup

  PlantMonitor(Stream &stream) : stream(stream) {}  
};

#endif

stream doesn't have a begin function - hence you can't use begin on the stream reference.
I just have commented it:

//based on https://github.com/monkmakes/mm_plant_monitor
#include "Arduino.h"
#include "PlantMonitorRob.h"

/*
PlantMonitor::PlantMonitor(&Serial1) : stream(&Serial1) {
  stream = HardwareSerial(&Serial1);
  stream.begin(9600);
}
*/

int PlantMonitor::getWater() {
  stream.print("w");
  while (! stream.read() == '=') {};
  return stream.parseInt();
}

float PlantMonitor::getTemp() {
  stream.print("t");
  while (! stream.read() == '=') {};
  return stream.parseFloat();
}

float PlantMonitor::getHumidity() {
  stream.print("h");
  while (! stream.read() == '=') {};
  return stream.parseFloat();
}

void PlantMonitor::ledOn() {
  stream.print("L");
}

void PlantMonitor::ledOff() {
  stream.print("l");
}

void PlantMonitor::begin() {
//    stream.begin(115200); 
}
//

please also activate the verbose output during compilation, there are several warnings regarding the cpp code.

When you dig deeper into the code consider to rewrite the hole thing based on

1 Like

Thank you @noiasca!
The plant monitor is working, and I have learned some things too. I have the link bookmarked in my current learning folder.

.

It would be nice if you could mark one answer as solution.

Furthermore you can say thank you to guys which helped you by pressing the like/heart under their post.

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