运行 ESP8266 上的 WebServerSecure 和 PubSubClient

Running WebServerSecure and PubSubClient on ESP8266

我为 ESP8266 写了一个草图。此草图读取一些传感器数据并通过 MQTT 发布。另外我想让Web服务器提供与HTML或JSON网络服务相同的数据。

MQTT 发布是通过 TaskScheduler 计时器触发的。

MQTT 和 Web 服务器这两个功能都可以独立工作,但遗憾的是不能一起使用。这是一个简化的草图:

#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();
}

运行 MQTT 只能正常工作并发布数据(我使用 mosquitto 代理)。 运行 Web 服务器 (https://...) 工作正常,如果注释掉第 177 行(因此 MQTT 不会被触发)。

在这两个功能都激活的情况下,一旦发送了第一条 MQTT 消息,Web 服务器就不再响应。我在 FF 中得到 PR_END_OF_FILE_ERROR,在 Chrome 中得到 ERR_CONNECTION_CLOSED。

我猜想,这些库以某种方式相互混淆,或者某些东西与证书混淆。但是,指纹属于服务器 运行 mosquitto,而 X509 证书属于 ESP8266 上的网络服务器 运行。这是两台不同的机器,彼此没有任何关系。

欢迎提出任何想法。

我怀疑这两个库都使用端口 443,并且在给定端口上只能有一个侦听器。我试过用备用端口(例如 80 和 8443)创建 BearSSL::ESP8266WebServerSecure 对象,但无法使它们工作。更糟糕的是,似乎没有办法在 BearSSL::ESP8266WebServerSecure 对象启动后停止侦听器,因此无法释放它供以后重用。

我最终使用 HTTP 获取 WiFi 凭据,然后从那里开始使用 HTTPS。不是一个非常令人满意的解决方案,但它有效。

更新:我能够 运行 端口 443 上的配置服务器,通过调用

停止它
  BearSSL::ESP8266WebServerSecure provisioningServer(443);
  BearSSL::ESP8266WebServerSecure server(443);

  provisioningServer.close();
  provisioningServer.~ESP8266WebServerSecure(); // note: cannot use TLS on both servers without this line

调用供应服务器的析构函数后,我能够在端口 443 上启动我的服务器。