Skip to content

Commit 0b4d618

Browse files
committed
ported MQTT Client to PsychicMqttClient
1 parent c2c9ba3 commit 0b4d618

20 files changed

+838
-1748
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ node_modules
1515
/lib/framework/WWWData.h
1616
*WWWData.h
1717
lib/framework/WWWData.h
18+
ssl_certs/cacert.pem

CHANGELOG.md

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,43 +6,42 @@ All notable changes to this project will be documented in this file.
66

77
> **Warning**: This update has breaking changes!
88
9-
### ToDo's for this release
10-
11-
- [ ] Provide CORS preflight confirmation
12-
- [ ] Switch AsyncMqttClient to ESP-IDF MQTT Client
13-
- [ ] Fix glitch in UI that System Status page is not reactive to 2sec polling of status
14-
- [ ] LightState NimBLE Demo --> Switch LED on and off with BLE HID device. Binding procedure with BLE HID device and BLE HID status page.
15-
- [ ] Update docs and check for consistency
16-
179
### Added
1810

1911
- Added postscript to platform.io build process to copy, rename and calculate MD5 checksum of \*.bin file. These files are ready for uploading to the Github Release page.
2012
- Added more information to SystemStatus API
2113
- Added generateToken API for security settings
2214
- Added Multi-WiFi capability. Add up to five WiFi configurations and connect to either strongest network (default), or by priority.
2315
- Added InfoDialog as a simpler version of the ConfirmDialog for a simple notification modal.
16+
- Added Adafruit certificate repository as the default choice for the X509 certificate bundle.
2417

2518
### Changed
2619

2720
- Better route protection for user page with deep link.
2821
- Changed build_interface.py script to check for modified files in the interface sources before re-building the interface. Saves some time on the compilation process.
2922
- Upload firmware binary allows uploading of MD5 checksum file in advance to verify downloaded firmware package.
3023
- GithubFirmwareManager checks against PIO build_target in filename to support Github OTA for binaries build for various targets. You should rename your old release \*.bin files on the Github release pages for backward compatibility.
24+
- Changed MQTT Client to an ESP-IDF backed one which supports SSL/TLS X509 root CA bundles and transport over WS.
25+
- Changed the PROGMEM_WWW flag to EMBED_WWW as there is technically speaking no PROGMEM on ESP32's.
26+
27+
### Fixed
28+
29+
- Fixed reactivity of System Status page.
3130

3231
### Removed
3332

3433
- Removed support for Arduino ESP OTA.
3534
- HttpEndpoints and Websocket Server without a securityManager are no longer possible.
3635

37-
### Migrate from ESPAsyncWebServer
36+
### Migrate from ESPAsyncWebServer to PsychicHttp
3837

3938
#### Migrate `main.cpp`
4039

4140
Change the server and ESPSvelteKit instances to PsychicHttpServer and give the ESP32SvelteKit constructor the number of http endpoints of your project.
4241

4342
```
4443
PsychicHttpServer server;
45-
ESP32SvelteKit esp32sveltekit(&server, 115);
44+
ESP32SvelteKit esp32sveltekit(&server, 125);
4645
```
4746

4847
Remove `server.begin();` in `void setup()`. This is handled by ESP32SvelteKit now.
@@ -57,6 +56,7 @@ Remove the following `build_flags`:
5756
-D WS_MAX_QUEUED_MESSAGES=64
5857
-D CONFIG_ASYNC_TCP_RUNNING_CORE=0
5958
-D NO_GLOBAL_ARDUINOOTA
59+
-D PROGMEM_WWW
6060
```
6161

6262
Add the following `build_flags` and adjust to your app, if needed:
@@ -65,9 +65,29 @@ Add the following `build_flags` and adjust to your app, if needed:
6565
-D BUILD_TARGET=\"$PIOENV\"
6666
-D APP_NAME=\"ESP32-Sveltekit\" ; Must only contain characters from [a-zA-Z0-9-_] as this is converted into a filename
6767
-D APP_VERSION=\"0.3.0\" ; semver compatible version string
68+
-D EMBED_WWW
69+
```
70+
71+
Remove the lib dependency `esphome/AsyncTCP-esphome @ ^2.0.0` and add `https://github.com/theelims/PsychicMqttClient.git`
72+
73+
Consider adjusting `board_ssl_cert_source = adafruit`, so that the new MQTT client has universal SSL/TLS support with a wide range of CA root certificates.
74+
75+
#### Migrate `factory_settings.ini`
76+
77+
The new MQTT client has slightly renamed factory settings:
78+
79+
```ini
80+
; MQTT settings
81+
-D FACTORY_MQTT_ENABLED=false
82+
-D FACTORY_MQTT_URI=\"mqtts://mqtt.eclipseprojects.io:8883\"
83+
-D FACTORY_MQTT_USERNAME=\"\" ; supports placeholders
84+
-D FACTORY_MQTT_PASSWORD=\"\"
85+
-D FACTORY_MQTT_CLIENT_ID=\"#{platform}-#{unique_id}\" ; supports placeholders
86+
-D FACTORY_MQTT_KEEP_ALIVE=120
87+
-D FACTORY_MQTT_CLEAN_SESSION=true
6888
```
6989

