cpu-loader 0.0.2__tar.gz → 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cpu-loader
3
- Version: 0.0.2
3
+ Version: 0.1.2
4
4
  Summary: A tool to generate CPU load on a system with runtime configuration through a WebUI and REST API
5
5
  Author-email: the78mole <the78mole@users.noreply.github.com>
6
6
  License: MIT
@@ -33,6 +33,7 @@ Requires-Dist: pydantic==2.10.0
33
33
  Requires-Dist: requests==2.32.3
34
34
  Requires-Dist: psutil>=5.9.0
35
35
  Requires-Dist: websockets>=12.0
36
+ Requires-Dist: paho-mqtt>=2.1.0
36
37
  Dynamic: license-file
37
38
 
38
39
  # CPU Loader
@@ -44,26 +45,48 @@ Dynamic: license-file
44
45
 
45
46
  A tool to generate CPU load on a system with runtime configuration through a WebUI and REST API.
46
47
 
47
- ## Features
48
-
49
- - 🔥 **Controllable CPU Load**: Generate precise CPU load per thread
50
- - 🎛️ **Per-Thread Control**: Individual sliders for each CPU thread
51
- - 🚀 **REST API**: Full programmatic control via HTTP endpoints
52
- - 🌐 **Modern WebUI**: Beautiful, responsive interface for real-time control
53
- - ⚡ **Instant Response**: Changes take effect immediately
54
- - 📊 **Live Monitoring**: View active threads and average load in real-time
55
-
56
- ## Screenshots
57
-
58
- ### WebUI Interface
59
- ![CPU Loader Control - 25% Load](https://github.com/user-attachments/assets/94f5a1a1-4328-46af-9155-e7d50cc8ba0e)
60
- *Individual thread control with sliders and preset buttons*
61
-
62
- ![CPU Loader Control - 50% Load](https://github.com/user-attachments/assets/bcab1f61-03a6-48ea-83b7-4402ec0d1389)
63
- *Setting all threads to 50% load*
64
-
65
- ![CPU Loader Control - 100% Load](https://github.com/user-attachments/assets/542afa1c-16da-4aff-a021-1e905e6f3372)
66
- *Maximum load across all threads*
48
+ ## ✨ Key Features
49
+
50
+ - **🎯 Precise Per-Thread Control**: Set individual CPU load (0-100%) for each core independently
51
+ - **⚡ High Performance**: Native C implementation with pthreads ensures accurate load generation
52
+ - **📊 Real-Time Monitoring**: Live WebSocket updates showing actual CPU usage via `psutil`
53
+ - **🎛️ Interactive WebUI**: Beautiful gradient interface with sliders and visual feedback
54
+ - **🚀 REST API**: Complete programmatic control for automation and testing
55
+ - **📱 Responsive Design**: Works seamlessly on desktop and mobile devices
56
+ - **🔄 Instant Updates**: Changes take effect immediately with sub-second response time
57
+ - 📡 **MQTT Integration**: Publish metrics and settings to MQTT broker for IoT/monitoring systems
58
+
59
+ ## 🖼️ Screenshots
60
+
61
+ ### Desktop Interface
62
+ <table>
63
+ <tr>
64
+ <td width="50%">
65
+ <img src="screenshots/01-initial-view.png" alt="Initial view with zero load">
66
+ <p align="center"><em>Clean interface showing all CPU threads at idle</em></p>
67
+ </td>
68
+ <td width="50%">
69
+ <img src="screenshots/02-with-load.png" alt="Mixed load across threads">
70
+ <p align="center"><em>Different load levels: 50%, 75%, and 25% on individual threads</em></p>
71
+ </td>
72
+ </tr>
73
+ <tr>
74
+ <td width="50%">
75
+ <img src="screenshots/03-live-metrics.png" alt="Live CPU metrics updating">
76
+ <p align="center"><em>Real-time metrics showing actual CPU usage with color-coded bars</em></p>
77
+ </td>
78
+ <td width="50%">
79
+ <img src="screenshots/04-mobile-view.png" alt="Mobile responsive view">
80
+ <p align="center"><em>Fully responsive mobile interface</em></p>
81
+ </td>
82
+ </tr>
83
+ </table>
84
+
85
+ ### Features Shown
86
+ - **Visual Load Bars**: Green progress bars show target load, blue bars display actual CPU usage
87
+ - **Real-Time Updates**: WebSocket connection provides live metrics every second
88
+ - **Preset Buttons**: Quick-set options (0%, 25%, 50%, 75%, 100%) for all threads
89
+ - **Smooth Gradients**: Modern UI with purple-to-blue gradient design
67
90
 
68
91
  ## Installation
69
92
 
@@ -112,6 +135,22 @@ uv run src/main.py
112
135
 
113
136
  The server will start on `http://localhost:8000`
114
137
 
138
+ #### Command-Line Options
139
+
140
+ ```bash
141
+ uv run src/main.py --help
142
+ ```
143
+
144
+ **Available Options:**
145
+ - `--host HOST`: Host to bind the server to (default: 0.0.0.0)
146
+ - `--port PORT`: Port to bind the server to (default: 8000)
147
+ - `--mqtt-broker-host HOST`: MQTT broker hostname
148
+ - `--mqtt-broker-port PORT`: MQTT broker port (default: 1883)
149
+ - `--mqtt-username USER`: MQTT username
150
+ - `--mqtt-password PASS`: MQTT password
151
+ - `--mqtt-topic-prefix PREFIX`: MQTT topic prefix (default: cpu-loader)
152
+ - `--mqtt-client-id ID`: MQTT client ID (default: cpu-loader)
153
+
115
154
  ### WebUI
116
155
 
117
156
  Open your browser and navigate to `http://localhost:8000`
@@ -167,6 +206,88 @@ curl -X POST http://localhost:8000/api/threads \
167
206
 
168
207
  Once the server is running, visit `http://localhost:8000/docs` for interactive API documentation powered by Swagger UI.
169
208
 
209
+ ## MQTT Publishing
210
+
211
+ CPU Loader can publish real-time CPU metrics and load control settings to an MQTT broker for integration with home automation systems, monitoring tools, or custom applications.
212
+
213
+ ### MQTT Topics
214
+
215
+ The application publishes to two topics:
216
+
217
+ 1. **`{prefix}/cpu_metrics`**: Published every second with current CPU utilization
218
+ ```json
219
+ {
220
+ "total_cpu_percent": 25.5,
221
+ "per_cpu_percent": [25.0, 26.0, 25.3, 25.7]
222
+ }
223
+ ```
224
+
225
+ 2. **`{prefix}/load_settings`**: Published when load settings change (retained message)
226
+ ```json
227
+ {
228
+ "num_threads": 4,
229
+ "loads": {"0": 25.0, "1": 25.0, "2": 25.0, "3": 25.0},
230
+ "average_load": 25.0
231
+ }
232
+ ```
233
+
234
+ ### Configuration
235
+
236
+ MQTT can be configured using environment variables or command-line arguments. Command-line arguments take precedence over environment variables.
237
+
238
+ #### Using Environment Variables
239
+
240
+ ```bash
241
+ export MQTT_BROKER_HOST=mqtt.example.com
242
+ export MQTT_BROKER_PORT=1883
243
+ export MQTT_USERNAME=myuser
244
+ export MQTT_PASSWORD=mypassword
245
+ export MQTT_TOPIC_PREFIX=cpu-loader
246
+ export MQTT_CLIENT_ID=cpu-loader-001
247
+
248
+ uv run src/main.py
249
+ ```
250
+
251
+ #### Using Command-Line Arguments
252
+
253
+ ```bash
254
+ uv run src/main.py \
255
+ --mqtt-broker-host mqtt.example.com \
256
+ --mqtt-broker-port 1883 \
257
+ --mqtt-username myuser \
258
+ --mqtt-password mypassword \
259
+ --mqtt-topic-prefix cpu-loader \
260
+ --mqtt-client-id cpu-loader-001
261
+ ```
262
+
263
+ #### Testing with Mosquitto
264
+
265
+ To test MQTT publishing locally:
266
+
267
+ ```bash
268
+ # Install mosquitto
269
+ sudo apt-get install mosquitto mosquitto-clients
270
+
271
+ # Start CPU Loader with MQTT
272
+ uv run src/main.py --mqtt-broker-host localhost
273
+
274
+ # Subscribe to all topics (in another terminal)
275
+ mosquitto_sub -h localhost -t "cpu-loader/#" -v
276
+ ```
277
+
278
+ ### MQTT Settings
279
+
280
+ | Setting | Environment Variable | CLI Argument | Default | Description |
281
+ |---------|---------------------|--------------|---------|-------------|
282
+ | Broker Host | `MQTT_BROKER_HOST` | `--mqtt-broker-host` | None | MQTT broker hostname or IP |
283
+ | Broker Port | `MQTT_BROKER_PORT` | `--mqtt-broker-port` | 1883 | MQTT broker port |
284
+ | Username | `MQTT_USERNAME` | `--mqtt-username` | None | MQTT authentication username |
285
+ | Password | `MQTT_PASSWORD` | `--mqtt-password` | None | MQTT authentication password |
286
+ | Topic Prefix | `MQTT_TOPIC_PREFIX` | `--mqtt-topic-prefix` | cpu-loader | Prefix for all MQTT topics |
287
+ | Client ID | `MQTT_CLIENT_ID` | `--mqtt-client-id` | cpu-loader | MQTT client identifier |
288
+
289
+ **Note:** If no MQTT broker host is configured, MQTT publishing will be disabled and the application will function normally without it.
290
+
170
291
  ## Example Script
171
292
 
172
293
  An example script (`src/example.py`) is provided to demonstrate programmatic control:
@@ -191,6 +312,7 @@ The example demonstrates:
191
312
  - **src/cpu_loader_core.c**: High-performance C implementation using pthreads for CPU load generation
192
313
  - **src/cpu_loader.py**: Python wrapper providing a clean API to the C extension
193
314
  - **src/main.py**: FastAPI application with REST API and embedded WebUI
315
+ - **src/mqtt_publisher.py**: MQTT client for publishing metrics and settings
194
316
  - **Threading Model**: Native pthreads for maximum efficiency and precise timing
195
317
  - **Load Algorithm**: High-resolution busy-wait loops with nanosecond precision
196
318
 
@@ -200,6 +322,7 @@ The example demonstrates:
200
322
  - FastAPI 0.115.0+
201
323
  - Uvicorn 0.32.0+
202
324
  - Pydantic 2.10.0+
325
+ - paho-mqtt 2.1.0+ (for MQTT publishing)
203
326
  - C compiler (gcc or clang) for building the extension
204
327
 
205
328
  ## Use Cases
@@ -209,6 +332,8 @@ The example demonstrates:
209
332
  - **Thermal Testing**: Check cooling system effectiveness
210
333
  - **Power Consumption Analysis**: Measure power usage at different load levels
211
334
  - **Benchmarking**: Create reproducible load scenarios
335
+ - **IoT Integration**: Integrate with Home Assistant, Node-RED, or other MQTT-based systems
336
+ - **Monitoring**: Feed CPU metrics into monitoring dashboards via MQTT
212
337
 
213
338
  ## Development
214
339
 
@@ -7,26 +7,48 @@
7
7
 
8
8
  A tool to generate CPU load on a system with runtime configuration through a WebUI and REST API.
9
9
 
10
- ## Features
11
-
12
- - 🔥 **Controllable CPU Load**: Generate precise CPU load per thread
13
- - 🎛️ **Per-Thread Control**: Individual sliders for each CPU thread
14
- - 🚀 **REST API**: Full programmatic control via HTTP endpoints
15
- - 🌐 **Modern WebUI**: Beautiful, responsive interface for real-time control
16
- - ⚡ **Instant Response**: Changes take effect immediately
17
- - 📊 **Live Monitoring**: View active threads and average load in real-time
18
-
19
- ## Screenshots
20
-
21
- ### WebUI Interface
22
- ![CPU Loader Control - 25% Load](https://github.com/user-attachments/assets/94f5a1a1-4328-46af-9155-e7d50cc8ba0e)
23
- *Individual thread control with sliders and preset buttons*
24
-
25
- ![CPU Loader Control - 50% Load](https://github.com/user-attachments/assets/bcab1f61-03a6-48ea-83b7-4402ec0d1389)
26
- *Setting all threads to 50% load*
27
-
28
- ![CPU Loader Control - 100% Load](https://github.com/user-attachments/assets/542afa1c-16da-4aff-a021-1e905e6f3372)
29
- *Maximum load across all threads*
10
+ ## ✨ Key Features
11
+
12
+ - **🎯 Precise Per-Thread Control**: Set individual CPU load (0-100%) for each core independently
13
+ - **⚡ High Performance**: Native C implementation with pthreads ensures accurate load generation
14
+ - **📊 Real-Time Monitoring**: Live WebSocket updates showing actual CPU usage via `psutil`
15
+ - **🎛️ Interactive WebUI**: Beautiful gradient interface with sliders and visual feedback
16
+ - **🚀 REST API**: Complete programmatic control for automation and testing
17
+ - **📱 Responsive Design**: Works seamlessly on desktop and mobile devices
18
+ - **🔄 Instant Updates**: Changes take effect immediately with sub-second response time
19
+ - 📡 **MQTT Integration**: Publish metrics and settings to MQTT broker for IoT/monitoring systems
20
+
21
+ ## 🖼️ Screenshots
22
+
23
+ ### Desktop Interface
24
+ <table>
25
+ <tr>
26
+ <td width="50%">
27
+ <img src="screenshots/01-initial-view.png" alt="Initial view with zero load">
28
+ <p align="center"><em>Clean interface showing all CPU threads at idle</em></p>
29
+ </td>
30
+ <td width="50%">
31
+ <img src="screenshots/02-with-load.png" alt="Mixed load across threads">
32
+ <p align="center"><em>Different load levels: 50%, 75%, and 25% on individual threads</em></p>
33
+ </td>
34
+ </tr>
35
+ <tr>
36
+ <td width="50%">
37
+ <img src="screenshots/03-live-metrics.png" alt="Live CPU metrics updating">
38
+ <p align="center"><em>Real-time metrics showing actual CPU usage with color-coded bars</em></p>
39
+ </td>
40
+ <td width="50%">
41
+ <img src="screenshots/04-mobile-view.png" alt="Mobile responsive view">
42
+ <p align="center"><em>Fully responsive mobile interface</em></p>
43
+ </td>
44
+ </tr>
45
+ </table>
46
+
47
+ ### Features Shown
48
+ - **Visual Load Bars**: Green progress bars show target load, blue bars display actual CPU usage
49
+ - **Real-Time Updates**: WebSocket connection provides live metrics every second
50
+ - **Preset Buttons**: Quick-set options (0%, 25%, 50%, 75%, 100%) for all threads
51
+ - **Smooth Gradients**: Modern UI with purple-to-blue gradient design
30
52
 
31
53
  ## Installation
32
54
 
@@ -75,6 +97,22 @@ uv run src/main.py
75
97
 
76
98
  The server will start on `http://localhost:8000`
77
99
 
100
+ #### Command-Line Options
101
+
102
+ ```bash
103
+ uv run src/main.py --help
104
+ ```
105
+
106
+ **Available Options:**
107
+ - `--host HOST`: Host to bind the server to (default: 0.0.0.0)
108
+ - `--port PORT`: Port to bind the server to (default: 8000)
109
+ - `--mqtt-broker-host HOST`: MQTT broker hostname
110
+ - `--mqtt-broker-port PORT`: MQTT broker port (default: 1883)
111
+ - `--mqtt-username USER`: MQTT username
112
+ - `--mqtt-password PASS`: MQTT password
113
+ - `--mqtt-topic-prefix PREFIX`: MQTT topic prefix (default: cpu-loader)
114
+ - `--mqtt-client-id ID`: MQTT client ID (default: cpu-loader)
115
+
78
116
  ### WebUI
79
117
 
80
118
  Open your browser and navigate to `http://localhost:8000`
@@ -130,6 +168,88 @@ curl -X POST http://localhost:8000/api/threads \
130
168
 
131
169
  Once the server is running, visit `http://localhost:8000/docs` for interactive API documentation powered by Swagger UI.
132
170
 
171
+ ## MQTT Publishing
172
+
173
+ CPU Loader can publish real-time CPU metrics and load control settings to an MQTT broker for integration with home automation systems, monitoring tools, or custom applications.
174
+
175
+ ### MQTT Topics
176
+
177
+ The application publishes to two topics:
178
+
179
+ 1. **`{prefix}/cpu_metrics`**: Published every second with current CPU utilization
180
+ ```json
181
+ {
182
+ "total_cpu_percent": 25.5,
183
+ "per_cpu_percent": [25.0, 26.0, 25.3, 25.7]
184
+ }
185
+ ```
186
+
187
+ 2. **`{prefix}/load_settings`**: Published when load settings change (retained message)
188
+ ```json
189
+ {
190
+ "num_threads": 4,
191
+ "loads": {"0": 25.0, "1": 25.0, "2": 25.0, "3": 25.0},
192
+ "average_load": 25.0
193
+ }
194
+ ```
195
+
196
+ ### Configuration
197
+
198
+ MQTT can be configured using environment variables or command-line arguments. Command-line arguments take precedence over environment variables.
199
+
200
+ #### Using Environment Variables
201
+
202
+ ```bash
203
+ export MQTT_BROKER_HOST=mqtt.example.com
204
+ export MQTT_BROKER_PORT=1883
205
+ export MQTT_USERNAME=myuser
206
+ export MQTT_PASSWORD=mypassword
207
+ export MQTT_TOPIC_PREFIX=cpu-loader
208
+ export MQTT_CLIENT_ID=cpu-loader-001
209
+
210
+ uv run src/main.py
211
+ ```
212
+
213
+ #### Using Command-Line Arguments
214
+
215
+ ```bash
216
+ uv run src/main.py \
217
+ --mqtt-broker-host mqtt.example.com \
218
+ --mqtt-broker-port 1883 \
219
+ --mqtt-username myuser \
220
+ --mqtt-password mypassword \
221
+ --mqtt-topic-prefix cpu-loader \
222
+ --mqtt-client-id cpu-loader-001
223
+ ```
224
+
225
+ #### Testing with Mosquitto
226
+
227
+ To test MQTT publishing locally:
228
+
229
+ ```bash
230
+ # Install mosquitto
231
+ sudo apt-get install mosquitto mosquitto-clients
232
+
233
+ # Start CPU Loader with MQTT
234
+ uv run src/main.py --mqtt-broker-host localhost
235
+
236
+ # Subscribe to all topics (in another terminal)
237
+ mosquitto_sub -h localhost -t "cpu-loader/#" -v
238
+ ```
239
+
240
+ ### MQTT Settings
241
+
242
+ | Setting | Environment Variable | CLI Argument | Default | Description |
243
+ |---------|---------------------|--------------|---------|-------------|
244
+ | Broker Host | `MQTT_BROKER_HOST` | `--mqtt-broker-host` | None | MQTT broker hostname or IP |
245
+ | Broker Port | `MQTT_BROKER_PORT` | `--mqtt-broker-port` | 1883 | MQTT broker port |
246
+ | Username | `MQTT_USERNAME` | `--mqtt-username` | None | MQTT authentication username |
247
+ | Password | `MQTT_PASSWORD` | `--mqtt-password` | None | MQTT authentication password |
248
+ | Topic Prefix | `MQTT_TOPIC_PREFIX` | `--mqtt-topic-prefix` | cpu-loader | Prefix for all MQTT topics |
249
+ | Client ID | `MQTT_CLIENT_ID` | `--mqtt-client-id` | cpu-loader | MQTT client identifier |
250
+
251
+ **Note:** If no MQTT broker host is configured, MQTT publishing will be disabled and the application will function normally without it.
252
+
133
253
  ## Example Script
134
254
 
135
255
  An example script (`src/example.py`) is provided to demonstrate programmatic control:
@@ -154,6 +274,7 @@ The example demonstrates:
154
274
  - **src/cpu_loader_core.c**: High-performance C implementation using pthreads for CPU load generation
155
275
  - **src/cpu_loader.py**: Python wrapper providing a clean API to the C extension
156
276
  - **src/main.py**: FastAPI application with REST API and embedded WebUI
277
+ - **src/mqtt_publisher.py**: MQTT client for publishing metrics and settings
157
278
  - **Threading Model**: Native pthreads for maximum efficiency and precise timing
158
279
  - **Load Algorithm**: High-resolution busy-wait loops with nanosecond precision
159
280
 
@@ -163,6 +284,7 @@ The example demonstrates:
163
284
  - FastAPI 0.115.0+
164
285
  - Uvicorn 0.32.0+
165
286
  - Pydantic 2.10.0+
287
+ - paho-mqtt 2.1.0+ (for MQTT publishing)
166
288
  - C compiler (gcc or clang) for building the extension
167
289
 
168
290
  ## Use Cases
@@ -172,6 +294,8 @@ The example demonstrates:
172
294
  - **Thermal Testing**: Check cooling system effectiveness
173
295
  - **Power Consumption Analysis**: Measure power usage at different load levels
174
296
  - **Benchmarking**: Create reproducible load scenarios
297
+ - **IoT Integration**: Integrate with Home Assistant, Node-RED, or other MQTT-based systems
298
+ - **Monitoring**: Feed CPU metrics into monitoring dashboards via MQTT
175
299
 
176
300
  ## Development
177
301
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cpu-loader"
3
- version = "0.0.2"
3
+ version = "0.1.2"
4
4
  description = "A tool to generate CPU load on a system with runtime configuration through a WebUI and REST API"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.8"
@@ -33,6 +33,7 @@ dependencies = [
33
33
  "requests==2.32.3",
34
34
  "psutil>=5.9.0",
35
35
  "websockets>=12.0",
36
+ "paho-mqtt>=2.1.0",
36
37
  ]
37
38
 
38
39
  [project.scripts]
@@ -50,4 +51,4 @@ build-backend = "setuptools.build_meta"
50
51
 
51
52
  [tool.setuptools]
52
53
  package-dir = {"" = "src"}
53
- py-modules = ["cpu_loader", "main", "example"]
54
+ py-modules = ["cpu_loader", "main", "example", "mqtt_publisher"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cpu-loader
3
- Version: 0.0.2
3
+ Version: 0.1.2
4
4
  Summary: A tool to generate CPU load on a system with runtime configuration through a WebUI and REST API
5
5
  Author-email: the78mole <the78mole@users.noreply.github.com>
6
6
  License: MIT
@@ -33,6 +33,7 @@ Requires-Dist: pydantic==2.10.0
33
33
  Requires-Dist: requests==2.32.3
34
34
  Requires-Dist: psutil>=5.9.0
35
35
  Requires-Dist: websockets>=12.0
36
+ Requires-Dist: paho-mqtt>=2.1.0
36
37
  Dynamic: license-file
37
38
 
38
39
  # CPU Loader
@@ -44,26 +45,48 @@ Dynamic: license-file
44
45
 
45
46
  A tool to generate CPU load on a system with runtime configuration through a WebUI and REST API.
46
47
 
47
- ## Features
48
-
49
- - 🔥 **Controllable CPU Load**: Generate precise CPU load per thread
50
- - 🎛️ **Per-Thread Control**: Individual sliders for each CPU thread
51
- - 🚀 **REST API**: Full programmatic control via HTTP endpoints
52
- - 🌐 **Modern WebUI**: Beautiful, responsive interface for real-time control
53
- - ⚡ **Instant Response**: Changes take effect immediately
54
- - 📊 **Live Monitoring**: View active threads and average load in real-time
55
-
56
- ## Screenshots
57
-
58
- ### WebUI Interface
59
- ![CPU Loader Control - 25% Load](https://github.com/user-attachments/assets/94f5a1a1-4328-46af-9155-e7d50cc8ba0e)
60
- *Individual thread control with sliders and preset buttons*
61
-
62
- ![CPU Loader Control - 50% Load](https://github.com/user-attachments/assets/bcab1f61-03a6-48ea-83b7-4402ec0d1389)
63
- *Setting all threads to 50% load*
64
-
65
- ![CPU Loader Control - 100% Load](https://github.com/user-attachments/assets/542afa1c-16da-4aff-a021-1e905e6f3372)
66
- *Maximum load across all threads*
48
+ ## ✨ Key Features
49
+
50
+ - **🎯 Precise Per-Thread Control**: Set individual CPU load (0-100%) for each core independently
51
+ - **⚡ High Performance**: Native C implementation with pthreads ensures accurate load generation
52
+ - **📊 Real-Time Monitoring**: Live WebSocket updates showing actual CPU usage via `psutil`
53
+ - **🎛️ Interactive WebUI**: Beautiful gradient interface with sliders and visual feedback
54
+ - **🚀 REST API**: Complete programmatic control for automation and testing
55
+ - **📱 Responsive Design**: Works seamlessly on desktop and mobile devices
56
+ - **🔄 Instant Updates**: Changes take effect immediately with sub-second response time
57
+ - 📡 **MQTT Integration**: Publish metrics and settings to MQTT broker for IoT/monitoring systems
58
+
59
+ ## 🖼️ Screenshots
60
+
61
+ ### Desktop Interface
62
+ <table>
63
+ <tr>
64
+ <td width="50%">
65
+ <img src="screenshots/01-initial-view.png" alt="Initial view with zero load">
66
+ <p align="center"><em>Clean interface showing all CPU threads at idle</em></p>
67
+ </td>
68
+ <td width="50%">
69
+ <img src="screenshots/02-with-load.png" alt="Mixed load across threads">
70
+ <p align="center"><em>Different load levels: 50%, 75%, and 25% on individual threads</em></p>
71
+ </td>
72
+ </tr>
73
+ <tr>
74
+ <td width="50%">
75
+ <img src="screenshots/03-live-metrics.png" alt="Live CPU metrics updating">
76
+ <p align="center"><em>Real-time metrics showing actual CPU usage with color-coded bars</em></p>
77
+ </td>
78
+ <td width="50%">
79
+ <img src="screenshots/04-mobile-view.png" alt="Mobile responsive view">
80
+ <p align="center"><em>Fully responsive mobile interface</em></p>
81
+ </td>
82
+ </tr>
83
+ </table>
84
+
85
+ ### Features Shown
86
+ - **Visual Load Bars**: Green progress bars show target load, blue bars display actual CPU usage
87
+ - **Real-Time Updates**: WebSocket connection provides live metrics every second
88
+ - **Preset Buttons**: Quick-set options (0%, 25%, 50%, 75%, 100%) for all threads
89
+ - **Smooth Gradients**: Modern UI with purple-to-blue gradient design
67
90
 
68
91
  ## Installation
69
92
 
@@ -112,6 +135,22 @@ uv run src/main.py
112
135
 
113
136
  The server will start on `http://localhost:8000`
114
137
 
138
+ #### Command-Line Options
139
+
140
+ ```bash
141
+ uv run src/main.py --help
142
+ ```
143
+
144
+ **Available Options:**
145
+ - `--host HOST`: Host to bind the server to (default: 0.0.0.0)
146
+ - `--port PORT`: Port to bind the server to (default: 8000)
147
+ - `--mqtt-broker-host HOST`: MQTT broker hostname
148
+ - `--mqtt-broker-port PORT`: MQTT broker port (default: 1883)
149
+ - `--mqtt-username USER`: MQTT username
150
+ - `--mqtt-password PASS`: MQTT password
151
+ - `--mqtt-topic-prefix PREFIX`: MQTT topic prefix (default: cpu-loader)
152
+ - `--mqtt-client-id ID`: MQTT client ID (default: cpu-loader)
153
+
115
154
  ### WebUI
116
155
 
117
156
  Open your browser and navigate to `http://localhost:8000`
@@ -167,6 +206,88 @@ curl -X POST http://localhost:8000/api/threads \
167
206
 
168
207
  Once the server is running, visit `http://localhost:8000/docs` for interactive API documentation powered by Swagger UI.
169
208
 
209
+ ## MQTT Publishing
210
+
211
+ CPU Loader can publish real-time CPU metrics and load control settings to an MQTT broker for integration with home automation systems, monitoring tools, or custom applications.
212
+
213
+ ### MQTT Topics
214
+
215
+ The application publishes to two topics:
216
+
217
+ 1. **`{prefix}/cpu_metrics`**: Published every second with current CPU utilization
218
+ ```json
219
+ {
220
+ "total_cpu_percent": 25.5,
221
+ "per_cpu_percent": [25.0, 26.0, 25.3, 25.7]
222
+ }
223
+ ```
224
+
225
+ 2. **`{prefix}/load_settings`**: Published when load settings change (retained message)
226
+ ```json
227
+ {
228
+ "num_threads": 4,
229
+ "loads": {"0": 25.0, "1": 25.0, "2": 25.0, "3": 25.0},
230
+ "average_load": 25.0
231
+ }
232
+ ```
233
+
234
+ ### Configuration
235
+
236
+ MQTT can be configured using environment variables or command-line arguments. Command-line arguments take precedence over environment variables.
237
+
238
+ #### Using Environment Variables
239
+
240
+ ```bash
241
+ export MQTT_BROKER_HOST=mqtt.example.com
242
+ export MQTT_BROKER_PORT=1883
243
+ export MQTT_USERNAME=myuser
244
+ export MQTT_PASSWORD=mypassword
245
+ export MQTT_TOPIC_PREFIX=cpu-loader
246
+ export MQTT_CLIENT_ID=cpu-loader-001
247
+
248
+ uv run src/main.py
249
+ ```
250
+
251
+ #### Using Command-Line Arguments
252
+
253
+ ```bash
254
+ uv run src/main.py \
255
+ --mqtt-broker-host mqtt.example.com \
256
+ --mqtt-broker-port 1883 \
257
+ --mqtt-username myuser \
258
+ --mqtt-password mypassword \
259
+ --mqtt-topic-prefix cpu-loader \
260
+ --mqtt-client-id cpu-loader-001
261
+ ```
262
+
263
+ #### Testing with Mosquitto
264
+
265
+ To test MQTT publishing locally:
266
+
267
+ ```bash
268
+ # Install mosquitto
269
+ sudo apt-get install mosquitto mosquitto-clients
270
+
271
+ # Start CPU Loader with MQTT
272
+ uv run src/main.py --mqtt-broker-host localhost
273
+
274
+ # Subscribe to all topics (in another terminal)
275
+ mosquitto_sub -h localhost -t "cpu-loader/#" -v
276
+ ```
277
+
278
+ ### MQTT Settings
279
+
280
+ | Setting | Environment Variable | CLI Argument | Default | Description |
281
+ |---------|---------------------|--------------|---------|-------------|
282
+ | Broker Host | `MQTT_BROKER_HOST` | `--mqtt-broker-host` | None | MQTT broker hostname or IP |
283
+ | Broker Port | `MQTT_BROKER_PORT` | `--mqtt-broker-port` | 1883 | MQTT broker port |
284
+ | Username | `MQTT_USERNAME` | `--mqtt-username` | None | MQTT authentication username |
285
+ | Password | `MQTT_PASSWORD` | `--mqtt-password` | None | MQTT authentication password |
286
+ | Topic Prefix | `MQTT_TOPIC_PREFIX` | `--mqtt-topic-prefix` | cpu-loader | Prefix for all MQTT topics |
287
+ | Client ID | `MQTT_CLIENT_ID` | `--mqtt-client-id` | cpu-loader | MQTT client identifier |
288
+
289
+ **Note:** If no MQTT broker host is configured, MQTT publishing will be disabled and the application will function normally without it.
290
+
170
291
  ## Example Script
171
292
 
172
293
  An example script (`src/example.py`) is provided to demonstrate programmatic control:
@@ -191,6 +312,7 @@ The example demonstrates:
191
312
  - **src/cpu_loader_core.c**: High-performance C implementation using pthreads for CPU load generation
192
313
  - **src/cpu_loader.py**: Python wrapper providing a clean API to the C extension
193
314
  - **src/main.py**: FastAPI application with REST API and embedded WebUI
315
+ - **src/mqtt_publisher.py**: MQTT client for publishing metrics and settings
194
316
  - **Threading Model**: Native pthreads for maximum efficiency and precise timing
195
317
  - **Load Algorithm**: High-resolution busy-wait loops with nanosecond precision
196
318
 
@@ -200,6 +322,7 @@ The example demonstrates:
200
322
  - FastAPI 0.115.0+
201
323
  - Uvicorn 0.32.0+
202
324
  - Pydantic 2.10.0+
325
+ - paho-mqtt 2.1.0+ (for MQTT publishing)
203
326
  - C compiler (gcc or clang) for building the extension
204
327
 
205
328
  ## Use Cases
@@ -209,6 +332,8 @@ The example demonstrates:
209
332
  - **Thermal Testing**: Check cooling system effectiveness
210
333
  - **Power Consumption Analysis**: Measure power usage at different load levels
211
334
  - **Benchmarking**: Create reproducible load scenarios
335
+ - **IoT Integration**: Integrate with Home Assistant, Node-RED, or other MQTT-based systems
336
+ - **Monitoring**: Feed CPU metrics into monitoring dashboards via MQTT
212
337
 
213
338
  ## Development
214
339
 
@@ -7,6 +7,7 @@ src/cpu_loader.py
7
7
  src/cpu_loader_core.c
8
8
  src/example.py
9
9
  src/main.py
10
+ src/mqtt_publisher.py
10
11
  src/cpu_loader.egg-info/PKG-INFO
11
12
  src/cpu_loader.egg-info/SOURCES.txt
12
13
  src/cpu_loader.egg-info/dependency_links.txt
@@ -4,3 +4,4 @@ pydantic==2.10.0
4
4
  requests==2.32.3
5
5
  psutil>=5.9.0
6
6
  websockets>=12.0
7
+ paho-mqtt>=2.1.0
@@ -2,3 +2,4 @@ cpu_loader
2
2
  cpu_loader_core
3
3
  example
4
4
  main
5
+ mqtt_publisher
@@ -2,9 +2,11 @@
2
2
  CPU Loader FastAPI Application
3
3
  Provides REST API and WebUI for controlling CPU load.
4
4
  """
5
+ import argparse
5
6
  import asyncio
7
+ import logging
6
8
  from contextlib import asynccontextmanager
7
- from typing import Dict, List, Set
9
+ from typing import Dict, List, Optional, Set
8
10
 
9
11
  import psutil
10
12
  import uvicorn
@@ -13,6 +15,13 @@ from fastapi.responses import HTMLResponse
13
15
  from pydantic import BaseModel, Field
14
16
 
15
17
  from cpu_loader import CPULoader
18
+ from mqtt_publisher import MQTTPublisher
19
+
20
+ # Configure logging
21
+ logging.basicConfig(
22
+ level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
23
+ )
24
+ logger = logging.getLogger(__name__)
16
25
 
17
26
 
18
27
  # Pydantic models for request/response validation
@@ -44,8 +53,9 @@ class CPUMetricsResponse(BaseModel):
44
53
  per_cpu_percent: List[float]
45
54
 
46
55
 
47
- # Global CPU loader instance and WebSocket connections
56
+ # Global CPU loader instance, MQTT publisher, and WebSocket connections
48
57
  cpu_loader = None
58
+ mqtt_publisher: Optional[MQTTPublisher] = None
49
59
  websocket_connections: Set[WebSocket] = set()
50
60
  monitoring_task = None
51
61
 
@@ -82,17 +92,34 @@ async def cpu_monitoring_loop():
82
92
 
83
93
  # Remove disconnected clients
84
94
  websocket_connections.difference_update(disconnected)
95
+
96
+ # Publish to MQTT if enabled
97
+ if mqtt_publisher:
98
+ mqtt_publisher.publish_cpu_metrics(total_cpu, per_cpu)
99
+
85
100
  except Exception as e:
86
- print(f"Error in CPU monitoring loop: {e}")
101
+ logger.error(f"Error in CPU monitoring loop: {e}")
87
102
  await asyncio.sleep(1.0)
88
103
 
89
104
 
90
105
  @asynccontextmanager
91
106
  async def lifespan(app: FastAPI):
92
107
  """Lifespan context manager for startup and shutdown events."""
93
- global cpu_loader, monitoring_task
108
+ global cpu_loader, mqtt_publisher, monitoring_task
94
109
  # Startup
95
110
  cpu_loader = CPULoader()
111
+
112
+ # Initialize MQTT publisher with settings from arguments or environment
113
+ mqtt_args = getattr(app.state, "mqtt_args", {})
114
+ try:
115
+ mqtt_publisher = MQTTPublisher(**mqtt_args)
116
+ except ImportError:
117
+ logger.warning("MQTT publishing disabled: paho-mqtt not installed")
118
+ mqtt_publisher = None
119
+ except Exception as e:
120
+ logger.error(f"Failed to initialize MQTT publisher: {e}")
121
+ mqtt_publisher = None
122
+
96
123
  monitoring_task = asyncio.create_task(cpu_monitoring_loop())
97
124
  yield
98
125
  # Shutdown
@@ -102,6 +129,8 @@ async def lifespan(app: FastAPI):
102
129
  await monitoring_task
103
130
  except asyncio.CancelledError:
104
131
  pass
132
+ if mqtt_publisher:
133
+ mqtt_publisher.disconnect()
105
134
  cpu_loader.shutdown()
106
135
 
107
136
 
@@ -311,7 +340,7 @@ async def get_webui():
311
340
  </head>
312
341
  <body>
313
342
  <div class="container">
314
- <h1>🔥 CPU Loader Control</h1>
343
+ <h1>&#x1F525; CPU Loader Control</h1>
315
344
  <p class="subtitle">Control CPU load per thread in real-time</p>
316
345
 
317
346
  <div class="status">
@@ -615,6 +644,13 @@ async def set_thread_count(request: ThreadCountRequest):
615
644
  """Set the number of threads."""
616
645
  try:
617
646
  cpu_loader.set_num_threads(request.num_threads)
647
+
648
+ # Publish updated settings to MQTT
649
+ if mqtt_publisher:
650
+ mqtt_publisher.publish_load_settings(
651
+ cpu_loader.get_num_threads(), cpu_loader.get_all_loads()
652
+ )
653
+
618
654
  return {
619
655
  "status": "success",
620
656
  "num_threads": request.num_threads,
@@ -629,6 +665,13 @@ async def set_thread_load(thread_id: int, request: ThreadLoadRequest):
629
665
  """Set the CPU load for a specific thread."""
630
666
  try:
631
667
  cpu_loader.set_thread_load(thread_id, request.load_percent)
668
+
669
+ # Publish updated settings to MQTT
670
+ if mqtt_publisher:
671
+ mqtt_publisher.publish_load_settings(
672
+ cpu_loader.get_num_threads(), cpu_loader.get_all_loads()
673
+ )
674
+
632
675
  return {
633
676
  "status": "success",
634
677
  "thread_id": thread_id,
@@ -644,6 +687,13 @@ async def set_all_thread_loads(request: AllThreadsLoadRequest):
644
687
  """Set the same CPU load for all threads."""
645
688
  try:
646
689
  cpu_loader.set_all_loads(request.load_percent)
690
+
691
+ # Publish updated settings to MQTT
692
+ if mqtt_publisher:
693
+ mqtt_publisher.publish_load_settings(
694
+ cpu_loader.get_num_threads(), cpu_loader.get_all_loads()
695
+ )
696
+
647
697
  return {
648
698
  "status": "success",
649
699
  "load_percent": request.load_percent,
@@ -654,5 +704,74 @@ async def set_all_thread_loads(request: AllThreadsLoadRequest):
654
704
  raise HTTPException(status_code=400, detail=str(e))
655
705
 
656
706
 
707
+ def parse_args():
708
+ """Parse command-line arguments."""
709
+ parser = argparse.ArgumentParser(
710
+ description="CPU Loader - Generate controllable CPU load with REST API and WebUI"
711
+ )
712
+ parser.add_argument(
713
+ "--host",
714
+ default="0.0.0.0",
715
+ help="Host to bind the server to (default: 0.0.0.0)",
716
+ )
717
+ parser.add_argument(
718
+ "--port",
719
+ type=int,
720
+ default=8000,
721
+ help="Port to bind the server to (default: 8000)",
722
+ )
723
+
724
+ # MQTT arguments
725
+ mqtt_group = parser.add_argument_group("MQTT settings")
726
+ mqtt_group.add_argument(
727
+ "--mqtt-broker-host",
728
+ help="MQTT broker hostname (env: MQTT_BROKER_HOST)",
729
+ )
730
+ mqtt_group.add_argument(
731
+ "--mqtt-broker-port",
732
+ type=int,
733
+ help="MQTT broker port (env: MQTT_BROKER_PORT, default: 1883)",
734
+ )
735
+ mqtt_group.add_argument(
736
+ "--mqtt-username",
737
+ help="MQTT username (env: MQTT_USERNAME)",
738
+ )
739
+ mqtt_group.add_argument(
740
+ "--mqtt-password",
741
+ help="MQTT password (env: MQTT_PASSWORD)",
742
+ )
743
+ mqtt_group.add_argument(
744
+ "--mqtt-topic-prefix",
745
+ help="MQTT topic prefix (env: MQTT_TOPIC_PREFIX, default: cpu-loader)",
746
+ )
747
+ mqtt_group.add_argument(
748
+ "--mqtt-client-id",
749
+ help="MQTT client ID (env: MQTT_CLIENT_ID, default: cpu-loader)",
750
+ )
751
+
752
+ return parser.parse_args()
753
+
754
+
657
755
  if __name__ == "__main__":
658
- uvicorn.run(app, host="0.0.0.0", port=8000)
756
+ args = parse_args()
757
+
758
+ # Prepare MQTT arguments (only non-None values)
759
+ mqtt_args = {}
760
+ if args.mqtt_broker_host:
761
+ mqtt_args["broker_host"] = args.mqtt_broker_host
762
+ if args.mqtt_broker_port:
763
+ mqtt_args["broker_port"] = args.mqtt_broker_port
764
+ if args.mqtt_username:
765
+ mqtt_args["username"] = args.mqtt_username
766
+ if args.mqtt_password:
767
+ mqtt_args["password"] = args.mqtt_password
768
+ if args.mqtt_topic_prefix:
769
+ mqtt_args["topic_prefix"] = args.mqtt_topic_prefix
770
+ if args.mqtt_client_id:
771
+ mqtt_args["client_id"] = args.mqtt_client_id
772
+
773
+ # Store MQTT args in app state for lifespan to access
774
+ app.state.mqtt_args = mqtt_args
775
+
776
+ # Run the server
777
+ uvicorn.run(app, host=args.host, port=args.port)
@@ -0,0 +1,189 @@
1
+ """
2
+ MQTT Publisher Module
3
+ Publishes CPU load control settings and metrics to MQTT broker.
4
+ """
5
+ import json
6
+ import logging
7
+ import os
8
+ from typing import Dict, Optional
9
+
10
+ try:
11
+ import paho.mqtt.client as mqtt
12
+ except ImportError:
13
+ mqtt = None
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class MQTTPublisher:
19
+ """Handles publishing CPU load data to MQTT broker."""
20
+
21
+ def __init__(
22
+ self,
23
+ broker_host: Optional[str] = None,
24
+ broker_port: Optional[int] = None,
25
+ username: Optional[str] = None,
26
+ password: Optional[str] = None,
27
+ topic_prefix: Optional[str] = None,
28
+ client_id: Optional[str] = None,
29
+ ):
30
+ """
31
+ Initialize MQTT publisher.
32
+
33
+ Args:
34
+ broker_host: MQTT broker hostname (env: MQTT_BROKER_HOST)
35
+ broker_port: MQTT broker port (env: MQTT_BROKER_PORT, default: 1883)
36
+ username: MQTT username (env: MQTT_USERNAME)
37
+ password: MQTT password (env: MQTT_PASSWORD)
38
+ topic_prefix: Topic prefix (env: MQTT_TOPIC_PREFIX, default: cpu-loader)
39
+ client_id: MQTT client ID (env: MQTT_CLIENT_ID, default: cpu-loader)
40
+ """
41
+ if mqtt is None:
42
+ raise ImportError(
43
+ "paho-mqtt not installed. Install with: pip install paho-mqtt"
44
+ )
45
+
46
+ # Get settings from arguments or environment variables
47
+ self.broker_host = broker_host or os.getenv("MQTT_BROKER_HOST")
48
+ self.broker_port = int(
49
+ broker_port or int(os.getenv("MQTT_BROKER_PORT", "1883"))
50
+ )
51
+ self.username = username or os.getenv("MQTT_USERNAME")
52
+ self.password = password or os.getenv("MQTT_PASSWORD")
53
+ self.topic_prefix = topic_prefix or os.getenv("MQTT_TOPIC_PREFIX", "cpu-loader")
54
+ self.client_id = client_id or os.getenv("MQTT_CLIENT_ID", "cpu-loader")
55
+
56
+ self.client: Optional[mqtt.Client] = None
57
+ self.connected = False
58
+
59
+ # Only connect if broker host is provided
60
+ if self.broker_host:
61
+ self._connect()
62
+ else:
63
+ logger.info("MQTT broker host not configured, MQTT publishing disabled")
64
+
65
+ def _connect(self):
66
+ """Connect to MQTT broker."""
67
+ try:
68
+ # Create MQTT client
69
+ self.client = mqtt.Client(
70
+ client_id=self.client_id,
71
+ protocol=mqtt.MQTTv311,
72
+ )
73
+
74
+ # Set callbacks
75
+ self.client.on_connect = self._on_connect
76
+ self.client.on_disconnect = self._on_disconnect
77
+
78
+ # Set credentials if provided
79
+ if self.username:
80
+ self.client.username_pw_set(self.username, self.password)
81
+
82
+ # Connect to broker
83
+ logger.info(
84
+ f"Connecting to MQTT broker at {self.broker_host}:{self.broker_port}"
85
+ )
86
+ self.client.connect(self.broker_host, self.broker_port, keepalive=60)
87
+
88
+ # Start network loop in background thread
89
+ self.client.loop_start()
90
+
91
+ except Exception as e:
92
+ logger.error(f"Failed to connect to MQTT broker: {e}")
93
+ self.client = None
94
+
95
+ def _on_connect(self, client, userdata, flags, rc):
96
+ """Callback when connected to MQTT broker."""
97
+ if rc == 0:
98
+ self.connected = True
99
+ logger.info("Connected to MQTT broker")
100
+ else:
101
+ self.connected = False
102
+ logger.error(f"Failed to connect to MQTT broker with code: {rc}")
103
+
104
+ def _on_disconnect(self, client, userdata, rc):
105
+ """Callback when disconnected from MQTT broker."""
106
+ self.connected = False
107
+ if rc != 0:
108
+ logger.warning(f"Unexpected disconnect from MQTT broker: {rc}")
109
+ else:
110
+ logger.info("Disconnected from MQTT broker")
111
+
112
+ def publish_load_settings(self, num_threads: int, loads: Dict[int, float]):
113
+ """
114
+ Publish load control settings to MQTT.
115
+
116
+ Args:
117
+ num_threads: Number of active threads
118
+ loads: Dictionary mapping thread ID to load percentage
119
+ """
120
+ if not self.connected or not self.client:
121
+ return
122
+
123
+ try:
124
+ # Calculate average load
125
+ avg_load = sum(loads.values()) / len(loads) if loads else 0.0
126
+
127
+ # Prepare payload
128
+ payload = {
129
+ "num_threads": num_threads,
130
+ "loads": loads,
131
+ "average_load": round(avg_load, 2),
132
+ }
133
+
134
+ # Publish to topic
135
+ topic = f"{self.topic_prefix}/load_settings"
136
+ self.client.publish(
137
+ topic,
138
+ json.dumps(payload),
139
+ qos=1,
140
+ retain=True,
141
+ )
142
+ logger.debug(f"Published load settings to {topic}")
143
+
144
+ except Exception as e:
145
+ logger.error(f"Failed to publish load settings: {e}")
146
+
147
+ def publish_cpu_metrics(self, total_cpu_percent: float, per_cpu_percent: list):
148
+ """
149
+ Publish CPU metrics to MQTT.
150
+
151
+ Args:
152
+ total_cpu_percent: Total CPU utilization percentage
153
+ per_cpu_percent: List of per-CPU utilization percentages
154
+ """
155
+ if not self.connected or not self.client:
156
+ return
157
+
158
+ try:
159
+ # Prepare payload
160
+ payload = {
161
+ "total_cpu_percent": round(total_cpu_percent, 1),
162
+ "per_cpu_percent": [round(cpu, 1) for cpu in per_cpu_percent],
163
+ }
164
+
165
+ # Publish to topic
166
+ topic = f"{self.topic_prefix}/cpu_metrics"
167
+ self.client.publish(
168
+ topic,
169
+ json.dumps(payload),
170
+ qos=0,
171
+ retain=False,
172
+ )
173
+ logger.debug(f"Published CPU metrics to {topic}")
174
+
175
+ except Exception as e:
176
+ logger.error(f"Failed to publish CPU metrics: {e}")
177
+
178
+ def disconnect(self):
179
+ """Disconnect from MQTT broker."""
180
+ if self.client:
181
+ try:
182
+ self.client.loop_stop()
183
+ self.client.disconnect()
184
+ logger.info("Disconnected from MQTT broker")
185
+ except Exception as e:
186
+ logger.error(f"Error disconnecting from MQTT broker: {e}")
187
+ finally:
188
+ self.client = None
189
+ self.connected = False
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes