chore: import local project into Gitea
This commit is contained in:
@@ -0,0 +1 @@
|
||||
BasedOnStyle: Google
|
||||
@@ -0,0 +1,2 @@
|
||||
.pio
|
||||
.vscode
|
||||
@@ -0,0 +1,25 @@
|
||||
; PlatformIO Project Configuration File
|
||||
;
|
||||
; Build options: build flags, source filter
|
||||
; Upload options: custom upload port, speed and extra flags
|
||||
; Library options: dependencies, extra library storages
|
||||
; Advanced options: extra scripting
|
||||
;
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[env:esp32dev]
|
||||
platform = espressif32@6.11.0
|
||||
board = esp32dev
|
||||
framework = arduino
|
||||
monitor_speed = 115200
|
||||
upload_speed = 921600
|
||||
build_flags =
|
||||
-DCORE_DEBUG_LEVEL=5
|
||||
-D WEBSOCKETS_NETWORK_TYPE=10
|
||||
-D TINY_GSM_MODEM_SIM7600
|
||||
lib_deps =
|
||||
digitaldragon/SSLClient@^1.3.2
|
||||
vshymanskyy/StreamDebugger@^1.0.1
|
||||
vshymanskyy/TinyGSM@^0.12.0
|
||||
symlink://../../..
|
||||
@@ -0,0 +1,3 @@
|
||||
This example project shows how to use a custom NetworkClient class to switch between two underlying network interfaces.
|
||||
|
||||
In this case it shows how a board can support Wi-Fi as well as GSM/LTE connectivity. It uses a shim to switch between the two network interfaces, thereby allowing a single binary to handle both interfaces without needing to reboot. This example can be extended to cover other network interfaces that conform to the Client() class interface.
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* main.cpp
|
||||
*
|
||||
* Created on: 15.06.2024
|
||||
*
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <WebSocketsClient.h>
|
||||
#include <WiFiMulti.h>
|
||||
|
||||
#include "network_client_impl.h"
|
||||
|
||||
WiFiMulti wifiMulti;
|
||||
WebSocketsClient webSocket;
|
||||
|
||||
#define USE_SERIAL Serial
|
||||
|
||||
void setClock() {
|
||||
configTime(0, 0, "pool.ntp.org", "time.nist.gov");
|
||||
|
||||
USE_SERIAL.print(F("Waiting for NTP time sync: "));
|
||||
time_t nowSecs = time(nullptr);
|
||||
while (nowSecs < 8 * 3600 * 2) {
|
||||
delay(500);
|
||||
USE_SERIAL.print(F("."));
|
||||
yield();
|
||||
nowSecs = time(nullptr);
|
||||
}
|
||||
|
||||
USE_SERIAL.println();
|
||||
struct tm timeinfo;
|
||||
gmtime_r(&nowSecs, &timeinfo);
|
||||
USE_SERIAL.print(F("Current time: "));
|
||||
USE_SERIAL.print(asctime(&timeinfo));
|
||||
}
|
||||
|
||||
void hexdump(const void *mem, uint32_t len, uint8_t cols = 16) {
|
||||
const uint8_t *src = (const uint8_t *)mem;
|
||||
USE_SERIAL.printf("\n[HEXDUMP] Address: 0x%08X len: 0x%X (%d)",
|
||||
(ptrdiff_t)src, len, len);
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
if (i % cols == 0) {
|
||||
USE_SERIAL.printf("\n[0x%08X] 0x%08X: ", (ptrdiff_t)src, i);
|
||||
}
|
||||
USE_SERIAL.printf("%02X ", *src);
|
||||
src++;
|
||||
}
|
||||
USE_SERIAL.printf("\n");
|
||||
}
|
||||
|
||||
void webSocketEvent(WStype_t type, uint8_t *payload, size_t length) {
|
||||
switch (type) {
|
||||
case WStype_DISCONNECTED:
|
||||
USE_SERIAL.printf("[WSc] Disconnected!\n");
|
||||
break;
|
||||
case WStype_CONNECTED:
|
||||
USE_SERIAL.printf("[WSc] Connected to url: %s\n", payload);
|
||||
|
||||
// send message to server when Connected
|
||||
webSocket.sendTXT("Connected");
|
||||
break;
|
||||
case WStype_TEXT:
|
||||
USE_SERIAL.printf("[WSc] get text: %s\n", payload);
|
||||
|
||||
// send message to server
|
||||
// webSocket.sendTXT("message here");
|
||||
break;
|
||||
case WStype_BIN:
|
||||
USE_SERIAL.printf("[WSc] get binary length: %u\n", length);
|
||||
hexdump(payload, length);
|
||||
|
||||
// send data to server
|
||||
// webSocket.sendBIN(payload, length);
|
||||
break;
|
||||
case WStype_ERROR:
|
||||
case WStype_FRAGMENT_TEXT_START:
|
||||
case WStype_FRAGMENT_BIN_START:
|
||||
case WStype_FRAGMENT:
|
||||
case WStype_FRAGMENT_FIN:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
USE_SERIAL.begin(115200);
|
||||
|
||||
USE_SERIAL.setDebugOutput(true);
|
||||
|
||||
USE_SERIAL.println();
|
||||
USE_SERIAL.println();
|
||||
USE_SERIAL.println();
|
||||
|
||||
for (uint8_t t = 4; t > 0; t--) {
|
||||
USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t);
|
||||
USE_SERIAL.flush();
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
wifiMulti.addAP("Berge", "BlauerHimmel!");
|
||||
|
||||
// WiFi.disconnect();
|
||||
while (wifiMulti.run() != WL_CONNECTED) {
|
||||
delay(100);
|
||||
}
|
||||
|
||||
// Call to enable WiFi interface to be used by the webSocketClient
|
||||
WebSocketsNetworkClient::Impl::enableWifi();
|
||||
|
||||
setClock();
|
||||
|
||||
// server address, port and URL. This server can be flakey.
|
||||
// Expected response: Request served by 0123456789abcdef
|
||||
webSocket.beginSSL("echo.websocket.org", 443, "/");
|
||||
|
||||
// event handler
|
||||
webSocket.onEvent(webSocketEvent);
|
||||
|
||||
// use HTTP Basic Authorization this is optional enable if needed
|
||||
// webSocket.setAuthorization("user", "Password");
|
||||
|
||||
// try ever 5000 again if connection has failed
|
||||
webSocket.setReconnectInterval(5000);
|
||||
}
|
||||
|
||||
void loop() { webSocket.loop(); }
|
||||
@@ -0,0 +1,148 @@
|
||||
#include "network_client_impl.h"
|
||||
|
||||
WebSocketsNetworkClient::WebSocketsNetworkClient()
|
||||
: _impl(new WebSocketsNetworkClient::Impl()) {}
|
||||
WebSocketsNetworkClient::WebSocketsNetworkClient(WiFiClient wifi_client)
|
||||
: _impl(new WebSocketsNetworkClient::Impl()) {}
|
||||
WebSocketsNetworkClient::~WebSocketsNetworkClient() {}
|
||||
|
||||
int WebSocketsNetworkClient::connect(IPAddress ip, uint16_t port) {
|
||||
if (_impl->gsm_client_) {
|
||||
return _impl->gsm_client_->connect(ip, port);
|
||||
} else if (_impl->wifi_client_) {
|
||||
return _impl->wifi_client_->connect(ip, port);
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WebSocketsNetworkClient::connect(const char *host, uint16_t port) {
|
||||
if (_impl->gsm_client_) {
|
||||
return _impl->gsm_client_->connect(host, port);
|
||||
} else if (_impl->wifi_client_) {
|
||||
return _impl->wifi_client_->connect(host, port);
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
return 0;
|
||||
}
|
||||
int WebSocketsNetworkClient::connect(const char *host, uint16_t port,
|
||||
int32_t timeout_ms) {
|
||||
if (_impl->gsm_client_) {
|
||||
return _impl->gsm_client_->connect(host, port, timeout_ms);
|
||||
} else if (_impl->wifi_client_) {
|
||||
return _impl->wifi_client_->connect(host, port, timeout_ms);
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t WebSocketsNetworkClient::write(uint8_t data) {
|
||||
if (_impl->gsm_client_) {
|
||||
return _impl->gsm_client_->write(data);
|
||||
} else if (_impl->wifi_client_) {
|
||||
return _impl->wifi_client_->write(data);
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t WebSocketsNetworkClient::write(const uint8_t *buf, size_t size) {
|
||||
Serial.printf("Send_: %zu\n", size);
|
||||
if (_impl->gsm_client_) {
|
||||
return _impl->gsm_client_->write(buf, size);
|
||||
} else if (_impl->wifi_client_) {
|
||||
return _impl->wifi_client_->write(buf, size);
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t WebSocketsNetworkClient::write(const char *str) {
|
||||
const int size = strlen(str);
|
||||
Serial.printf("Send: %zu\n", size);
|
||||
if (_impl->gsm_client_) {
|
||||
return _impl->gsm_client_->write((const uint8_t *)str, size);
|
||||
} else if (_impl->wifi_client_) {
|
||||
return _impl->wifi_client_->write((const uint8_t *)str, size);
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WebSocketsNetworkClient::available() {
|
||||
if (_impl->gsm_client_) {
|
||||
return _impl->gsm_client_->available();
|
||||
} else if (_impl->wifi_client_) {
|
||||
return _impl->wifi_client_->available();
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WebSocketsNetworkClient::read() {
|
||||
if (_impl->gsm_client_) {
|
||||
return _impl->gsm_client_->read();
|
||||
} else if (_impl->wifi_client_) {
|
||||
return _impl->wifi_client_->read();
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WebSocketsNetworkClient::read(uint8_t *buf, size_t size) {
|
||||
if (_impl->gsm_client_) {
|
||||
return _impl->gsm_client_->read(buf, size);
|
||||
} else if (_impl->wifi_client_) {
|
||||
return _impl->wifi_client_->read(buf, size);
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WebSocketsNetworkClient::peek() {
|
||||
if (_impl->gsm_client_) {
|
||||
return _impl->gsm_client_->peek();
|
||||
} else if (_impl->wifi_client_) {
|
||||
return _impl->wifi_client_->peek();
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WebSocketsNetworkClient::flush() {
|
||||
if (_impl->gsm_client_) {
|
||||
return _impl->gsm_client_->flush();
|
||||
} else if (_impl->wifi_client_) {
|
||||
return _impl->wifi_client_->flush();
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
}
|
||||
|
||||
void WebSocketsNetworkClient::stop() {
|
||||
if (_impl->gsm_client_) {
|
||||
return _impl->gsm_client_->stop();
|
||||
} else if (_impl->wifi_client_) {
|
||||
return _impl->wifi_client_->stop();
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
}
|
||||
|
||||
uint8_t WebSocketsNetworkClient::connected() {
|
||||
if (_impl->gsm_client_) {
|
||||
return _impl->gsm_client_->connected();
|
||||
} else if (_impl->wifi_client_) {
|
||||
return _impl->wifi_client_->connected();
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
WebSocketsNetworkClient::operator bool() {
|
||||
if (_impl->gsm_client_) {
|
||||
return _impl->gsm_client_->operator bool();
|
||||
} else if (_impl->wifi_client_) {
|
||||
return _impl->wifi_client_->operator bool();
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#include "network_client_impl.h"
|
||||
|
||||
std::unique_ptr<WiFiClient> WebSocketsNetworkClient::Impl::wifi_client_ = nullptr;
|
||||
std::unique_ptr<WiFiClientSecure> WebSocketsNetworkClient::Impl::wifi_client_secure_ =
|
||||
nullptr;
|
||||
std::unique_ptr<TinyGsmClient> WebSocketsNetworkClient::Impl::gsm_client_ = nullptr;
|
||||
std::unique_ptr<SSLClient> WebSocketsNetworkClient::Impl::gsm_client_secure_ = nullptr;
|
||||
const char *WebSocketsNetworkClient::Impl::no_interface_error_ = "No interface";
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <SSLClient.h>
|
||||
#include <TinyGSM.h>
|
||||
#include <WebSocketsNetworkClientSecure.h>
|
||||
#include <WiFiClientSecure.h>
|
||||
|
||||
struct WebSocketsNetworkClient::Impl {
|
||||
static void enableWifi() {
|
||||
// Do nothing if already enabled
|
||||
if (wifi_client_ && wifi_client_secure_) {
|
||||
return;
|
||||
}
|
||||
wifi_client_ = std::unique_ptr<WiFiClient>(new WiFiClient());
|
||||
wifi_client_secure_ =
|
||||
std::unique_ptr<WiFiClientSecure>(new WiFiClientSecure());
|
||||
}
|
||||
static void disableWifi() {
|
||||
wifi_client_ = nullptr;
|
||||
wifi_client_secure_ = nullptr;
|
||||
}
|
||||
static void enableGsm(TinyGsm* tiny_gsm) {
|
||||
// Do nothing if already enabled
|
||||
if (gsm_client_ && gsm_client_secure_) {
|
||||
return;
|
||||
}
|
||||
gsm_client_ = std::unique_ptr<TinyGsmClient>(new TinyGsmClient(*tiny_gsm));
|
||||
gsm_client_secure_ =
|
||||
std::unique_ptr<SSLClient>(new SSLClient(gsm_client_.get()));
|
||||
}
|
||||
static void disableGsm() {
|
||||
if (gsm_client_secure_) {
|
||||
gsm_client_secure_->stop();
|
||||
}
|
||||
gsm_client_secure_ = nullptr;
|
||||
gsm_client_ = nullptr;
|
||||
}
|
||||
|
||||
static std::unique_ptr<WiFiClient> wifi_client_;
|
||||
static std::unique_ptr<WiFiClientSecure> wifi_client_secure_;
|
||||
static std::unique_ptr<TinyGsmClient> gsm_client_;
|
||||
static std::unique_ptr<SSLClient> gsm_client_secure_;
|
||||
|
||||
static const char* no_interface_error_;
|
||||
};
|
||||
@@ -0,0 +1,200 @@
|
||||
#include "network_client_impl.h"
|
||||
|
||||
WebSocketsNetworkClientSecure::WebSocketsNetworkClientSecure() {}
|
||||
WebSocketsNetworkClientSecure::WebSocketsNetworkClientSecure(
|
||||
WiFiClient wifi_client) {}
|
||||
WebSocketsNetworkClientSecure::~WebSocketsNetworkClientSecure() {
|
||||
if (_impl->gsm_client_secure_) {
|
||||
_impl->gsm_client_secure_->stop();
|
||||
}
|
||||
}
|
||||
|
||||
int WebSocketsNetworkClientSecure::connect(IPAddress ip, uint16_t port) {
|
||||
if (_impl->gsm_client_secure_) {
|
||||
return _impl->gsm_client_secure_->connect(ip, port);
|
||||
} else if (_impl->wifi_client_secure_) {
|
||||
return _impl->wifi_client_secure_->connect(ip, port);
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WebSocketsNetworkClientSecure::connect(const char *host, uint16_t port) {
|
||||
if (_impl->gsm_client_secure_) {
|
||||
return _impl->gsm_client_secure_->connect(host, port);
|
||||
} else if (_impl->wifi_client_secure_) {
|
||||
return _impl->wifi_client_secure_->connect(host, port);
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
return 0;
|
||||
}
|
||||
int WebSocketsNetworkClientSecure::connect(const char *host, uint16_t port,
|
||||
int32_t timeout_ms) {
|
||||
if (_impl->gsm_client_secure_) {
|
||||
// Ignore timeout as will cause read() to block for specified time
|
||||
return _impl->gsm_client_secure_->connect(host, port);
|
||||
} else if (_impl->wifi_client_secure_) {
|
||||
return _impl->wifi_client_secure_->connect(host, port, timeout_ms);
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t WebSocketsNetworkClientSecure::write(uint8_t data) {
|
||||
if (_impl->gsm_client_secure_) {
|
||||
return _impl->gsm_client_secure_->write(data);
|
||||
} else if (_impl->wifi_client_secure_) {
|
||||
return _impl->wifi_client_secure_->write(data);
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t WebSocketsNetworkClientSecure::write(const uint8_t *buf, size_t size) {
|
||||
if (_impl->gsm_client_secure_) {
|
||||
return _impl->gsm_client_secure_->write(buf, size);
|
||||
} else if (_impl->wifi_client_secure_) {
|
||||
return _impl->wifi_client_secure_->write(buf, size);
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t WebSocketsNetworkClientSecure::write(const char *str) {
|
||||
const int size = strlen(str);
|
||||
if (_impl->gsm_client_secure_) {
|
||||
return _impl->gsm_client_secure_->write((const uint8_t *)str, size);
|
||||
} else if (_impl->wifi_client_secure_) {
|
||||
return _impl->wifi_client_secure_->write((const uint8_t *)str, size);
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WebSocketsNetworkClientSecure::available() {
|
||||
if (_impl->gsm_client_secure_) {
|
||||
return _impl->gsm_client_secure_->available();
|
||||
} else if (_impl->wifi_client_secure_) {
|
||||
return _impl->wifi_client_secure_->available();
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WebSocketsNetworkClientSecure::read() {
|
||||
if (_impl->gsm_client_secure_) {
|
||||
return _impl->gsm_client_secure_->read();
|
||||
} else if (_impl->wifi_client_secure_) {
|
||||
return _impl->wifi_client_secure_->read();
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WebSocketsNetworkClientSecure::read(uint8_t *buf, size_t size) {
|
||||
if (_impl->gsm_client_secure_) {
|
||||
return _impl->gsm_client_secure_->read(buf, size);
|
||||
} else if (_impl->wifi_client_secure_) {
|
||||
return _impl->wifi_client_secure_->read(buf, size);
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WebSocketsNetworkClientSecure::peek() {
|
||||
if (_impl->gsm_client_secure_) {
|
||||
return _impl->gsm_client_secure_->peek();
|
||||
} else if (_impl->wifi_client_secure_) {
|
||||
return _impl->wifi_client_secure_->peek();
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WebSocketsNetworkClientSecure::flush() {
|
||||
if (_impl->gsm_client_secure_) {
|
||||
return _impl->gsm_client_secure_->flush();
|
||||
} else if (_impl->wifi_client_secure_) {
|
||||
return _impl->wifi_client_secure_->flush();
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
}
|
||||
|
||||
void WebSocketsNetworkClientSecure::stop() {
|
||||
if (_impl->gsm_client_secure_) {
|
||||
return _impl->gsm_client_secure_->stop();
|
||||
} else if (_impl->wifi_client_secure_) {
|
||||
return _impl->wifi_client_secure_->stop();
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
}
|
||||
|
||||
uint8_t WebSocketsNetworkClientSecure::connected() {
|
||||
if (_impl->gsm_client_secure_) {
|
||||
return _impl->gsm_client_secure_->connected();
|
||||
} else if (_impl->wifi_client_secure_) {
|
||||
return _impl->wifi_client_secure_->connected();
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
WebSocketsNetworkClientSecure::operator bool() {
|
||||
if (_impl->gsm_client_secure_) {
|
||||
return _impl->gsm_client_secure_->operator bool();
|
||||
} else if (_impl->wifi_client_secure_) {
|
||||
return _impl->wifi_client_secure_->operator bool();
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WebSocketsNetworkClientSecure::setCACert(const char *rootCA) {
|
||||
if (_impl->gsm_client_secure_) {
|
||||
return _impl->gsm_client_secure_->setCertificate(rootCA);
|
||||
} else if (_impl->wifi_client_secure_) {
|
||||
return _impl->wifi_client_secure_->setCACert(rootCA);
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
}
|
||||
|
||||
#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 4)
|
||||
void WebSocketsNetworkClientSecure::setCACertBundle(const uint8_t *bundle,
|
||||
size_t bundle_size) {
|
||||
#else
|
||||
void WebSocketsNetworkClientSecure::setCACertBundle(const uint8_t *bundle) {
|
||||
#endif
|
||||
if (_impl->gsm_client_secure_) {
|
||||
return _impl->gsm_client_secure_->setCACertBundle(bundle);
|
||||
} else if (_impl->wifi_client_secure_) {
|
||||
#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 4)
|
||||
return _impl->wifi_client_secure_->setCACertBundle(bundle, bundle_size);
|
||||
#else
|
||||
return _impl->wifi_client_secure_->setCACertBundle(bundle);
|
||||
#endif
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
}
|
||||
|
||||
void WebSocketsNetworkClientSecure::setInsecure() {
|
||||
if (_impl->gsm_client_secure_) {
|
||||
_impl->gsm_client_secure_->setInsecure();
|
||||
} else if (_impl->wifi_client_secure_) {
|
||||
_impl->wifi_client_secure_->setInsecure();
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
}
|
||||
|
||||
bool WebSocketsNetworkClientSecure::verify(const char *fingerprint,
|
||||
const char *domain_name) {
|
||||
if (_impl->gsm_client_secure_) {
|
||||
// Simply calling SSLClient::verify() will break TLS handshake
|
||||
// Can be skipped as verification is done by SSLClient itself,
|
||||
// ArduinoWebSockets need not call it
|
||||
return true;
|
||||
} else if (_impl->wifi_client_secure_) {
|
||||
return _impl->wifi_client_secure_->verify(fingerprint, domain_name);
|
||||
}
|
||||
Serial.println(_impl->no_interface_error_);
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
*secret*
|
||||
!*secrets.hpp.template
|
||||
*x509_crt_bundle.bin
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,39 @@
|
||||
Owner,Common Name or Certificate Name
|
||||
Amazon Trust Services,Amazon Root CA 1
|
||||
Amazon Trust Services,Amazon Root CA 2
|
||||
Amazon Trust Services,Amazon Root CA 3
|
||||
Amazon Trust Services,Amazon Root CA 4
|
||||
Amazon Trust Services,Starfield Services Root Certificate Authority - G2
|
||||
DigiCert,Baltimore CyberTrust Root
|
||||
DigiCert,Cybertrust Global Root
|
||||
DigiCert,DigiCert Assured ID Root CA
|
||||
DigiCert,DigiCert Assured ID Root G2
|
||||
DigiCert,DigiCert Assured ID Root G3
|
||||
DigiCert,DigiCert Global Root CA
|
||||
DigiCert,DigiCert Global Root G2
|
||||
DigiCert,DigiCert Global Root G3
|
||||
DigiCert,DigiCert High Assurance EV Root CA
|
||||
DigiCert,DigiCert Trusted Root G4
|
||||
GlobalSign,GlobalSign ECC Root CA - R5
|
||||
GlobalSign,GlobalSign Root CA - R3
|
||||
GlobalSign,GlobalSign Root CA - R6
|
||||
GlobalSign,GlobalSign Root CA
|
||||
GoDaddy,Go Daddy Class 2 CA
|
||||
GoDaddy,Go Daddy Root Certificate Authority - G2
|
||||
GoDaddy,Starfield Class 2 CA
|
||||
GoDaddy,Starfield Root Certificate Authority - G2
|
||||
Google Trust Services LLC (GTS),GlobalSign ECC Root CA - R4
|
||||
Google Trust Services LLC (GTS),GlobalSign Root CA - R2
|
||||
Google Trust Services LLC (GTS),GTS Root R1
|
||||
Google Trust Services LLC (GTS),GTS Root R2
|
||||
Google Trust Services LLC (GTS),GTS Root R3
|
||||
Google Trust Services LLC (GTS),GTS Root R4
|
||||
"IdenTrust Services, LLC",DST Root CA X3
|
||||
"IdenTrust Services, LLC",IdenTrust Commercial Root CA 1
|
||||
"IdenTrust Services, LLC",IdenTrust Public Sector Root CA 1
|
||||
Sectigo,Comodo AAA Services root
|
||||
Sectigo,COMODO Certification Authority
|
||||
Sectigo,COMODO ECC Certification Authority
|
||||
Sectigo,COMODO RSA Certification Authority
|
||||
Sectigo,USERTrust ECC Certification Authority
|
||||
Sectigo,USERTrust RSA Certification Authority
|
||||
|
@@ -0,0 +1,227 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# ESP32 x509 certificate bundle generation utility
|
||||
#
|
||||
# Converts PEM and DER certificates to a custom bundle format which stores just the
|
||||
# subject name and public key to reduce space
|
||||
#
|
||||
# The bundle will have the format: number of certificates; crt 1 subject name length; crt 1 public key length;
|
||||
# crt 1 subject name; crt 1 public key; crt 2...
|
||||
#
|
||||
# Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http:#www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import os
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
from io import open
|
||||
|
||||
try:
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
except ImportError:
|
||||
print('The cryptography package is not installed.'
|
||||
'Please refer to the Get Started section of the ESP-IDF Programming Guide for '
|
||||
'setting up the required packages.')
|
||||
raise
|
||||
|
||||
ca_bundle_bin_file = 'x509_crt_bundle'
|
||||
|
||||
quiet = False
|
||||
|
||||
|
||||
def status(msg):
|
||||
""" Print status message to stderr """
|
||||
if not quiet:
|
||||
critical(msg)
|
||||
|
||||
|
||||
def critical(msg):
|
||||
""" Print critical message to stderr """
|
||||
sys.stderr.write('gen_crt_bundle.py: ')
|
||||
sys.stderr.write(msg)
|
||||
sys.stderr.write('\n')
|
||||
|
||||
|
||||
class CertificateBundle:
|
||||
def __init__(self):
|
||||
self.certificates = []
|
||||
self.compressed_crts = []
|
||||
|
||||
if os.path.isfile(ca_bundle_bin_file):
|
||||
os.remove(ca_bundle_bin_file)
|
||||
|
||||
def add_from_path(self, crts_path):
|
||||
|
||||
found = False
|
||||
for file_path in os.listdir(crts_path):
|
||||
found |= self.add_from_file(os.path.join(crts_path, file_path))
|
||||
|
||||
if found is False:
|
||||
raise InputError('No valid x509 certificates found in %s' % crts_path)
|
||||
|
||||
def add_from_file(self, file_path):
|
||||
try:
|
||||
if file_path.endswith('.pem'):
|
||||
status('Parsing certificates from %s' % file_path)
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
crt_str = f.read()
|
||||
self.add_from_pem(crt_str)
|
||||
return True
|
||||
|
||||
elif file_path.endswith('.der'):
|
||||
status('Parsing certificates from %s' % file_path)
|
||||
with open(file_path, 'rb') as f:
|
||||
crt_str = f.read()
|
||||
self.add_from_der(crt_str)
|
||||
return True
|
||||
|
||||
except ValueError:
|
||||
critical('Invalid certificate in %s' % file_path)
|
||||
raise InputError('Invalid certificate')
|
||||
|
||||
return False
|
||||
|
||||
def add_from_pem(self, crt_str):
|
||||
""" A single PEM file may have multiple certificates """
|
||||
|
||||
crt = ''
|
||||
count = 0
|
||||
start = False
|
||||
|
||||
for strg in crt_str.splitlines(True):
|
||||
if strg == '-----BEGIN CERTIFICATE-----\n' and start is False:
|
||||
crt = ''
|
||||
start = True
|
||||
elif strg == '-----END CERTIFICATE-----\n' and start is True:
|
||||
crt += strg + '\n'
|
||||
start = False
|
||||
self.certificates.append(x509.load_pem_x509_certificate(crt.encode(), default_backend()))
|
||||
count += 1
|
||||
if start is True:
|
||||
crt += strg
|
||||
|
||||
if(count == 0):
|
||||
raise InputError('No certificate found')
|
||||
|
||||
status('Successfully added %d certificates' % count)
|
||||
|
||||
def add_from_der(self, crt_str):
|
||||
self.certificates.append(x509.load_der_x509_certificate(crt_str, default_backend()))
|
||||
status('Successfully added 1 certificate')
|
||||
|
||||
def create_bundle(self):
|
||||
# Sort certificates in order to do binary search when looking up certificates
|
||||
self.certificates = sorted(self.certificates, key=lambda cert: cert.subject.public_bytes(default_backend()))
|
||||
|
||||
bundle = struct.pack('>H', len(self.certificates))
|
||||
|
||||
for crt in self.certificates:
|
||||
""" Read the public key as DER format """
|
||||
pub_key = crt.public_key()
|
||||
pub_key_der = pub_key.public_bytes(serialization.Encoding.DER, serialization.PublicFormat.SubjectPublicKeyInfo)
|
||||
|
||||
""" Read the subject name as DER format """
|
||||
sub_name_der = crt.subject.public_bytes(default_backend())
|
||||
|
||||
name_len = len(sub_name_der)
|
||||
key_len = len(pub_key_der)
|
||||
len_data = struct.pack('>HH', name_len, key_len)
|
||||
|
||||
bundle += len_data
|
||||
bundle += sub_name_der
|
||||
bundle += pub_key_der
|
||||
|
||||
return bundle
|
||||
|
||||
def add_with_filter(self, crts_path, filter_path):
|
||||
|
||||
filter_set = set()
|
||||
with open(filter_path, 'r', encoding='utf-8') as f:
|
||||
csv_reader = csv.reader(f, delimiter=',')
|
||||
|
||||
# Skip header
|
||||
next(csv_reader)
|
||||
for row in csv_reader:
|
||||
filter_set.add(row[1])
|
||||
|
||||
status('Parsing certificates from %s' % crts_path)
|
||||
crt_str = []
|
||||
with open(crts_path, 'r', encoding='utf-8') as f:
|
||||
crt_str = f.read()
|
||||
|
||||
# Split all certs into a list of (name, certificate string) tuples
|
||||
pem_crts = re.findall(r'(^.+?)\n(=+\n[\s\S]+?END CERTIFICATE-----\n)', crt_str, re.MULTILINE)
|
||||
|
||||
filtered_crts = ''
|
||||
for name, crt in pem_crts:
|
||||
if name in filter_set:
|
||||
filtered_crts += crt
|
||||
|
||||
self.add_from_pem(filtered_crts)
|
||||
|
||||
|
||||
class InputError(RuntimeError):
|
||||
def __init__(self, e):
|
||||
super(InputError, self).__init__(e)
|
||||
|
||||
|
||||
def main():
|
||||
global quiet
|
||||
|
||||
parser = argparse.ArgumentParser(description='ESP-IDF x509 certificate bundle utility')
|
||||
|
||||
parser.add_argument('--quiet', '-q', help="Don't print non-critical status messages to stderr", action='store_true')
|
||||
parser.add_argument('--input', '-i', nargs='+', required=True,
|
||||
help='Paths to the custom certificate folders or files to parse, parses all .pem or .der files')
|
||||
parser.add_argument('--filter', '-f', help='Path to CSV-file where the second columns contains the name of the certificates \
|
||||
that should be included from cacrt_all.pem')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
quiet = args.quiet
|
||||
|
||||
bundle = CertificateBundle()
|
||||
|
||||
for path in args.input:
|
||||
if os.path.isfile(path):
|
||||
if os.path.basename(path) == 'cacrt_all.pem' and args.filter:
|
||||
bundle.add_with_filter(path, args.filter)
|
||||
else:
|
||||
bundle.add_from_file(path)
|
||||
elif os.path.isdir(path):
|
||||
bundle.add_from_path(path)
|
||||
else:
|
||||
raise InputError('Invalid --input=%s, is neither file nor folder' % args.input)
|
||||
|
||||
status('Successfully added %d certificates in total' % len(bundle.certificates))
|
||||
|
||||
crt_bundle = bundle.create_bundle()
|
||||
|
||||
with open(ca_bundle_bin_file, 'wb') as f:
|
||||
f.write(crt_bundle)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
main()
|
||||
except InputError as e:
|
||||
print(e)
|
||||
sys.exit(2)
|
||||
@@ -0,0 +1,46 @@
|
||||
|
||||
This directory is intended for project specific (private) libraries.
|
||||
PlatformIO will compile them to static libraries and link into executable file.
|
||||
|
||||
The source code of each library should be placed in a an own separate directory
|
||||
("lib/your_library_name/[here are source files]").
|
||||
|
||||
For example, see a structure of the following two libraries `Foo` and `Bar`:
|
||||
|
||||
|--lib
|
||||
| |
|
||||
| |--Bar
|
||||
| | |--docs
|
||||
| | |--examples
|
||||
| | |--src
|
||||
| | |- Bar.c
|
||||
| | |- Bar.h
|
||||
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||
| |
|
||||
| |--Foo
|
||||
| | |- Foo.c
|
||||
| | |- Foo.h
|
||||
| |
|
||||
| |- README --> THIS FILE
|
||||
|
|
||||
|- platformio.ini
|
||||
|--src
|
||||
|- main.c
|
||||
|
||||
and a contents of `src/main.c`:
|
||||
```
|
||||
#include <Foo.h>
|
||||
#include <Bar.h>
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
PlatformIO Library Dependency Finder will find automatically dependent
|
||||
libraries scanning project source files.
|
||||
|
||||
More information about PlatformIO Library Dependency Finder
|
||||
- https://docs.platformio.org/page/librarymanager/ldf.html
|
||||
@@ -0,0 +1,25 @@
|
||||
; PlatformIO Project Configuration File
|
||||
;
|
||||
; Build options: build flags, source filter
|
||||
; Upload options: custom upload port, speed and extra flags
|
||||
; Library options: dependencies, extra library storages
|
||||
; Advanced options: extra scripting
|
||||
;
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[env:esp32dev]
|
||||
platform = espressif32
|
||||
board = esp32dev
|
||||
framework = arduino
|
||||
monitor_speed = 115200
|
||||
upload_speed = 921600
|
||||
build_flags =
|
||||
-DCORE_DEBUG_LEVEL=5
|
||||
lib_deps = ../../../src
|
||||
|
||||
extra_scripts =
|
||||
pre:run_gen_script.py
|
||||
|
||||
board_build.embed_txtfiles =
|
||||
data/cert/x509_crt_bundle.bin
|
||||
@@ -0,0 +1,12 @@
|
||||
This is a PlatformIO project that uses a modified WiFiClientSecure library (in `lib`) to
|
||||
implement proper SSL support using root certificates as discussed
|
||||
[here](https://github.com/espressif/arduino-esp32/issues/3646#issuecomment-648292677)
|
||||
|
||||
It is based on the work by [meltdonw03](https://github.com/meltdown03) in that thread, and the
|
||||
[BasicHttpsClient example](https://github.com/espressif/arduino-esp32/blob/1.0.4/libraries/HTTPClient/examples/BasicHttpsClient/BasicHttpsClient.ino) from the arduino-esp32 project.
|
||||
|
||||
Just copy `include/secrets.hpp.template` to `include/secrets.hpp` and fill in your WiFi details.
|
||||
Then it should be pretty much ready to go. The local WiFiClientSecure library should take priority.
|
||||
Debug is set to verbose, so you'll see a lot of noise, but there should also be this readme on success :)
|
||||
|
||||
To get a current CA cert bundle download it from [curl's website](https://curl.se/docs/caextract.html).
|
||||
@@ -0,0 +1,6 @@
|
||||
Import("env")
|
||||
|
||||
env.Execute("$PYTHONEXE -m pip install cryptography")
|
||||
env.Execute("$PYTHONEXE gen_crt_bundle.py --input cacrt_all.pem")
|
||||
env.Execute("mkdir -p data/cert")
|
||||
env.Execute("mv -f x509_crt_bundle data/cert/x509_crt_bundle.bin")
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* main.cpp
|
||||
*
|
||||
* Created on: 15.06.2024
|
||||
*
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#include <WiFiMulti.h>
|
||||
|
||||
#include <WebSocketsClient.h>
|
||||
|
||||
extern const uint8_t rootca_crt_bundle_start[] asm(
|
||||
"_binary_data_cert_x509_crt_bundle_bin_start");
|
||||
|
||||
WiFiMulti wifiMulti;
|
||||
WebSocketsClient webSocket;
|
||||
|
||||
#define USE_SERIAL Serial
|
||||
|
||||
void setClock() {
|
||||
configTime(0, 0, "pool.ntp.org", "time.nist.gov");
|
||||
|
||||
USE_SERIAL.print(F("Waiting for NTP time sync: "));
|
||||
time_t nowSecs = time(nullptr);
|
||||
while(nowSecs < 8 * 3600 * 2) {
|
||||
delay(500);
|
||||
USE_SERIAL.print(F("."));
|
||||
yield();
|
||||
nowSecs = time(nullptr);
|
||||
}
|
||||
|
||||
USE_SERIAL.println();
|
||||
struct tm timeinfo;
|
||||
gmtime_r(&nowSecs, &timeinfo);
|
||||
USE_SERIAL.print(F("Current time: "));
|
||||
USE_SERIAL.print(asctime(&timeinfo));
|
||||
}
|
||||
|
||||
void hexdump(const void * mem, uint32_t len, uint8_t cols = 16) {
|
||||
const uint8_t * src = (const uint8_t *)mem;
|
||||
USE_SERIAL.printf("\n[HEXDUMP] Address: 0x%08X len: 0x%X (%d)", (ptrdiff_t)src, len, len);
|
||||
for(uint32_t i = 0; i < len; i++) {
|
||||
if(i % cols == 0) {
|
||||
USE_SERIAL.printf("\n[0x%08X] 0x%08X: ", (ptrdiff_t)src, i);
|
||||
}
|
||||
USE_SERIAL.printf("%02X ", *src);
|
||||
src++;
|
||||
}
|
||||
USE_SERIAL.printf("\n");
|
||||
}
|
||||
|
||||
void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
|
||||
switch(type) {
|
||||
case WStype_DISCONNECTED:
|
||||
USE_SERIAL.printf("[WSc] Disconnected!\n");
|
||||
break;
|
||||
case WStype_CONNECTED:
|
||||
USE_SERIAL.printf("[WSc] Connected to url: %s\n", payload);
|
||||
|
||||
// send message to server when Connected
|
||||
webSocket.sendTXT("Connected");
|
||||
break;
|
||||
case WStype_TEXT:
|
||||
USE_SERIAL.printf("[WSc] get text: %s\n", payload);
|
||||
|
||||
// send message to server
|
||||
// webSocket.sendTXT("message here");
|
||||
break;
|
||||
case WStype_BIN:
|
||||
USE_SERIAL.printf("[WSc] get binary length: %u\n", length);
|
||||
hexdump(payload, length);
|
||||
|
||||
// send data to server
|
||||
// webSocket.sendBIN(payload, length);
|
||||
break;
|
||||
case WStype_ERROR:
|
||||
case WStype_FRAGMENT_TEXT_START:
|
||||
case WStype_FRAGMENT_BIN_START:
|
||||
case WStype_FRAGMENT:
|
||||
case WStype_FRAGMENT_FIN:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
USE_SERIAL.begin(115200);
|
||||
|
||||
USE_SERIAL.setDebugOutput(true);
|
||||
|
||||
USE_SERIAL.println();
|
||||
USE_SERIAL.println();
|
||||
USE_SERIAL.println();
|
||||
|
||||
for(uint8_t t = 4; t > 0; t--) {
|
||||
USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t);
|
||||
USE_SERIAL.flush();
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
wifiMulti.addAP("SSID", "WIFI_PASSPHRASE");
|
||||
|
||||
// WiFi.disconnect();
|
||||
while(wifiMulti.run() != WL_CONNECTED) {
|
||||
delay(100);
|
||||
}
|
||||
|
||||
setClock();
|
||||
|
||||
// server address, port and URL. This server can be flakey.
|
||||
// Expected response: Request served by 0123456789abcdef
|
||||
webSocket.beginSslWithBundle("echo.websocket.org", 443, "/", rootca_crt_bundle_start, "");
|
||||
|
||||
// event handler
|
||||
webSocket.onEvent(webSocketEvent);
|
||||
|
||||
// use HTTP Basic Authorization this is optional enable if needed
|
||||
// webSocket.setAuthorization("user", "Password");
|
||||
|
||||
// try ever 5000 again if connection has failed
|
||||
webSocket.setReconnectInterval(5000);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
webSocket.loop();
|
||||
}
|
||||
Reference in New Issue
Block a user