Replacement for IFTTT for mailbox notifier

"Mailbox" here refers to the one the USPS carrier places snail-mail into.

Some years ago I did a mailbox notifier project, and I used IFTTT for it. I don't use the notifier anymore because I downsized into a condo with small ganged mailboxes. But I want to give it to a friend . The problem is that IFTTT is no longer free on anything you can actually do. So I need to find a different, but free, way to handle the notification.

The notifier uses an ESP8266 to connect via Wifi to my router. Then it connects over the internet to the IFTTT server, and provides the Webhooks key that I set up originally. IFTTT then sends a notification to the IFTTT app on my phone, which pops up that notice telling me the postman has arrived. Or, it used to.

If possible I'd like to stick with a phone notification, not an email or text message, or anything that has to be deleted.

Does anyone have recent experience doing something like this? I guess I really would like to find a free version of IFTTT that doesn't consider Webhooks a "pro feature" requiring payment. Any suggestions would be appreciated.

Try Pushover. Works great for me. Five bucks one-time fee.

Thanks very much. I'll check it out.

Dave, do you have an example sketch that works using wifi that you could post or send to me privately?

Sure. I'll do that tomorrow.

Here you go. As noted, the Pushover code is based on ESP8266 NodeMCU: Send Pushover Notifications (Arduino IDE) | Random Nerd Tutorials

/*
  deployedB.ino

  192.168.1.xxx

  garage root cellar ("inside box") w/ DS18B20 temp sensors
  if temp is too low -> push notification using Pushover
  
  Compiled with:
     Adafruit Feather ESP8266 Huzzah (even tho' just Adafruit ESP8266 breakout)
     4 MB (FS:2MB OTA:~1019kB)
     LwIP: v2 Higher bandwidth, no features
	 
  "Power on the controller then the servo." <-- from Customer Service Hitec RCD/Multiplex USA
*/

#define DEBUG true
#define Serial \
if (DEBUG) Serial

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ArduinoOTA.h>
#include "ThingSpeak.h"
#include <OneWire.h>
#include <DallasTemperature.h>
DeviceAddress insideT, outsideT;

//for Pushover
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecure.h>
#include <ArduinoJson.h>

//************
// pins
#define ONE_WIRE_BUS 12  // for Huzzah ESP8266
const uint8_t pinForFan = 14;
const uint8_t pinForServo = 5;
const uint8_t pinForLED = 0; // write 0 to turn on red LED (low=on)

//************
// servo
bool valveOpen;
uint16_t angleUS;  // valve angle in microseconds
//D85MG 850us to 2350us, 145 deg max range, 0.095 deg / us
//angles in degrees:
const uint8_t angleCloseClean = 18;   // over closed...for cleaning the pot
const uint8_t angleCloseNormal = 31;  // normal closed
const uint8_t angleOpenNormal = 135;  // normal open
const uint8_t angleOpenClean = 150;   // over open ...for cleaning the pot

//******************
// DS18B20 and temps
float TempCurrentInside, TempCurrentOutside, TempPrevious, TempPredicted;
float TempInMin = 99, TempOutMin = 99; // not used anymore...could delete
float TempInMax = -99, TempOutMax = -99;  // not used anymore...could delete
uint32_t lastTempRequestTime;

const uint8_t setpointHi = 36;  // turn on fan if above this
float setpointLo = 35.0;        // shut off if below this; changed on the fly depending on outside air temp

//******************
// ThingSpeak
// the below also used for outside cold box
unsigned long myChannelNumber = 123;
const char* myWriteAPIKey = "abc";

//************
// for tracking duration
uint32_t timeValveClosed;  // time valve closed (msec)

//************
// wifi
const char* ssid = "abc";
const char* password = "xyz";

//************
// objects
ESP8266WebServer server(80);
WiFiClient client;
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

//************
// PushOver
const char* apiToken = "abc";
const char* userToken = "abc";
const char* pushoverApiEndpoint = "https://api.pushover.net/1/messages.json";

// Pushover root certificate (valid from 11/10/2006 to 15/01/2038)
const char* PUSHOVER_ROOT_CA =
"-----BEGIN CERTIFICATE-----\n"
                               "MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh\n"
                               "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n"
                               "d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\n"
                               "MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT\n"
                               "MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n"
                               "b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG\n"
                               "9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI\n"
                               "2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx\n"
                               "1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ\n"
                               "q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz\n"
                               "tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ\n"
                               "vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP\n"
                               "BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV\n"
                               "5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY\n"
                               "1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4\n"
                               "NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG\n"
                               "Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91\n"
                               "8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe\n"
                               "pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl\n"
                               "MrY=\n"
                               "-----END CERTIFICATE-----\n";


