Отправка HTTP POST запросов с ESP32 через GSM модем SIM800L

В Интернет довольно много библиотек для работы с SIM800L с Arduino, однако при использовании их под ESP32/ESP8266 некоторые из них не компилируются, иные не поддерживают HardwareSerial, работая только через SoftwareSerial.

Я потратил немало времени на поиск стабильно работающего варианта. Пришлось поправить исходники нескольких библиотек. Правки залил на GitHub. Надеюсь, после CI вскоре эти фиксы будут имплементированы в основной код библиотек.

Подробно по подключению модуля SIM800L к ESP32 рассмотрено в предыдущей статье. При тестировании использовались модули:

  • SIM800L — оптимально приобретать Aliexpress.
  • MH-ET Live ESP32 Devkit 38 pins — также на Aliexpres.
  • DC-DC step down converter. В схеме использовался недорогой модуль Mini-360 за 0,3 $.

Библиотека TinyGSM

Из всего многообразия выбора мне приглянулась библиотека TinyGSM. До моих правок библиотека не компилировалась на ESP32 и ESP8266 выдавая сообщение об ошибке: cannot declare variable ‘client’ to be of abstract type ‘TinyGsmSim800::GsmClient’. Если поправить код, то компиляция проходит нормально.

Однако остается другая проблема. При передаче POST запроса под ESP32/ESP8266 используется штатная библиотека HTTPClient. Она при инициализации для отправки данных через GSM модем должна как-то принять Client (отнаследован от класса Stream) от него. Но передать GSMClient нельзя, поскольку библиотека может принимать только WiFiClient. И это проблема, поскольку библиотеку HTTPClient нельзя использовать не вписав вариант работы через GSMClient.

Однако, выход есть — использовать библиотеку ArduinoHttpClient. Она не компилируется под ESP32/ESP8266, однако я внес на GitHub нужные правки, так что, надеюсь, они будут приняты и станут доступны для всех страждущих. На всякий случай код, который нужно вписать в class HttpClient такой:

    #ifdef ESP32 || ESP8266
    virtual int connect(IPAddress ip, uint16_t port, int timeout)
    {
        throw "Method [virtual int connect(IPAddress ip, uint16_t port, int timeout)] has not implemented yet in HttpClient.h";
    }

    virtual int connect(const char *host, uint16_t port, int timeout)
    {
        throw "Method [virtual int connect(const char *host, uint16_t port, int timeout)] has not implemented yet in HttpClient.h";
    }
    #endif

После того как правки внесены в обе библиотеки они нормально компилируются и стабильно работают с SIM800L передавая телеметрию.

Пример кода

Прмер кода я залил на GitHub. Возможно, к моменту прочтения статьи он станет доступным в Examples\HttpClient_ESP32. На всякий случай дублирую здесь.

#include <ArduinoHttpClient.h>
#define TINY_GSM_MODEM_SIM800
#include <TinyGsmClient.h>

#define RXD2 16
#define TXD2 17

#define APN_NAME "internet.beeline.ru"
#define APN_USER "beeline"
#define APN_PSWD "beeline"

#define GSM_AUTOBAUD_MIN 9600
#define GSM_AUTOBAUD_MAX 38400

#define Max_Modem_Reboots 5

// set your cad pin (optional)
#define SIM_PIN ""

TinyGsm modemGSM(Serial2);

TinyGsmClient clientGSM(modemGSM);

String server = "andrey.fedorov.ru";
const int  port = 80;

int Modem_Reboots_Counter = 0;

HttpClient* http;

void RestartGSMModem()
{
    Serial.println("Restarting GSM...");
    if (!modemGSM.restart())
    {
      Serial.println("\tFailed. :-(\r\n");
      //ESP.restart();
    } 
    if (Modem_Reboots_Counter < Max_Modem_Reboots)
    {
      Init_GSM_SIM800();
    }
    Modem_Reboots_Counter++;
}

String GSMSignalLevel(int level)
{
  switch (level)
  {
    case 0:
      return "-115 dBm or less";
    case 1:
      return "-111 dBm";
    case 31:
      return "-52 dBm or greater";
    case 99:
      return "not known or not detectable";
    default:   
         if (level > 1 && level < 31)
           return "-110... -54 dBm";
  }
  return "Unknown";
}

String GSMRegistrationStatus(RegStatus state)
{
  switch (state)
  {
    case REG_UNREGISTERED:
      return "Not registered, MT is not currently searching a new operator to register to";
    case REG_SEARCHING:
      return "Not registered, but MT is currently searching a new operator to register to";
    case REG_DENIED:
      return "Registration denied";
    case REG_OK_HOME:
      return "Registered, home network";
    case REG_OK_ROAMING:
      return "Registered, roaming";
    case REG_UNKNOWN:
      return "Unknown"; 
  }
  return "Unknown";
}

String SIMStatus(SimStatus state)
{
  switch (state)
  {
    case SIM_ERROR:
      return "SIM card ERROR";
    case SIM_READY:
      return "SIM card is READY";
    case SIM_LOCKED:
      return "SIM card is LOCKED. PIN/PUK needed.";
  }
  return "Unknown STATUS";
}

