Skip to content

Commit c63f26b

Browse files
committed
Add MQTT TLS support and CA certificate handling
- Implemented toggle functionality for CA certificate visibility in the UI. - Added MQTT TLS configuration options and handling in the backend. - Integrated LittleFS for storing and retrieving the CA certificate. - Updated MQTT connection logic to use TLS if enabled.
1 parent 74f7364 commit c63f26b

File tree

4 files changed

+100
-5
lines changed

4 files changed

+100
-5
lines changed

SmartEVSE-3/data/index.html

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,15 @@
5757

5858
$(document).ready(loadData());
5959

60+
// Function to toggle cert visibility
61+
function toggleCertVisibility() {
62+
if ($('#mqtt_tls').is(':checked')) {
63+
$('#mqtt_ca_cert_wrapper').show();
64+
} else {
65+
$('#mqtt_ca_cert_wrapper').hide();
66+
}
67+
}
68+
6069
/**
6170
* Helper function to query the DOM for a single element.
6271
*/
@@ -296,6 +305,9 @@
296305
$('#mqtt_username').val(data.mqtt.username);
297306
$('#mqtt_password').val(data.mqtt.password);
298307
$('#mqtt_topic_prefix').val(data.mqtt.topic_prefix);
308+
$('#mqtt_tls').prop('checked', data.mqtt.tls).checkboxradio("refresh"); // Set and refresh widget
309+
$('#mqtt_ca_cert').val(data.mqtt.ca_cert || '');
310+
toggleCertVisibility();
299311
}
300312

301313
if (data.settings.lcdlock == 1) {
@@ -364,6 +376,9 @@
364376
setTimeout("loadData()", 5000);
365377
});
366378
}
379+
380+
// Add event listener for TLS checkbox
381+
$(document).on('change', '#mqtt_tls', toggleCertVisibility);
367382
</script>
368383

369384

@@ -1090,6 +1105,9 @@ <h1 id="serialnr" class="h5 mb-0 text-white-800">SmartEVSE-</h1>
10901105
$('.mqtt_settings').toggle();
10911106
if (mqttEditMode) {
10921107
$('#edit_mqtt_button').text("Close Settings")
1108+
$.get("/mqtt_ca_cert", function(certData) {
1109+
$('#mqtt_ca_cert').val(certData);
1110+
});
10931111
} else {
10941112
$('#edit_mqtt_button').text("Edit Settings")
10951113
}
@@ -1101,7 +1119,9 @@ <h1 id="serialnr" class="h5 mb-0 text-white-800">SmartEVSE-</h1>
11011119
mqtt_port: $('#mqtt_port').val(),
11021120
mqtt_username: $('#mqtt_username').val(),
11031121
mqtt_password: $('#mqtt_password').val(),
1104-
mqtt_topic_prefix: $('#mqtt_topic_prefix').val()
1122+
mqtt_topic_prefix: $('#mqtt_topic_prefix').val(),
1123+
mqtt_tls: $('#mqtt_tls').is(':checked') ? 1 : 0,
1124+
mqtt_ca_cert: $('#mqtt_ca_cert').val()
11051125
};
11061126
// Build query string with proper encoding
11071127
var query = Object.keys(params)
@@ -1368,10 +1388,14 @@ <h1 id="serialnr" class="h5 mb-0 text-white-800">SmartEVSE-</h1>
13681388
<button onclick="toggleMqttEdit()" style="display:inline-block;" id="edit_mqtt_button">Edit Settings</button>
13691389
<div class="mqtt_settings" style="display:none">
13701390
<label>Host: <input id="mqtt_host" title="Leave empty to disable MQTT"></label>
1371-
<label>Port: <input id="mqtt_port"></label>
1391+
<label>Port: <input id="mqtt_port" title="Usually 1883 for MQTT, and 8883 for secure MQTTS"></label>
13721392
<label>Username: <input id="mqtt_username" title="Leave empty for anonymous MQTT"></label>
13731393
<label>Password: <input id="mqtt_password" title="Leave empty for anonymous MQTT"></label>
13741394
<label>Topic Prefix: <input id="mqtt_topic_prefix"></label>
1395+
<label>Enable TLS: <input type="checkbox" id="mqtt_tls" value="1"></label>
1396+
<div id="mqtt_ca_cert_wrapper" style="display:none;">
1397+
<label>CA Certificate (PEM): <textarea id="mqtt_ca_cert" rows="10" style="width:100%; font-family:monospace;" title="Paste the PEM-formatted CA certificate here for TLS."></textarea></label>
1398+
</div>
13751399
<button onclick="configureMqtt()" style="display:inline-block;">Save</button>
13761400
</div>
13771401
</div>