// Create a list of certificates with the server certificate
X509List cert(PUSHOVER_ROOT_CA);

void setup() {

  Serial.begin(115200);

  pinMode(pinForServo, OUTPUT);
  pinMode(pinForFan, OUTPUT);
  pinMode(pinForLED, OUTPUT);

  digitalWrite(pinForLED, HIGH);  // turn LED on w/ LOW, off w/ HIGH

  //************
  // wifi
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  
  Serial.println();  Serial.print("IP:");  Serial.println(WiFi.localIP());

  //************
  // ThingSpeak
  ThingSpeak.begin(client);

  //************
  // DS18B20
  sensors.begin();
  sensors.getAddress(insideT,0);
  sensors.getAddress(outsideT,1);
  sensors.requestTemperatures();

  // using getTempF with address is supposedly faster than using getTempByIndex()
  TempCurrentInside = sensors.getTempF(insideT);
  TempCurrentOutside = sensors.getTempF(outsideT);
  Serial.println(TempCurrentInside,1);
  Serial.println(TempCurrentOutside,1);

  //sensors.setWaitForConversion(false); // no need to use the non-blocking way
  // doing two .getTempF in sequence, followed by requestTemperatures
  // takes about 27 ms
  // requestTemperatures alone takes 2 ms

  //************
  // exercise the valves and turn on LED while doing so
  digitalWrite(pinForLED, LOW);
  Serial.println("opening valve");
  openValves();
  delay(5000);
  Serial.println("closing valve");
  closeValves();
  delay(5000);
  Serial.println("cleaning servo");
  cleanServos();
  delay(1000);
  digitalWrite(pinForLED, HIGH);

  //************
  // webserver
  server.on("/", handleRoot);
  server.on("/clear", handleClear);  // clear TS max and min ... not used anymore
  server.on("/tmpnstate", handleTemps);  // show current in and out temps, and valve state
  server.on("/force", handleForce);  // force a new temp reading
  server.on("/cycle", handleCycle);  // cycle the valve
  server.on("/push", handlePush);    // send push via Pushover, for testing
  server.onNotFound(handleNotFound);
  server.begin();

  //************
  // For PushOver, set time via NTP, as required for x.509 validation

  configTime(3 *3600,0,"pool.ntp.org","time.nist.gov");

  Serial.print("Waiting for NTP time sync: ");
  time_t now = time(nullptr);

  while (now < 8 * 3600 * 2) {
    delay(500);
    Serial.print(".");
    now = time(nullptr);
  }
  
  Serial.println("");

  struct tm timeinfo;
  gmtime_r(&now, &timeinfo);

  Serial.print("Current time: ");
  Serial.print(asctime(&timeinfo));

  ArduinoOTA.begin();
}

//----------------------------------------------------------------------------

void loop() {

  ArduinoOTA.handle();
  server.handleClient();

  uint32_t currentTime = millis();

  //************
  // poke servo...doesn't appear to be needed...could delete
  const uint16_t intervalServo = 10000;  // every 10 secs
  static uint32_t prevTmServo = currentTime; // drifts, but so what

  if (currentTime - prevTmServo > intervalServo) {
    digitalWrite(pinForLED, LOW);
    prevTmServo = currentTime;
    delay(20);
    if (valveOpen)  // if was open, rewrite open normal angle
    {
      servoWrite(angleOpenNormal);
    } else  // if was closed, rewrite closed normal angle
    {
      servoWrite(angleCloseNormal);
    }
    digitalWrite(pinForLED, HIGH);
  }

  //************
  // get temps and take action
  const uint32_t interval = 120000;
  static uint32_t prevTime = currentTime;

  if (currentTime - prevTime > interval) {
    prevTime = currentTime;  
    checkWiFi();
    getTemps();
    moveServo();
  }

  //************
  // clean servo pot
  const uint32_t cleanInterval = 172800000;  // every two days...2x24x60x60x1000
  static uint32_t prevTmClean = currentTime;

  if (currentTime - prevTmClean > cleanInterval) {
    prevTmClean = currentTime;
    cleanServos();
  }

}  // end of loop


//----------------------------------------------------------------------------
void handleRoot() {

  server.send(200,"text/plain","garage root cellar esp8266 .xxx");
}

//----------------------------------------------------------------------------
void handleClear() { //not needed...
  server.send(200,"text/plain","Clearing mx/mn tmps in ESP8266...");
  TempInMin = 99;
  TempInMax = -99;
  TempOutMin = 99;
  TempOutMax = -99;
}

//----------------------------------------------------------------------------
void handleTemps() { //  /tmpnstate

  char myCstr[40];

  if (valveOpen) {
    sprintf(myCstr,"inside=%.1f outside=%.1f  valve open", TempCurrentInside, TempCurrentOutside);
  } else {
    sprintf(myCstr,"inside=%.1f outside=%.1f  valve closed", TempCurrentInside, TempCurrentOutside);
  }

  server.send(200,"text/plain", myCstr);
}