String SwapLocation(String location)
{
   int i = location.indexOf(',');
   int j = location.indexOf(',', i+1);
   String longitude = location.substring(i+1, j); 
   i = location.indexOf(',', j+1);
   String latitude = location.substring(j+1, i); 
   return latitude + "," + longitude;
}

bool SendTextByPOST(String server, String url, String postData)
{
  bool stateGPRS = modemGSM.isGprsConnected();
  bool res = false;
  if (stateGPRS)
  {
    http = new HttpClient(clientGSM, server, port);
    Serial.println("Send POST request...");
    http->beginRequest();
    http->post(url);
    http->sendHeader("Content-Type", "application/x-www-form-urlencoded");
    http->sendHeader("Content-Length", postData.length());
    //http.sendHeader("X-Custom-Header", "custom-header-value");
    http->beginBody();
    http->print(postData);
    http->endRequest();

    // read the status code and body of the response
    int statusCode = http->responseStatusCode();
    if (statusCode == t_http_codes::HTTP_CODE_OK)
    {
      //String response = http->responseBody();
      Serial.println("Status code: " + String(statusCode));
      //Serial.println("Response: " + response);
      res = false;
    }
    else
    {
      Serial.println("Error code: " + String(statusCode));
      res = true;
    }
    
    http->stop();
   }
  return res;
}

void Init_GSM_SIM800() 
{
  Serial.println("Initialize GSM modem...");
  Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2);
  Serial.println("Serial GSM Txd is on GPIO" + String(TXD2));
  Serial.println("Serial GSM Rxd is on GPIO" + String(RXD2));

  //pinMode(LED_PIN, OUTPUT);

  delay(3000);

  TinyGsmAutoBaud(Serial2, GSM_AUTOBAUD_MIN, GSM_AUTOBAUD_MAX);

  String info = modemGSM.getModemInfo();
  
  Serial.println(info);
 
  if (!modemGSM.restart())
  {
    RestartGSMModem();
  }
  else
  {
    Serial.println("Modem restart OK");
  }

  if (modemGSM.getSimStatus() != SIM_READY)
  {
    Serial.println("Check PIN code for the SIM. SIM status: " + SimStatus(modemGSM.getSimStatus()));
    if (SIM_PIN != "")
    {
     Serial.println("Try to unlock SIM PIN.");
      modemGSM.simUnlock(SIM_PIN);
      delay(3000);
      if (modemGSM.getSimStatus() != 3)
      {
        RestartGSMModem();
      }
    }
  }
 
  if (!modemGSM.waitForNetwork()) 
  {
    Serial.println("Failed to connect to network");
    RestartGSMModem();
  }
  else
  {
    RegStatus registration = modemGSM.getRegistrationStatus();
    Serial.println("Registration: [" + GSMRegistrationStatus(registration) + "]"); 
    Serial.println("Modem network OK");
  }
  
  Serial.println(modemGSM.gprsConnect(APN_NAME,APN_USER,APN_PSWD) ? "GPRS Connect OK" : "GPRS Connection failed");

  bool stateGPRS = modemGSM.isGprsConnected();
  if (!stateGPRS)
  {
    RestartGSMModem();
  }
  
  String state = stateGPRS ? "connected" : "not connected";
  Serial.println("GPRS status: " + state);
  Serial.println("CCID: " + modemGSM.getSimCCID());
  Serial.println("IMEI: " + modemGSM.getIMEI());
  Serial.println("Operator: " + modemGSM.getOperator());

  IPAddress local = modemGSM.localIP();
  Serial.println("Local IP: " + local.toString());

  int csq = modemGSM.getSignalQuality();
  if (csq == 0)
  {
    Serial.println("Signal quality is 0. Restart modem.");
    RestartGSMModem();
  }
  Serial.println("Signal quality: " + GSMSignalLevel(csq) + " [" + String(csq) + "]");

  int battLevel = modemGSM.getBattPercent();
  Serial.println("Battery level: " + String(battLevel) + "%");

  float battVoltage = modemGSM.getBattVoltage() / 1000.0F;
  Serial.println("Battery voltage: " + String(battVoltage));

  String gsmLoc = modemGSM.getGsmLocation();
  Serial.println("GSM location: " + gsmLoc);
  Serial.println("GSM location: " + SwapLocation(gsmLoc));
}

void setup() 
{
  Serial.begin(9600);
  
  Init_GSM_SIM800();
}

void loop() 
{
  float h0 = 20 + (float)random(20)/10;
  float t0 = 30 + (float)random(30)/10;

  String postData = "IMEI=861349031663619&PTYPE=DUMP";
         postData += "&H0=" + String(h0); //.replace(',','.');
         postData += "&T0=" + String(t0); //.replace(',','.');

  Serial.println("Send data [" + postData + "] to [" + server + "].");
  SendTextByPOST(server, "/", postData);
  delay(60000*5);

}

Полезные ссылки

Spread the love
Запись опубликована в рубрике IT рецепты с метками , , , . Добавьте в закладки постоянную ссылку.