I wrote a sketch for ESP8266. This sketch reads some sensor data and published it via MQTT. In addition I want to let a Web server provide the same data as HTML, or JSON web service.
The MQTT publish is triggered via a TaskScheduler timer.
Both functions, MQTT and Web server, work for itself, but sadly not together. Here's a simplified sketch:
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <ESP8266WebServerSecure.h>
#include <PubSubClient.h>
#include <TaskScheduler.h>
#include <My_WLAN.h> // provices connection to local WLAN and network settings
const char DNS_NAME[] = "myserver.local";
const int HTTPS_PORT = 443; // HTTPS
const char MQTT_SVR[] = "myserver.local";
const unsigned int MQTT_PORT = 8883; // MQTTS
WiFiClientSecure wifiClient;
PubSubClient mqttClient(wifiClient); // MQTT client instance
ESP8266WebServerSecure server(HTTPS_PORT); // web server instance
void t1Callback(void); // callback method prototypes
Task t1(60000, TASK_FOREVER, &t1Callback); // main loop task
Scheduler timer; // task scheduler
static const uint8_t SVR_FINGERPRINT[20] PROGMEM = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20 };
static const char deviceCert[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
[... certificate ...]
-----END CERTIFICATE-----
)EOF";
static const char deviceKey[] PROGMEM = R"EOF(
-----BEGIN RSA PRIVATE KEY-----
[... key ...]
-----END RSA PRIVATE KEY-----
)EOF";
/* *****************************
MQTT_connect
* *****************************/
void MQTT_connect()
{
int attempt = 0;
/* loop until reconnected */
while (!mqttClient.connected() && attempt < 10) {
attempt++;
Serial.print("Attempting MQTT connection ("); Serial.print(attempt); Serial.print(")...");
mqttClient.setServer(MQTT_SVR, MQTT_PORT);
if (mqttClient.connect(DNS_NAME)) {
Serial.println("success");
} else {
Serial.print("failed, status code = "); Serial.print(mqttClient.state());
Serial.println(". - Try again in 5 seconds...");
delay(5000);
}
}
}
/* *****************************
Web Server handleRoot
* *****************************/
void handleRoot() {
digitalWrite(LED_BUILTIN, LOW); // on
Serial.println("WebServer ROOT");
server.send(200, "text/html", "WebServer ROOT");
digitalWrite(LED_BUILTIN, HIGH); // off
}
/* *****************************
Web Server handleNotFound
* *****************************/
void handleNotFound() {
digitalWrite(LED_BUILTIN, LOW); // on
String message = "File not found\n\n";
message += "URI: ";
message += server.uri();
message += "\nMethod: ";
message += (server.method() == HTTP_GET) ? "GET" : "POST";
message += "\nArguments: ";
message += server.args();
message += "\n";
for (uint8_t i = 0; i < server.args(); i++) {
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
}
server.send(404, "text/plain", message);
digitalWrite(LED_BUILTIN, HIGH); // off
}
/* *************************
MQTT_publish_something
* *************************/
void MQTT_publish_something() {
digitalWrite(LED_BUILTIN, LOW); // on
char payload[30] = "some_payload_data";
if (!mqttClient.publish("MQTT/Test", payload, true)) { // retain message
Serial.println("MQTT message lost!");
}
digitalWrite(LED_BUILTIN, HIGH); // off
}
/* *************************
t1: main timer (callback)
* *************************/
void t1Callback() {
my.WiFi_connect(); // check and re-connect to WLAN (in My_WLAN.h)
if (WiFi.status() == WL_CONNECTED) {
MQTT_connect();
MQTT_publish_something();
}
}
/* *************************
setup
* *************************/
void setup() {
pinMode(LED_BUILTIN, OUTPUT); // internal LED
digitalWrite(LED_BUILTIN, HIGH); // off
/* -----------------------
open Serial |
----------------------- */
Serial.begin(74880);
while (!Serial); // wait for Serial being ready
/* -----------------------
connect to WLAN |
----------------------- */
my.WiFi_connect(); // this is connecting to WLAN & error handling (in My_WLAN.h)
wifiClient.setFingerprint(SVR_FINGERPRINT);
/* -----------------------
set mDNS |
----------------------- */
if (MDNS.begin(DNS_NAME)) {
Serial.printf("mDNS responder started for %s\n", DNS_NAME);
MDNS.addService("https", "tcp", HTTPS_PORT); // add service to MDNS-SD
MDNS.addService("mqtt", "tcp", MQTT_PORT);
} else
Serial.println("Error setting up mDNS responder!");
/* -----------------------
start HTTPS server |
----------------------- */
server.getServer().setRSACert(new X509List(deviceCert), new PrivateKey(deviceKey));
server.on("/", handleRoot); // standard HTML root
server.onNotFound(handleNotFound);
server.begin();
Serial.println("HTTPS server started.");
Serial.println();
/* -----------------------
start timer |
----------------------- */
timer.init();
timer.addTask(t1);
// line 177:
timer.enableAll();
}
void loop() {
MDNS.update();
// line 184:
server.handleClient();
mqttClient.loop();
timer.execute();
}
Running MQTT only works fine and publishes data (I use the mosquitto broker). Running the Web server (https://...) works fine as well, if commenting out line 177 (so MQTT does not get triggered).
With both functions active, as soon as the first MQTT message had been sent, the web server does not answer any more. I get PR_END_OF_FILE_ERROR in FF and ERR_CONNECTION_CLOSED in Chrome.
I guess, that these libraries somehow mess with each other, or that something confuses with the certificates. However, the fingerprint belongs to the server running mosquitto, while the X509 certificate belongs to the web server running on the ESP8266. These are two different machines and have nothing to do with each other.
Any idea welcome.
I suspect both libraries use port 443, and you can only have one listener on a given port. I've tried creating a BearSSL::ESP8266WebServerSecure object with alternate ports, such as 80 and 8443 but can't get them to work. Worse, there doesn't seem to be a way to stop a listener once a BearSSL::ESP8266WebServerSecure object has started, so it can't be released for later reuse.
I ended up using HTTP to get WiFi credentials, then HTTPS from there on out. Not a very satisfactory solution but it works.
Update: I was able to run a provisioning server on port 443, stop it by calling
BearSSL::ESP8266WebServerSecure provisioningServer(443);
BearSSL::ESP8266WebServerSecure server(443);
provisioningServer.close();
provisioningServer.~ESP8266WebServerSecure(); // note: cannot use TLS on both servers without this line
After calling the provisioning server's destructor I was able to start my server on port 443.