//----------------------------------------------------------------------------
void handleForce() {

  server.send(404,"text/plain","getting temps");
  getTemps();
}

//----------------------------------------------------------------------------
void handleCycle() {

  server.send(404,"text/plain","cycling valve");
  if (valveOpen)  // if was open, turn off fan and close
  {
    GPOC = (1 << pinForFan);  // turn fan off
    servoWrite(angleCloseNormal);
  } else  // if was closed, open (but don't turn fan on)
  {
    servoWrite(angleOpenNormal);
  }
  
  delay(1000);
  
  if (valveOpen)  // if was open, open and turn fan back
  {
    servoWrite(angleOpenNormal);
    GPOS = (1 << pinForFan);  // turn fan on
  } else                      // if was closed, close
  {
    servoWrite(angleCloseNormal);
  }
}

//----------------------------------------------------------------------------
void handlePush() {

  server.send(200,"text/plain","sending test push... inside cellar too cold");
  sendNotification();
}

//----------------------------------------------------------------------------
void handleNotFound() {

  server.send(404,"text/plain","404: Not found");
}

//----------------------------------------------------------------------------
// check wifi and retry
void checkWiFi() {

  if (WiFi.status() != WL_CONNECTED) {

    while (WiFi.status() != WL_CONNECTED) {
      WiFi.begin(ssid, password);
      delay(5000);
    }
  }
}

//----------------------------------------------------------------------------
void getTemps() {

  sensors.requestTemperatures();  //blocks for about 750 ms
  TempCurrentOutside = sensors.getTempF(outsideT);
  TempCurrentOutside += 1.03;  // calibrated at 32 deg F w/ mercury thermo
  Serial.print("outside = ");
  Serial.print(TempCurrentOutside);
  Serial.println("ºF");
  if (TempCurrentOutside < TempOutMin) TempOutMin = TempCurrentOutside;
  if (TempCurrentOutside > TempOutMax) TempOutMax = TempCurrentOutside;

  // if quite cold outside, then need warmer low setpoint, so fans shut off sooner
  if (TempCurrentOutside < 15) {
    setpointLo = 35.6;
  } else if (TempCurrentOutside > 18) {
    setpointLo = 35.0;
  }

  TempCurrentInside = sensors.getTempF(insideT);
  TempCurrentInside += 1.14;  // cal at 32 deg F w/ mercury thermo
  Serial.print("inside = ");
  Serial.print(TempCurrentInside);
  Serial.println("ºF");
  if (TempCurrentInside < TempInMin) TempInMin = TempCurrentInside;
  if (TempCurrentInside > TempInMax) TempInMax = TempCurrentInside;

  if (TempCurrentInside <= 32.0) sendNotification();

  //If fan is on (& valve open), then predict next temperature.
  //If predicted temp is less than low setpoint, turn motor off now (and close valve).

  if (valveOpen) {
    TempPredicted = 2 * TempCurrentInside - TempPrevious;
    //the above is based on the fact that if the change is linear,
    //the next change will be a decrease of (TempPrevious - TempCurrentInside)
    //so the predicted temp is the TempCurrentInside-(TempPrevious-TempCurrentInside)
    //or 2*TempCurrentInside-TempPrevious
    //E.g.: if prev=36.29 and current=35.72, then predict 35.17 and keep fan going
    //Or, if prev=36.29 and current=35.5, then predict 34.71 and shut off fan

    if (TempPredicted < setpointLo) {
      closeValves();
    } else {
      TempPrevious = TempCurrentInside;
    }
  }

  //ThingSpeak post takes 0.48 to 0.62 seconds

  ThingSpeak.setField(2, TempCurrentInside);
  ThingSpeak.setField(3, TempCurrentOutside);

  //ThingSpeak.setField(3, TempInMin); // not using max and min anymore
  //ThingSpeak.setField(4, TempInMax);
  //ThingSpeak.setField(5, TempOutMin);
  //ThingSpeak.setField(6, TempOutMax);
  //ThingSpeak.setField(7, valveOpen);

  ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey);

  //uint32_t endCount = ESP.getCycleCount();
  //float deltaMS = (endCount - startCount) / 80000;  // entire routine takes 1.3 secs, incl printing temps
  //Serial.println(deltaMS);
  Serial.println();
}

