0

I am using GSM module SIM800 on Arduino and want to learn how to create my own custom library for it. I have been using TinyGSM library and trying to learn from it. I feel like this library is 10x overkill for my project needs and its very hard to follow because it has very many nested functions which keeps calling another functions over and over again which makes it nearly impossible to follow this code (atleast for begginer programmer as me).

I want to create a class constructor which would allow me to create GSM module object, and then I can use this object to use any functions that I declare within a class.

From the tinyGSM library, I think the object constructor is defined as:

public:
  explicit TinyGsmSim800(Stream& stream) : stream(stream) {
    memset(sockets, 0, sizeof(sockets));
  }

However, I am not able to fully understand why it is being declared in this manner?

  1. Why Stream instead of Serial?
  2. What are those sockets and why use memset?

This is going to be my first custom library that I am going to write and I dont have much experience with classes, hoping you guys could help me out.

I have created a cpp file where I am going to declare my constructor. I also want to create a send_AT_command function where I am going to pass a string of AT command that I want to send to my GSM modem. Can someone help me understand how to properly build a Constructor and how to further use it in my other functions?


#include "custom_modem.h"
#include "Arduino.h"


custom_modem :: custom_modem(Serial serial)
{

}

custom_modem :: send_AT_command(String command){

}

UPDATE2

I have made some progress. I have found another thread: Arduino: Calling serial.begin() on a Stream object

And I have simplified it further for my needs.

My header file:


#ifndef custom_modem_h
#define custom_modem_h

#include <Arduino.h>
#include <Stream.h>
#include <HardwareSerial.h>


#define MODEM_TX             27
#define MODEM_RX             26

 class custom_modem
 {
   public:
      custom_modem(HardwareSerial* serial);
      void start_modem();
      void send_AT_command(String command);

   private:
      Stream * _port;
       
//Stream& _serial;
 };


 #endif

My source file:

#include "custom_modem.h"
#include "Arduino.h"


custom_modem::custom_modem(HardwareSerial* serial)
{
    _port = serial;
}

void custom_modem::start_modem(){
    static_cast<HardwareSerial*>(_port)->begin(115200, SERIAL_8N1, MODEM_RX, MODEM_TX);
}

I am now able to create a class object and the program does not return any errors:

custom_modem modem(&SerialAT);

However, this code brings me a lot of questions:

1. As you can see from the source file, I am passing HardwareSerial* serial and then I assign this variable to

_port = serial

But in my header file, the _port is declared as Stream* variable. How does this work?

2. Why do I need use pointers as such: In my source file:

custom_modem::custom_modem(HardwareSerial* serial)

And in my header file:

custom_modem(HardwareSerial* serial);

Is it not better to pass it by reference? using HardwareSerial& ? What is the difference? Pointers are killing me I dont understand them very well

3. Why do I need to use this annoying cast?

    static_cast<HardwareSerial*>(_port)->begin(115200, SERIAL_8N1, MODEM_RX, MODEM_TX);

If I simply write:

 _port.begin(115200, SERIAL_8N1, MODEM_RX, MODEM_TX);

Its going to give me an error.

I would very much appreciate if you could help me answer those questions! Thanks

UPDATE3

I have developed a method for my class to send an AT command and return an answer:

String custom_modem::send_AT_command()
{
    String return_message; // Crate a string to store a return message
    static_cast<HardwareSerial*>(_port)->write("AT"); // Write an "AT" message to serial device. Expecting to receive "OK"
    delay(1);
     while( static_cast<HardwareSerial*>(_port)->available())
       {
          char c = static_cast<HardwareSerial*>(_port)->read(); // Read character at a time
          return_message.concat(c); // append a string with the character
       }
    return return_message;
}

However, When I call this in my arduino code:

String answer;
void loop() {
  answer = modem.send_AT_command();
  Serial.print("answer=");
  Serial.println(answer);
  delay(2000);
  // put your main code here, to run repeatedly:

}

It does not seem to return anything: Serial monitor image

I believe this could be an issue with the way I implement my send_AT_command function?

UPDATE4

Solved the AT command issue by adding a line termination /n/r. Now works fine.

When trying to initialise a class by referencing HardwareSerial, I still have problems:

My cpp:

#include "Arduino.h"
#include "String.h"


custom_modem::custom_modem(HardwareSerial* serial)
{
    _port = serial;
}

void custom_modem::start_modem(){
    _port->begin(115200, SERIAL_8N1, MODEM_RX, MODEM_TX);
}


String custom_modem::send_AT_command()
{
    String return_message; // Crate a string to store a return message
    _port->write("AT\n\r"); // Write an "AT" message to serial device. Expecting to receive "OK"
    delay(1);
     while(_port->available())
       {
          char c = _port->read(); // Read character at a time
          return_message.concat(c); // append a string with the character
       }
    return return_message;
}

and the header:

#ifndef custom_modem_h
#define custom_modem_h

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

#define MODEM_TX             27
#define MODEM_RX             26

 class custom_modem
 {
   public:
      custom_modem(HardwareSerial* serial);
      void start_modem();
      String send_AT_command();

   private:
      HardwareSerial* _port;
 };


 #endif

The code above works when I initialise class object as :

#define SerialAT  Serial1
custom_modem modem(&SerialAT);