70-
Remove the lib dependency `esphome/AsyncTCP-esphome @ ^2.0.0`.
90+
Max Topic Length is no longer needed.
7191

7292
#### Custom Stateful Services
7393

factory_settings.ini

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,12 @@ build_flags =
3737

3838
; MQTT settings
3939
-D FACTORY_MQTT_ENABLED=false
40-
-D FACTORY_MQTT_HOST=\"test.mosquitto.org\"
41-
-D FACTORY_MQTT_PORT=1883
40+
-D FACTORY_MQTT_URI=\"mqtts://mqtt.eclipseprojects.io:8883\"
4241
-D FACTORY_MQTT_USERNAME=\"\" ; supports placeholders
4342
-D FACTORY_MQTT_PASSWORD=\"\"
4443
-D FACTORY_MQTT_CLIENT_ID=\"#{platform}-#{unique_id}\" ; supports placeholders
45-
-D FACTORY_MQTT_KEEP_ALIVE=60
44+
-D FACTORY_MQTT_KEEP_ALIVE=120
4645
-D FACTORY_MQTT_CLEAN_SESSION=true
47-
-D FACTORY_MQTT_MAX_TOPIC_LENGTH=128
4846

4947
; JWT Secret
5048
-D FACTORY_JWT_SECRET=\"#{random}-#{random}\" ; supports placeholders

features.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[features]
22
build_flags =
3-
-D FT_SECURITY=0
3+
-D FT_SECURITY=1
44
-D FT_MQTT=1
55
-D FT_NTP=1
66
-D FT_UPLOAD_FIRMWARE=1

interface/src/routes/connections/mqtt/MQTT.svelte