//----------------------------------------------------------------------------
void moveServo() {

  // http://www.ee.calpoly.edu/media/uploads/resources/KarnaughExplorer_1.html

  bool A = TempCurrentInside > setpointHi;                   // too warm
  bool B = TempCurrentInside < setpointLo;                   // too cold
  bool C = valveOpen;                                        // valves open
  bool D = TempCurrentOutside > TempCurrentInside;           // outside is warmer than inside
  bool E = (millis() - timeValveClosed) >  (20 * 60 * 1000); // more than 20 min since valves closed

  if ((C && D) || (B && C)) {
    closeValves();
  } else if ((A && !C && !D) || (A && !C && E)) {
    openValves();
  }
}

//----------------------------------------------------------------------------
void
openValves() {

  servoWrite(angleOpenNormal);  // set servo
  GPOS = (1 << pinForFan);      // turn fan on
  TempPrevious = TempCurrentInside;
  valveOpen = true;
}

//----------------------------------------------------------------------------
void
closeValves() {

  GPOC = (1 << pinForFan);       // turn fan off
  servoWrite(angleCloseNormal);  // set servo
  valveOpen = false;
  timeValveClosed = millis();
}

//----------------------------------------------------------------------------
void cleanServos() {

  servoWrite(angleCloseClean);
  delay(500);
  servoWrite(angleOpenClean);
  delay(500);

  if (valveOpen)  // if was open, leave open at normal angle
  {
    servoWrite(angleOpenNormal);
  } else  // and was closed, leave closed at normal angle
  {
    servoWrite(angleCloseNormal);
  }
}

//----------------------------------------------------------------------------
void servoWrite(uint8_t angle) {

  uint16_t angleUS = 850 + (angle * (2350 - 850)) / 180;  // 2350 - 850 = 1,500    1,500 x 150 max angle = 225,000 fits in ESP8266 int32_twon't fit in uint16_t
  GPOS = (1 << pinForServo);                              //set pin high; takes 0.06 uS or so, vs 1.5 uS for digitalWrite (not worth doing...but fun!)
  delayMicroseconds(angleUS);                             // not rec for delays > 20 mS (doesn't yield...); no problem here
  GPOC = (1 << pinForServo);                              // clear pin (set low) after appropriate pulse width
}

//----------------------------------------------------------------------------
// send PushOver notification
void sendNotification() {

  // Based on https://randomnerdtutorials.com/esp8266-nodemcu-pushover-arduino/
  // Make HTTPS POST request to send notification

  if (WiFi.status() == WL_CONNECTED) {
    // Create a JSON object with notification details
    // Check the API parameters: https://pushover.net/api

    StaticJsonDocument<512> notification;
    notification["token"] = apiToken;
    notification["user"] = userToken;
    notification["message"] ="blueFoamInsideIsCold";
    notification["title"] ="ESP8266 Notification";
    notification["url"] ="";
    notification["url_title"] ="";
    notification["html"] ="";
    notification["priority"] ="";
    notification["sound"] ="cosmic";
    notification["timestamp"] ="";

    // Serialize the JSON object to a string
    String jsonStringNotification;
    serializeJson(notification, jsonStringNotification);

    // Create a WiFiClientSecure object
    WiFiClientSecure client;

    // Set the certificate
    client.setTrustAnchors(&cert);

    // Create an HTTPClient object
    HTTPClient http;

    // Specify the target URL
    http.begin(client, pushoverApiEndpoint);

    // Add headers
    http.addHeader("Content-Type","application/json");

    // Send the POST request with the JSON data
    int httpResponseCode = http.POST(jsonStringNotification);

    // Check the response
    if (httpResponseCode > 0) {
      Serial.printf("HTTP response code: %d\n", httpResponseCode);
      String response = http.getString();
      Serial.println("Response:");
      Serial.println(response);
    } else {
      Serial.printf("HTTP response code: %d\n", httpResponseCode);
    }

    // Close the connection
    http.end();
  }
}

Thanks very much.

1 Like

Ok, I got it working. Thanks very much for your help.

For future reference, I want to report that I found a way to make an HTTPS connection without having to mess with the server's certificate, or having to get NTP time. Of course you would do this only if you don't mind taking the chance that your sensor data might be received by some site pretending to be your target server, or that someone might get your Pushover keys. Since I only have the mailbox event, I'm willing to take that risk in return for not having to update the certificate. (They are making noises now about requiring certs to be updated every seven days.)

The method uses BearSSL, which has a setInsecure() function. It still does HTTPS with TLS v1.2, but just doesn't check the validity of the server's certificate. Here's my code:

/*
This is a mailbox notifier sketch for a Lolin D1 Mini or Mini Pro.  It was
compiled using the Arduino IDE v1.8.8 with ESP8266 Core v2.5.2. The
Pushover app is used to send a notification to a smartphone.
*/

#include <ESP8266WiFi.h>                         //automatically includes BearSSL

#define ssid "mySSIDR"                           //SSID and PW of your router
#define password "myPassword"