Could you clarify how do I have to change the code to get it to work if I want to use HardwareSerial by referencing:

If I change both .cpp and header file :

HardwareSerial* serial

to

HardwareSerial& serial

It gives me an error. I believe it might have to do with the variable _port in my header file.:

 private:
      HardwareSerial* _port;

What would be the correct way to initialise it?

Lukas Petrikas
  • 65
  • 2
  • 11
  • Stream is an Arduino API base class for read/parse and write/print classes. `sockets` is an array and that memset initializes it to zeros at once. why don't you just use that library? – Juraj Apr 14 '21 at 06:36
  • Becuase I cannot understand most of it and it is very hard to follow. Sadly, the library community is not very active therefore they are not able to help me with some of the questions. I figured I would be better of developing my own little class for the GSM module and send AT command exactly how I want and what commands I want. Also, it is great opportunity to learn how to write my own library with class – Lukas Petrikas Apr 14 '21 at 06:52
  • The TinyGSM library follows the Arduino networking API. It works the same way as Arduino Ethernet and WiFi libraries EthernetClient/WiFiClient. it is definitely wort to learn the Arduino networking API instead of reinventing the wheel – Juraj Apr 14 '21 at 07:26
  • It just frustrates me that in order to setup modem it has to go through 10 different nested functions instead of just simply calling 1. One function calls another function and this another function calls another function and it keeps going. I almost feel like they have done this solely for the purpose so others wont be able to follow the code or reuse it. Im not experienced programmer but surely thats not the most efficient way to do that – Lukas Petrikas Apr 14 '21 at 07:41
  • it is a high quality Arduino library. it supports many modem types and the compiled result is small because the compiler can optimize it for the modem used – Juraj Apr 14 '21 at 09:10
  • I have updated my initial question regarding a arduino classes still hoping that someone might be able to answer a few questions for me. – Lukas Petrikas Apr 14 '21 at 09:15
  • @Juraj, the author is looking to learn, not to build a product. For that purpose reinventing the wheel is a wonderfully productive thing to do, as you figure out how stuff is built and why. – Tarmo Apr 14 '21 at 10:48
  • @Tarmo Yes that is the case! If I learn how to create my own class, next time I am dealing with libraries with classes it will be much easier for me to understand what is going on. – Lukas Petrikas Apr 14 '21 at 11:04
  • but this is a Q&A site, not a forum – Juraj Apr 14 '21 at 11:18
  • But these are questions :) – Tarmo Apr 14 '21 at 11:19
  • too many. https://stackoverflow.com/tour – Juraj Apr 14 '21 at 11:20
  • There's no hard limit posted. – Tarmo Apr 14 '21 at 11:24
  • So I am only allowed to make one question in a thread? Do you suggest I make 3 different threads if I have 3 relevant questions? – Lukas Petrikas Apr 14 '21 at 11:24

1 Answers1

1

Firstly I'd recommend picking up decent a C++ tutorial. This'll help you a lot as you're currently struggling with basic language concepts (e.g. pointers, references, inheritance) and you can't really do anything non-trivial without understanding those. Can't say I can recommend any recent materials, but Bruce Eckel's free "Thinking in C++" struck me as a newbie-friendly book. It's a bit outdated as it doesn't cover the latest standards - C++11/14/17/20 - but it goes over the language basics rather well.

  1. If you look at the code, HardwareSerial inherits from Stream. This makes it possible to implicitly (i.e. the compiler will do it without being told to) cast a pointer to child class into a pointer to parent class. Note that this has consequences: functions which are not declared virtual will be called from the parent class, not the child class.
    As for why TinyGSM uses Stream instead of a Serial - it's more universal. There can be other communication channels with a GSM module - perhaps a software emulated UART, USB-Serial interface, SPI bus, or something else completely. They will easily adapt the library code to support all those if they adhere to a more abstract interface.
    Note that you don't have to do that. Use HardwareSerial, it'll be easier.

  2. Learn pointers, you can't program C or C++ without. I'll not explain, it's a longer story. In reality reference almost is a pointer, it's just slightly friendlier to use. Feel free to use references in your code, I certainly would. Note that initializing a reference member of a class is done differently:

custom_modem::custom_modem(HardwareSerial& serial)
: _port(serial)
{ }
  1. The cast is needed because you're accessing a child class through a pointer to parent class. As I mentioned earlier, you're not ready to tackle this so just use HardwareSerial instead of Stream in your class.
Tarmo
  • 3,728
  • 1
  • 8
  • 25
  • Thank you for the answer. I have updated my question. Please could you check UPDATE4 from my initial question? I have put some additional question there regarding the initialising a reference member class – Lukas Petrikas Apr 14 '21 at 11:57
  • Look at the code in my answer. This is how you have to initialize class members which are references. – Tarmo Apr 14 '21 at 12:00
  • So in that case, I must get rid off the _port variable declaration in my header file Private: ? ```private: HardwareSerial* _port;``` – Lukas Petrikas Apr 14 '21 at 12:09
  • No, why? That stays as is. Just update the constructor definition. – Tarmo Apr 14 '21 at 15:29
  • Ok I was able to compile the code without errors when I remove the * sign : ```private: HardwareSerial _port;``` and also, I change``` _port->write()``` to ```port.write()``` – Lukas Petrikas Apr 14 '21 at 15:54