Lines changed: 20 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -16,37 +16,24 @@
1616
enabled: boolean;
1717
connected: boolean;
1818
client_id: string;
19-
disconnect_reason: number;
19+
last_error: string;
2020
};
2121
2222
type MQTTSettings = {
2323
enabled: boolean;
24-
host: string;
25-
port: number;
24+
uri: string;
2625
username: string;
2726
password: string;
2827
client_id: string;
2928
keep_alive: number;
3029
clean_session: boolean;
31-
max_topic_length: number;
3230
};
3331
3432
let mqttSettings: MQTTSettings;
3533
let mqttStatus: MQTTStatus;
3634
3735
let formField: any;
3836
39-
const disconnectReason = [
40-
'TCP Disconnected',
41-
'Unacceptable Protocol Version',
42-
'Identifier Rejected',
43-
'Server unavailable',
44-
'Malformed Credentials',
45-
'Not Authorized',
46-
'Not Enough Memory',
47-
'TLS Rejected'
48-
];
49-
5037
async function getMQTTStatus() {
5138
try {
5239
const response = await fetch('/rest/mqttStatus', {
@@ -123,29 +110,17 @@
123110
function handleSubmitMQTT() {
124111
let valid = true;
125112
126-
// Validate Server
127-
// RegEx for IPv4
128-
const regexExpIPv4 =
129-
/\b(?:(?:2(?:[0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9])\.){3}(?:(?:2([0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9]))\b/;
113+
// Validate Server URI
130114
const regexExpURL =
131-
/[-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?/i;
115+
/(([-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4})|(\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b))(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?/i;
132116
133-
if (!regexExpURL.test(mqttSettings.host) && !regexExpIPv4.test(mqttSettings.host)) {
117+
if (!regexExpURL.test(mqttSettings.uri)) {
134118
valid = false;
135119
formErrors.host = true;
136120
} else {
137121
formErrors.host = false;
138122
}
139123
140-
// Validate if port is a number and within the right range
141-
let port = Number(mqttSettings.port);
142-
if (0 <= port && port <= 65536) {
143-
formErrors.port = false;
144-
} else {
145-
formErrors.port = true;
146-
valid = false;
147-
}
148-
149124
// Validate if port is a number and within the right range
150125
let keepalive = Number(mqttSettings.keep_alive);
151126
if (1 <= keepalive && keepalive <= 600) {
@@ -194,7 +169,7 @@
194169
{:else if !mqttStatus.enabled}
195170
MQTT Disabled
196171
{:else}
197-
{disconnectReason[mqttStatus.disconnect_reason]}
172+
{mqttStatus.last_error}
198173
{/if}
199174
</div>
200175
</div>
@@ -231,50 +206,28 @@
231206
<span>Enable MQTT</span>
232207
</label>
233208
<div class="hidden sm:block" />
234-
<!-- Host -->
235-
<div>
209+
<!-- URI -->
210+
<div class="sm:col-span-2">
236211
<label class="label" for="host">
237-
<span class="label-text text-md">Host</span>
212+
<span class="label-text text-md">URI</span>
238213
</label>
239214
<input
240215
type="text"
241216
class="input input-bordered invalid:border-error w-full invalid:border-2 {formErrors.host
242217
? 'border-error border-2'
243218
: ''}"
244-
bind:value={mqttSettings.host}
219+
bind:value={mqttSettings.uri}
245220
id="host"
246221
min="3"
247222
max="64"
248223
required
249224
/>
250225
<label class="label" for="host">
251226
<span class="label-text-alt text-error {formErrors.host ? '' : 'hidden'}"
252-
>Must be a valid IPv4 address or URL</span
227+
>Must be a valid URI</span
253228
>
254229
</label>
255230
</div>
256-
<!-- Port -->
257-
<div>
258-
<label class="label" for="port">
259-
<span class="label-text text-md">Port</span>
260-
</label>
261-
<input
262-
type="number"
263-
min="0"
264-
max="65536"
265-
class="input input-bordered invalid:border-error w-full invalid:border-2 {formErrors.port
266-
? 'border-error border-2'
267-
: ''}"
268-
bind:value={mqttSettings.port}
269-
id="port"
270-
required
271-
/>
272-
<label for="port" class="label"
273-
><span class="label-text-alt text-error {formErrors.port ? '' : 'hidden'}"
274-
>Port number must be between 0 and 65536</span
275-
></label
276-
>
277-
</div>
278231
<!-- Username -->
279232
<div>
280233
<label class="label" for="user">
@@ -306,15 +259,6 @@
306259
id="clientid"
307260
/>
308261
</div>
309-
<!-- Clean Session -->
310-
<label class="label inline-flex cursor-pointer content-end justify-start gap-4">
311-
<input
312-
type="checkbox"
313-
bind:checked={mqttSettings.clean_session}
314-
class="checkbox checkbox-primary mt-2 sm:-mb-8 sm:mt-0"
315-
/>
316-
<span class="mt-2 sm:-mb-8 sm:mt-0">Clean Session?</span>
317-
</label>
318262
<!-- Keep Alive -->
319263
<div>
320264
<label class="label" for="keepalive">
@@ -340,31 +284,15 @@
340284
></label
341285
>
342286
</div>
343-
<!-- Max Topic Length -->
344-
<div>
345-
<label class="label" for="maxtopic">
346-
<span class="label-text text-md">Max Topic Length</span>
347-
</label>
348-
<label for="maxtopic" class="input-group">
349-
<input
350-
type="number"
351-
min="64"
352-
max="4096"
353-
class="input input-bordered invalid:border-error w-full invalid:border-2 {formErrors.topic_length
354-
? 'border-error border-2'
355-
: ''}"
356-
bind:value={mqttSettings.max_topic_length}
357-
id="maxtopic"
358-
required
359-
/>
360-
<span>Chars</span>
361-
</label>
362-
<label for="maxtopic" class="label"
363-
><span class="label-text-alt text-error {formErrors.topic_length ? '' : 'hidden'}"
364-
>Must be between 64 and 4096 characters</span
365-
></label
366-
>
367-
</div>
287+
<!-- Clean Session -->
288+
<label class="label inline-flex cursor-pointer content-end justify-start gap-4">
289+
<input
290+
type="checkbox"
291+
bind:checked={mqttSettings.clean_session}
292+
class="checkbox checkbox-primary"
293+
/>
294+
<span class="">Clean Session?</span>
295+
</label>
368296
</div>
369297
<div class="divider mb-2 mt-0" />
370298
<div class="mx-4 flex flex-wrap justify-end gap-2">

interface/src/routes/system/status/SystemStatus.svelte

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
uptime: number;
5151
};
5252
53+
let systemStatus: SystemStatus;
54+
5355
async function getSystemStatus() {
5456
try {
5557
const response = await fetch('/rest/systemStatus', {
@@ -59,10 +61,11 @@
5961
'Content-Type': 'application/json'
6062
}
6163
});
62-
return await response.json();
64+
systemStatus = await response.json();
6365
} catch (error) {
6466
console.log('Error:', error);
6567
}
68+
return systemStatus;
6669
}
6770
6871
const interval = setInterval(async () => {
@@ -180,7 +183,7 @@
180183
<div class="w-full overflow-x-auto">
181184
{#await getSystemStatus()}
182185
<Spinner />
183-
{:then systemStatus}
186+
{:then nothing}
184187
<div
185188
class="flex w-full flex-col space-y-1"
186189
transition:slide|local={{ duration: 300, easing: cubicOut }}
@@ -329,9 +332,7 @@
329332
<div>
330333
<div class="font-bold">Core Temperature</div>
331334
<div class="text-sm opacity-75">
332-
{systemStatus.core_temp.toFixed(2) == 53.33
333-
? 'NaN'
334-
: systemStatus.core_temp.toFixed(2) + ' °C'}
335+
{systemStatus.core_temp == 53.33 ? 'NaN' : systemStatus.core_temp.toFixed(2) + ' °C'}
335336
</div>
336337
</div>
337338
</div>

0 commit comments

Comments
 (0)