const String userkey = "myUserKeyxxxxxxxxxxxxxxxxxxxxx";  //Pushover keys
const String appkey = "myAppKeyxxxxxxxxxxxxxxxxxxxxxx";

const String message = "Mailbox Opened";

const int API_TIMEOUT = 15000;
const int httpsPort = 443;
const int powerPin = D2;                         //power kept on by this pin
const char* server = "api.pushover.net";         //Pushover server
String pushParameters;                           //keys and message
bool result;

void setup() {
  pinMode(powerPin, OUTPUT);                     //keep power on
  digitalWrite(powerPin, HIGH);

  delay(5000);
  Serial.begin(57600);                           // all Serial ignored if no USB connection
  delay(100);

  if(connectToWifi()) {                          //if connect ok, send POST to Pushover
    makePushoverRequest();
  }
  WiFi.disconnect();

  Serial.println("Turning off power");
  delay(1000);
  digitalWrite(powerPin, LOW);                   //this will turn off power

  delay(60000);                                  //this completes only if power still on
  Serial.println("Power still on");
  delay(1000);
  ESP.deepSleep(0);                              //should never get this far
}

void loop(){
}

// Establish WiFi connection to the router

bool connectToWifi() {
  WiFi.disconnect();
  Serial.print("Connecting to: ");
  Serial.println(ssid);
  WiFi.mode(WIFI_STA);                           //connect as client
  WiFi.begin(ssid, password);                    //connect to router
  Serial.print("Attempting to connect: ");

  int i = 16;                                    //try to connect for 15 seconds
  while((WiFi.status() != WL_CONNECTED) && (i-- > 0)) {
    delay(1000);
    Serial.print(i);
    Serial.print(", ");
  }
  Serial.println();

  //print connection result
  if(WiFi.status() == WL_CONNECTED){
    Serial.println("Connected.");
    Serial.print("D1 Mini IP address: ");
    Serial.println(WiFi.localIP());
    result = true;
  }
  else {
    Serial.println("Connection failed - check your credentials or connection");
    result = false;
  }
  return result;
}

// Make an HTTPS request to the Pushover web service

void makePushoverRequest() {
  Serial.print("Connecting to ");
  Serial.print(server);
  pushParameters = "token=" + appkey + "&user=" + userkey + "&message=" + message;

  BearSSL::WiFiClientSecure client;

  for (int tries = 0; tries < 5; tries++) {      //try up to 5 times to connect
    client.setTimeout(API_TIMEOUT);
    client.setInsecure();                        //don't check certificate/fingerprint
    if(client.connect(server, httpsPort)) break; //exit FOR loop if connection
    Serial.print(".");                           //  else wait, try again
    delay(2000);
  }

  Serial.println();
  if(!client.connected()) {
    Serial.println("Failed to connect to server");
    client.stop();
    return;                                      //if no connection, bail out
  }
  Serial.println("Connected");                   //if good connection, send POST

    int length = pushParameters.length();
    Serial.println("Posting push notification");
    Serial.println(pushParameters);
    Serial.println(length);
    client.println("POST /1/messages.json HTTP/1.1");
    client.println("Host: api.pushover.net");
    client.println("Connection: close\r\nContent-Type: application/x-www-form-urlencoded");
    client.print("Content-Length: ");
    client.print(length);
    client.println("\r\n");
    client.print(pushParameters);

  int timeout = 50;                              //wait 5 seconds for a response
  while(!client.available() && (timeout-- > 0)){
    delay(100);
  }

  if(!client.available()) {
     Serial.println("No response to POST");
     client.stop();
     return;
  }
  String response;
  response = client.readStringUntil('\n');
  Serial.println(response);                      // should be 200 if all ok

  while(client.available()){
                                                 // comment one out:
    client.read();                               // skip rest of response, or
//    Serial.print(client.read());                 // print rest of response
// Edit:  should be Serial.print((char)client.read());
  }
  Serial.println("\nClosing connection");
  delay(1000);
  client.stop();
}

I should add that BearSSL is already included in the ESP8266WiFi.h library. But I don't think that's the case with ESP32. So you might have to install BearSSL separately there, and #include it.

1 Like

Thanks for that. The cert update bit me not long after I started using Pushover. If, eventually, it really does need to be updated every seven days, that would make Pushover worthless. I'm going to change all my sketches to use your approach.

1 Like

I got your approach to work with an ESP32, using this library

and replacing your client with the ssl_client in its HTTPs example.

Yes indeed. That all looks very familiar. So now I guess we have setInsecure() for pretty much any board that can use the Arduino IDE and can do SSL.