SmartEVSE-3/src/esp32.cpp

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ char RequiredEVCCID[32] = ""; // R
2020
#include <Preferences.h>
2121

2222
#include <FS.h>
23+
#include <LittleFS.h>
2324

2425
#include <WiFi.h>
2526
#include "network_common.h"
@@ -483,6 +484,37 @@ void getButtonState() {
483484
}
484485

485486

487+
String readMqttCaCert() {
488+
if (!LittleFS.exists("/mqtt_ca.pem")) {
489+
_LOG_A("No /mqtt_ca.pem found.\n");
490+
return "";
491+
}
492+
File file = LittleFS.open("/mqtt_ca.pem", "r");
493+
if (!file) {
494+
_LOG_A("Failed to open /mqtt_ca.pem for reading.\n");
495+
return "";
496+
}
497+
String cert = file.readString();
498+
file.close();
499+
return cert;
500+
}
501+
502+
void writeMqttCaCert(const String& cert) {
503+
if (cert.isEmpty()) {
504+
LittleFS.remove("/mqtt_ca.pem");
505+
_LOG_D("Removed /mqtt_ca.pem.\n");
506+
return;
507+
}
508+
File file = LittleFS.open("/mqtt_ca.pem", "w");
509+
if (!file) {
510+
_LOG_A("Failed to open /mqtt_ca.pem for writing.\n");
511+
return;
512+
}
513+
file.print(cert);
514+
file.close();
515+
_LOG_D("Wrote %d bytes to /mqtt_ca.pem.\n", cert.length());
516+
}
517+
486518
#if MQTT
487519
void mqtt_receive_callback(const String topic, const String payload) {
488520
if (topic == MQTTprefix + "/Set/Mode") {
@@ -1368,7 +1400,7 @@ bool handle_URI(struct mg_connection *c, struct mg_http_message *hm, webServerR
13681400
doc["mqtt"]["topic_prefix"] = MQTTprefix;
13691401
doc["mqtt"]["username"] = MQTTuser;
13701402
doc["mqtt"]["password_set"] = MQTTpassword != "";
1371-
1403+
doc["mqtt"]["tls"] = MQTTtls;
13721404
if (MQTTclient.connected) {
13731405
doc["mqtt"]["status"] = "Connected";
13741406
} else {
@@ -2857,6 +2889,11 @@ extern void Timer20ms(void * parameter);
28572889
validate_settings();
28582890
ReadRFIDlist(); // Read all stored RFID's from storage
28592891

2892+
// Note that LittleFS is also used by OCPP
2893+
if(!LittleFS.begin(true)) {
2894+
_LOG_A("LittleFS Mount Failed\n");
2895+
}
2896+
28602897
getButtonState();
28612898
/* * @param Buttons: < o >
28622899
* Value: 1 2 4

SmartEVSE-3/src/network_common.cpp

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ String MQTTHost = "";
4646
uint16_t MQTTPort;
4747
mg_timer *MQTTtimer;
4848
uint8_t lastMqttUpdate = 0;
49+
bool MQTTtls = false;
4950
#endif
5051

5152
mg_connection *HttpListener80, *HttpListener443;
@@ -112,9 +113,23 @@ void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event
112113
void MQTTclient_t::connect(void) {
113114
if (MQTTHost != "") {
114115
char s_mqtt_url[80];
115-
snprintf(s_mqtt_url, sizeof(s_mqtt_url), "mqtt://%s:%i", MQTTHost.c_str(), MQTTPort);
116+
const char* scheme = MQTTtls ? "mqtts" : "mqtt";
117+
snprintf(s_mqtt_url, sizeof(s_mqtt_url), "%s://%s:%i", scheme, MQTTHost.c_str(), MQTTPort);
116118
String lwtTopic = MQTTprefix + "/connected";
117119
esp_mqtt_client_config_t mqtt_cfg = { .uri = s_mqtt_url, .client_id=MQTTprefix.c_str(), .username=MQTTuser.c_str(), .password=MQTTpassword.c_str(), .lwt_topic=lwtTopic.c_str(), .lwt_msg="offline", .lwt_qos=0, .lwt_retain=1, .lwt_msg_len=7, .keepalive=15 };
120+
121+
static String ca_cert_str;
122+
if (MQTTtls) {
123+
ca_cert_str = readMqttCaCert();
124+
if (ca_cert_str.length() > 0) {
125+
mqtt_cfg.cert_pem = ca_cert_str.c_str();
126+
_LOG_D("Using CA cert from LittleFS (%d bytes).\n", ca_cert_str.length());
127+
} else {
128+
mqtt_cfg.cert_pem = NULL;
129+
_LOG_A("No CA cert in LittleFS.\n");
130+
}
131+
}
132+
118133
MQTTclient.client = esp_mqtt_client_init(&mqtt_cfg);
119134
/* The last argument may be used to pass data to the event handler, in this example mqtt_event_handler */
120135
esp_mqtt_client_register_event(MQTTclient.client, (esp_mqtt_event_id_t) ESP_EVENT_ANY_ID, (esp_event_handler_t) mqtt_event_handler, NULL);
@@ -1230,6 +1245,17 @@ static void fn_http_server(struct mg_connection *c, int ev, void *ev_data) {
12301245
doc["mqtt_password_set"] = (MQTTpassword != "");
12311246
}
12321247

1248+
if (request->hasParam("mqtt_tls")) {
1249+
MQTTtls = request->getParam("mqtt_tls")->value() == "1";
1250+
doc["mqtt_tls"] = MQTTtls;
1251+
}
1252+
1253+
if(request->hasParam("mqtt_ca_cert")) {
1254+
String cert = request->getParam("mqtt_ca_cert")->value();
1255+
writeMqttCaCert(cert); // Save to LittleFS
1256+
doc["mqtt_ca_cert_set"] = !cert.isEmpty();
1257+
}
1258+
12331259
// disconnect mqtt so it will automatically reconnect with then new params
12341260
MQTTclient.disconnect();
12351261
#if MQTT_ESP == 1
@@ -1242,13 +1268,17 @@ static void fn_http_server(struct mg_connection *c, int ev, void *ev_data) {
12421268
preferences.putString("MQTTprefix", MQTTprefix);
12431269
preferences.putString("MQTTHost", MQTTHost);
12441270
preferences.putUShort("MQTTPort", MQTTPort);
1271+
preferences.putBool("MQTTtls", MQTTtls);
12451272
preferences.end();
12461273
}
12471274
}
12481275
#endif
12491276
String json;
12501277
serializeJson(doc, json);
12511278
mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s\r\n", json.c_str()); // Yes. Respond JSON
1279+
} else if (mg_http_match_uri(hm, "/mqtt_ca_cert") && !memcmp("GET", hm->method.buf, hm->method.len)) {
1280+
String cert = readMqttCaCert();
1281+
mg_http_reply(c, 200, "Content-Type: text/plain\r\n", "%s\r\n", cert.c_str());
12521282
} else { // if everything else fails, serve static page
12531283
// Cache ".webp" or ".ico" image files for one year without revalidation or server checks.
12541284
if (mg_match(hm->uri, mg_str("#.webp"), NULL) ||
@@ -1493,6 +1523,7 @@ void WiFiSetup(void) {
14931523
#endif
14941524
MQTTHost = preferences.getString("MQTTHost", "");
14951525
MQTTPort = preferences.getUShort("MQTTPort", 1883);
1526+
MQTTtls = preferences.getBool("MQTTtls", false);
14961527
#endif //MQTT
14971528
preferences.end();
14981529
}

SmartEVSE-3/src/network_common.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ extern String MQTTprefix;
5656
extern String MQTTHost;
5757
extern uint16_t MQTTPort;
5858
extern uint8_t lastMqttUpdate;
59+
extern bool MQTTtls;
5960

6061
class MQTTclient_t {
6162
#if MQTT_ESP == 0
@@ -98,14 +99,16 @@ class MQTTclient_t {
9899
extern MQTTclient_t MQTTclient;
99100
extern void SetupMQTTClient();
100101
extern void mqtt_receive_callback(const String topic, const String payload);
102+
extern String readMqttCaCert();
103+
extern void writeMqttCaCert(const String& cert);
101104
#endif //MQTT
102105

103106
// wrapper so hasParam and getParam still work
104107
class webServerRequest {
105108
private:
106109
struct mg_http_message *hm_internal;
107110
String _value;
108-
char temp[64];
111+
char temp[4096]; // allow CA cert to be sent
109112

110113
public:
111114
void setMessage(struct mg_http_message *hm);

0 commit comments

Comments
 (0)