I took the testing a bit farther and found out that .readStringUntil caused the program to hang: unresponsive to OTA upload request, unresponsive to a simple myServer.on request (I added a simple server).

 String response;
  response = ssl_client.readStringUntil('\n');
  Serial.println(response);  // should be 200 if all ok

So I replaced the following from your sketch:

  String response;
  response = ssl_client.readStringUntil('\n');
  Serial.println(response);  // should be 200 if all ok
  
  // ... never finished the above.  Died at .readStringUntil

  while (ssl_client.available()) {
    // comment one out:
    //ssl_client.read();  // skip rest of response, or
    Serial.print(ssl_client.read());   // print rest of response
  }

with this from the Mobizt example:

  Serial.print("Read response...");

  while (ssl_client.available()) {
    Serial.print((char)ssl_client.read());
  }

And it worked.

The response was

which seemed odd (something to do with character encoding, I guess), but the push came to my phone, and that's the goal...

Edit to add: deleting the cast to char gives a response of 255, which apparently indicates abnormal termination, even tho' the push is successful.

@ShermanP , what response do you get? 200?

I don't think my code ever used "ssl_client.readStringUntil...".
It uses "client.readStringUntil...", "client" being what I used above in:

BearSSL::WiFiClientSecure client;

Maybe you're not reading from the channel you have open to the server, but from something else that may not exist. However, my code does have an error:

Serial.print(client.read());

should be:

Serial.print((char)client.read());

Yes. Here's the full response:

Connecting to: DATACENTER
Attempting to connect: 15, 14, 13, 12, 11, 10, 
Connected.
D1 Mini IP address: 192.168.1.13
Connecting to api.pushover.net
Connected
Sending push notification
HTTP/1.1 200 OK
Date: Sun, 19 Jan 2025 18:44:42 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: close
X-Frame-Options: SAMEORIGIN
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, OPTIONS
Access-Control-Allow-Headers: X-Requested-With, X-Prototype-Version, Origin, Accept, Content-Type, X-CSRF-Token, X-Pushover-App, Authorization
Access-Control-Max-Age: 1728000
X-Limit-App-Limit: 10000
X-Limit-App-Remaining: 9973
X-Limit-App-Reset: 1738389600
ETag: W/"aef96fe45df6c074933b8beffba0f887"
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: e810c4b0-bd01-41a5-b488-1623b37323e9
X-Runtime: 0.060147
Strict-Transport-Security: max-age=31536000
X-PO-H: a
cf-cache-status: DYNAMIC
Server: cloudflare
CF-RAY: 9048fa403adbd197-MCI

3d
{"status":1,"request":"e810c4b0-bd01-41a5-b488-1623b37323e9"}
0


Closing connection
Turning off power

Sorry for the confusion. No, your code didn't. I changed your "client" to "ssl_client":

/*
Test Pushover w/o its cert.  It works. 17 Jan 2025.

Tested with TinyPico 192.168.1.210

following the example in https://forum.arduino.cc/t/replacement-for-ifttt-for-mailbox-notifier/1339317/8

but with this library https://github.com/mobizt/ESP_SSLClient

and replacing shermanp's client with the mobizt ssl_client

*/

//#include <ESP8266WiFi.h> //automatically includes BearSSL for 8266
#include <WiFi.h>
#include <ESP_SSLClient.h>  // uses BearSSL...for ESP32
#include <ArduinoOTA.h>

#include <WebServer.h> 
WebServer myServer(80);

ESP_SSLClient ssl_client;
WiFiClient basic_client;

const char* ssid = "abc";
const char* password = "abc";

const String userkey = "abc";  //Pushover keys
const String appkey = "abc";

const String message = "test no cert";

const int API_TIMEOUT = 15000;
const int httpsPort = 443;
const char* server = "api.pushover.net";  //Pushover server
String pushParameters;                               //keys and message
bool result;

void setup() {

  Serial.begin(115200);  // all Serial ignored if no USB connection
  delay(5000);
  Serial.println("hello world");

  connectToWifi();

  Serial.println("success");

  // the following are needed to set up the Mobizt ssl_client

  // ignore server ssl certificate verification
  ssl_client.setInsecure();

  // Set the receive and transmit buffers size in bytes for memory allocation (512 to 16384).
  ssl_client.setBufferSizes(1024 /* rx */, 512 /* tx */);

  /** Call setDebugLevel(level) to set the debug
     * esp_ssl_debug_none = 0
     * esp_ssl_debug_error = 1
     * esp_ssl_debug_warn = 2
     * esp_ssl_debug_info = 3
     * esp_ssl_debug_dump = 4
     */
  ssl_client.setDebugLevel(1);

  // In case ESP32 WiFiClient, the session timeout should be set,
  // if the TCP session was kept alive because it was unable to detect the server disconnection.
#if defined(ESP32)
  ssl_client.setSessionTimeout(120);  // Set the timeout in seconds (>=120 seconds)
#endif

  // Due to the basic_client pointer is assigned, to avoid dangling pointer, basic_client should be existed
  // as long as it was used by ssl_client for transportation.
  ssl_client.setClient(&basic_client);


 // my stuff...
  myServer.on("/", handleRoot);
  myServer.on("/push", handlePush);

  ArduinoOTA.begin();
  myServer.begin();
}

void loop() {
  ArduinoOTA.handle();
  myServer.handleClient();
}

// Establish WiFi connection to the router

bool connectToWifi() {
  WiFi.disconnect();
  delay(1000);
  Serial.print("Connecting to: ");
  Serial.println(ssid);
  //WiFi.begin(ssid, password);                    //connect to router
  Serial.print("Attempting to connect. ");

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    delay(5000);
    abort();  // was ESP.restart(); // changed to abort() on 11 July 2022
  }

  //print connection result
  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("Connected.");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
    result = true;
  } else {
    Serial.println("Connection failed - check your credentials or connection");
    result = false;
  }
  return result;
}

void makePushoverRequest() {
  Serial.print("Connecting to ");
  Serial.print(server);
  pushParameters = "token=" + appkey + "&user=" + userkey + "&message=" + message;

  for (int tries = 0; tries < 10; tries++) {  //try up to 5 times to connect
    ssl_client.setTimeout(API_TIMEOUT);
    ssl_client.setInsecure();                          //don't check certificate/fingerprint
    if (ssl_client.connect(server, httpsPort)) break;  //exit FOR loop if connection
    Serial.print(".");                                 //  else wait, try again
    delay(2000);
  }

  Serial.println();
  if (!ssl_client.connected()) {
    Serial.println("Failed to connect to server");
    ssl_client.stop();
    return;  //if no connection, bail out
  }
  Serial.println("Connected");  //if good connection, send POST

  int length = pushParameters.length();
  Serial.println("Posting push notification");
  Serial.println(pushParameters);
  Serial.println(length);
  ssl_client.println("POST /1/messages.json HTTP/1.1");
  ssl_client.println("Host: api.pushover.net");
  ssl_client.println("Connection: close\r\nContent-Type: application/x-www-form-urlencoded");
  ssl_client.print("Content-Length: ");
  ssl_client.print(length);
  ssl_client.println("\r\n");
  ssl_client.print(pushParameters);

  int timeout = 50;  //wait 5 seconds for a response
  while (!ssl_client.available() && (timeout-- > 0)) {
    Serial.println(timeout);
    delay(100);
  }

  Serial.println("got here 1");

  if (!ssl_client.available()) {
    Serial.println("No response to POST");
    ssl_client.stop();
    return;
  }

  Serial.println("got here 2");  // got here, but didn't finish the following...

  /*

  String response;
  response = ssl_client.readStringUntil('\n');
  Serial.println(response);  // should be 200 if all ok
  
  // ... never finished the above.  Died at .readStringUntil

  Serial.println("got here 3");

  while (ssl_client.available()) {
    // comment one out:
    //ssl_client.read();  // skip rest of response, or
    Serial.print(ssl_client.read());   // print rest of response
  }

  Serial.println("got here 4");

  */

  // the following is from the Mobizt example
  Serial.println("Read response...");
  while (ssl_client.available()) {
    //Serial.print((char)ssl_client.read());  //this printed garbage; try removing the cast...
    Serial.print(ssl_client.read());  // prints 255, that's all
  }
  Serial.println();

  Serial.println("\nClosing connection");
  delay(1000);
  ssl_client.stop();

  Serial.println("got here 5");
}

void handlePush() {
  myServer.send(200, "text/plain", "sending test push...");
  makePushoverRequest();
}

void handleRoot() {
  myServer.send(200, "text/plain", "ESP32 test push w/o cert");
}

And the serial output:

hello world
Connecting to: Flipper
Attempting to connect. Connected.
IP address: 192.168.1.210
success
Connecting to api.pushover.net
Connected
Posting push notification
token=abc...
93
49
48
47
got here 1
got here 2
Read response...
255

Closing connection
got here 5

Maybe so, but I can't see that in the code! Maybe I'm missing something.

When I include the cast to char, I get a garbage symbol! See post #12.

I looked at the ESP8266, ESP32, and AVR cores and noticed a difference between their readStringUntil methods:

8266:

String Stream::readStringUntil(char terminator) {
    String ret;
    int c = timedRead();
    while(c >= 0 && c != terminator) {   //<<<<<< not cast to char
        ret += (char) c;
        c = timedRead();
    }
    return ret;
}

32:

String Stream::readStringUntil(char terminator) {
  String ret;
  int c = timedRead();
  while (c >= 0 && (char)c != terminator) {  // <<<<<< note the cast to char
    ret += (char)c;
    c = timedRead();
  }
  return ret;
}

AVR:

String Stream::readStringUntil(char terminator)
{
  String ret;
  int c = timedRead();
  while (c >= 0 && c != terminator)  // <<<<<<<< not cast to char
  {
    ret += (char)c;
    c = timedRead();
  }
  return ret;
}

The 32 code is the only one that has the cast to char:

(char) c != terminator

the other two use:

c !=terminator

I don't know enough about C++ to know whether that may be why my sketch hung at the readStringUntil. I will try changing the ESP32 core (delete the (char)), revert to your code (with the readStringUntil), and see what happens.

Edit to add: not tested yet, but it occurred to me that the timeout should stop it, regardless...

If the HTTPs.ino example works, I don't know why your code doesn't work. Have you actually run the example?

It appears from the "got here"s that the connection is staying open, but for some reason you aren't reading what's coming in, or the server is being told not to send anything. Or maybe basic_client is being shut down. Well, I'm just guessing.

You might want to pare your code down to the smallest version that doesn't work, and post an Issue to the Github page. Or send him an email. I don't have an ESP32, so I can't really test anything.

Edit: It seems if your code compiles, this wouldn't be the issue, but you never know:

https://www.youtube.com/watch?v=JdIHAY7UDOk

Thanks for the ideas. Getting slammed with work, so won't be able to check things out for a while.

I started over with the basic WiFiClientSecure example and got it to work with an ESP32 (TinyPico). "Work" means that I received the Pushover notification (as I did with the code in post #14) and I got a reasonable response from Pushover (which I didn't get with the previous code).

Example response:

{"status":1,"request":"35d624ae-c4b1-4161-9f6b-8f84f7b28cf6"}

The new code:

#include <WiFi.h>
#include <WiFiClientSecure.h>

WiFiClientSecure client;

const char* ssid = "abc";
const char* password = "xyz";

const String userkey = "123";
const String appkey = "789";

const String message = "test no cert";

const char* server = "api.pushover.net";

String pushParameters;

void setup() {

  Serial.begin(115200);
  delay(5000);
  Serial.println("hello world");

  connectToWifi();
  makePushoverRequest();
}

void loop() {
}

void connectToWifi() {

  WiFi.disconnect();
  delay(1000);
  Serial.print("Connecting to: "); Serial.println(ssid);
  Serial.print("Attempting to connect. ");

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    delay(5000);
    abort();  // was ESP.restart(); // changed to abort() on 11 July 2022
  }

  //print connection result
  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("Connected."); Serial.print("IP address: "); Serial.println(WiFi.localIP());
  } else {
    Serial.println("Connection failed.");
  }
}

void makePushoverRequest() {

  client.setInsecure();

  Serial.println("\nStarting connection to server...");

  if (!client.connect(server, 443))
    Serial.println("Connection failed.");
  else {
    Serial.println("Connected to server.");

    pushParameters = "token=" + appkey + "&user=" + userkey + "&message=" + message;
    int length = pushParameters.length();

    Serial.println("Posting push notification.");

    client.println("POST /1/messages.json HTTP/1.1");
    client.println("Host: api.pushover.net");
    client.println("Connection: close\r\nContent-Type: application/x-www-form-urlencoded");
    client.print("Content-Length: ");
    client.print(length);
    client.println("\r\n");
    client.print(pushParameters);

    while (client.connected()) {
      String line = client.readStringUntil('\n');
      if (line == "\r") {
        Serial.println("headers received");
        break;
      }
    }
    
    while (client.available()) {  // if there are incoming bytes available from the server, read them and print them:
      char c = client.read();
      Serial.write(c);
    }

    client.stop();
  }
}

Ok, so WifiClientSecure.h for the ESP32 has the setInsecure() function built in. I wondered whether in my ESP8266 version I really needed the BearSSL reference. So I changed:

BearSSL::WiFiClientSecure client;

to just:

WiFiClientSecure client;

And it works!!! So now I wonder if you really need both the WiFI.h and the WiFiClientSecure.h includes. The only #include I have is:

#include <ESP8266WiFi.h>

The other thing is you aren't serial printing the result of the readStringUntil() line. That's where you should get:

HTTP/1.1 200 OK

Anyway, it's great that setInsecure() works on the ESP32. We don't need no stinkin' certificates. :slight_smile:

Thanks for the follow-up and pointing out that I wasn't printing the results of readStringUntil(). I fixed that, and now I get the full response, including the "HTTP/1.1 200 OK".

My code wouldn't compile without adding the WiFi.h include. I thought that was odd but didn't dig into it.

:smile: