MQTTSuite
Loading...
Searching...
No Matches
MqttModel.cpp
Go to the documentation of this file.
1/*
2 * MQTTSuite - A lightweight MQTT Integration System
3 * Copyright (C) Volker Christian <me@vchrist.at>
4 * 2022, 2023, 2024, 2025
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the Free
8 * Software Foundation, either version 3 of the License, or (at your option)
9 * any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 * more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20/*
21 * MIT License
22 *
23 * Permission is hereby granted, free of charge, to any person obtaining a copy
24 * of this software and associated documentation files (the "Software"), to deal
25 * in the Software without restriction, including without limitation the rights
26 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
27 * copies of the Software, and to permit persons to whom the Software is
28 * furnished to do so, subject to the following conditions:
29 *
30 * The above copyright notice and this permission notice shall be included in
31 * all copies or substantial portions of the Software.
32 *
33 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
39 * THE SOFTWARE.
40 */
41
42#include "MqttModel.h"
43
44#include "Mqtt.h"
45
46#ifndef DOXYGEN_SHOULD_SKIP_THIS
47
48#include <cctype>
49#include <core/socket/SocketAddress.h>
50#include <core/socket/stream/SocketConnection.h>
51#include <express/Response.h>
52#include <iot/mqtt/MqttContext.h>
53// #include <iot/mqtt/packets/Publish.h>
54#include <nlohmann/json.hpp>
55#include <nlohmann/json_fwd.hpp>
56#include <web/http/server/SocketContext.h>
57
58// IWYU pragma: no_include <nlohmann/detail/json_ref.hpp>
59
60#include <ctime>
61#include <functional>
62#include <iomanip>
63#include <sstream>
64#include <utility>
65
66struct tm;
67
68#endif // DOXYGEN_SHOULD_SKIP_THIS
69
70namespace mqtt::mqttbroker::lib {
71
73 : onlineSinceTimePoint(std::chrono::system_clock::now()) {
74 }
75
77 static MqttModel mqttModel;
78
79 return mqttModel;
80 }
81
82 static std::string windowId(const std::string& clientId) {
83 std::ostringstream windowId("window");
84 for (char ch : clientId) {
85 if (std::isalnum(static_cast<unsigned char>(ch))) {
86 windowId << ch;
87 } else {
88 windowId << std::hex << std::uppercase << std::setw(2) << std::setfill('0')
89 << static_cast<int>(static_cast<unsigned char>(ch));
90 }
91 }
92
93 return windowId.str();
94 }
95
96 static std::string href(const std::string& text, const std::string& url, const std::string& windowId, uint16_t width, uint16_t height) {
97 return "<a href=\"#\" onClick=\""
98 "let key = '" +
99 windowId +
100 "'; "
101 "if (!localStorage.getItem(key)) "
102 " localStorage.setItem(key, key + '-' + Math.random().toString(36).substr(2, 6)); "
103 "let uniqueId = localStorage.getItem(key); "
104 "if (!window._openWindows) window._openWindows = {}; "
105 "if (!window._openWindows[uniqueId] || window._openWindows[uniqueId].closed) { "
106 " window._openWindows[uniqueId] = window.open('" +
107 url + "', uniqueId, 'width=" + std::to_string(width) + ", height=" + std::to_string(height) +
108 ",location=no, menubar=no, status=no, toolbar=no'); "
109 "} else { "
110 " window._openWindows[uniqueId].focus(); "
111 "} return false;\" "
112 "style=\"color:inherit;\">" +
113 text + "</a>";
114 }
115
116 void MqttModel::addClient(const std::string& clientId, Mqtt* mqtt) {
117 MqttModelEntry mqttModelEntry(mqtt);
118
119 nlohmann::json json({href(mqtt->getClientId(), "/client?" + mqtt->getClientId(), windowId(mqtt->getClientId()), 450, 900),
120 mqttModelEntry.onlineSince(),
121 "<duration>" + mqttModelEntry.onlineDuration() + "</duration>",
122 mqtt->getConnectionName(),
123 mqtt->getMqttContext()->getSocketConnection()->getLocalAddress().toString(),
124 mqtt->getMqttContext()->getSocketConnection()->getRemoteAddress().toString(),
125 "<button class=\"red-btn\" onClick=\"disconnectClient('" + mqtt->getClientId() + "')\">Disconnect</button>"});
126
127 sendEvent(json.dump(), "connect", std::to_string(id++));
128
129 modelMap.emplace(clientId, std::move(mqttModelEntry));
130 }
131
132 void MqttModel::delClient(const std::string& clientId) {
133 sendEvent(clientId, "disconnect", std::to_string(id++));
134
135 modelMap.erase(clientId);
136 }
137
138 std::map<std::string, MqttModel::MqttModelEntry>& MqttModel::getClients() {
139 return modelMap;
140 }
141
142 const Mqtt* MqttModel::getMqtt(const std::string& clientId) {
143 const Mqtt* mqtt = nullptr;
144
145 auto modelIt = modelMap.find(clientId);
146 if (modelIt != modelMap.end()) {
147 mqtt = modelIt->second.getMqtt();
148 }
149
150 return mqtt;
151 }
152
153 std::string MqttModel::onlineSince() {
154 return timePointToString(onlineSinceTimePoint);
155 }
156
157 std::string MqttModel::onlineDuration() {
158 return durationToString(onlineSinceTimePoint);
159 }
160
161 void MqttModel::addEventReceiver(const std::shared_ptr<express::Response>& response, [[maybe_unused]] const std::string& lastEventId) {
162 auto& eventReceiver = eventReceiverList.emplace_back(response);
163
164 response->getSocketContext()->onDisconnected([this, &eventReceiver]() {
165 eventReceiverList.remove(eventReceiver);
166 });
167
168 for (auto& mqttModel : modelMap) {
169 nlohmann::json json({href(mqttModel.first, "/client?" + mqttModel.first, windowId(mqttModel.first), 450, 900),
170 mqttModel.second.onlineSince(),
171 "<duration>" + mqttModel.second.onlineDuration() + "</duration>",
172 mqttModel.second.getMqtt()->getConnectionName(),
173 mqttModel.second.getMqtt()->getMqttContext()->getSocketConnection()->getLocalAddress().toString(),
174 mqttModel.second.getMqtt()->getMqttContext()->getSocketConnection()->getRemoteAddress().toString(),
175 "<button class=\"red-btn\" onClick=\"disconnectClient('" + mqttModel.second.getMqtt()->getClientId() +
176 "')\">Disconnect</button>"});
177
178 sendEvent(json.dump(), "connect", std::to_string(id++));
179 }
180 }
181
182 void MqttModel::publish([[maybe_unused]] const iot::mqtt::packets::Publish& publish) {
183 /*
184 if (publish.getRetain()) {
185 sendEvent(publish.getTopic() + " : " + publish.getMessage(), "retain", std::to_string(id++));
186 }
187 */
188 }
189
191 : mqtt(mqtt) {
192 }
193
196
197 std::string MqttModel::MqttModelEntry::onlineSince() const {
198 return mqtt->getMqttContext()->getSocketConnection()->getSocketContext()->getOnlineSince();
199 }
200
202 return mqtt->getMqttContext()->getSocketConnection()->getSocketContext()->getOnlineDuration();
203 }
204
206 return mqtt;
207 }
208
209 void MqttModel::sendEvent(const std::string& data, const std::string& event, const std::string& id) {
210 for (auto& eventReceiver : eventReceiverList) {
211 if (const auto& response = eventReceiver.response.lock()) {
212 if (response->isConnected()) {
213 if (!event.empty()) {
214 response->sendFragment("event:" + event);
215 }
216 if (!id.empty()) {
217 response->sendFragment("id:" + id);
218 }
219 response->sendFragment("data:" + data);
220 response->sendFragment();
221 }
222 }
223 }
224 }
225
226 std::string MqttModel::timePointToString(const std::chrono::time_point<std::chrono::system_clock>& timePoint) {
227 std::time_t time = std::chrono::system_clock::to_time_t(timePoint);
228 std::tm* tm_ptr = std::gmtime(&time);
229
230 char buffer[100];
231 std::string onlineSince = "Formatting error";
232
233 // Format: "2025-02-02 14:30:00"
234 if (std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", tm_ptr)) {
235 onlineSince = std::string(buffer) + " UTC";
236 }
237
238 return onlineSince;
239 }
240
241 std::string MqttModel::durationToString(const std::chrono::time_point<std::chrono::system_clock>& bevore,
242 const std::chrono::time_point<std::chrono::system_clock>& later) {
243 using seconds_duration_type = std::chrono::duration<std::chrono::seconds::rep>::rep;
244
245 seconds_duration_type totalSeconds = std::chrono::duration_cast<std::chrono::seconds>(later - bevore).count();
246
247 // Compute days, hours, minutes, and seconds
248 seconds_duration_type days = totalSeconds / 86400; // 86400 seconds in a day
249 seconds_duration_type remainder = totalSeconds % 86400;
250 seconds_duration_type hours = remainder / 3600;
251 remainder = remainder % 3600;
252 seconds_duration_type minutes = remainder / 60;
253 seconds_duration_type seconds = remainder % 60;
254
255 // Format the components into a string using stringstream
256 std::ostringstream oss;
257 if (days > 0) {
258 oss << days << " day" << (days == 1 ? "" : "s") << ", ";
259 }
260 oss << std::setw(2) << std::setfill('0') << hours << ":" << std::setw(2) << std::setfill('0') << minutes << ":" << std::setw(2)
261 << std::setfill('0') << seconds;
262
263 return oss.str();
264 }
265
266 MqttModel::EventReceiver::EventReceiver(const std::shared_ptr<express::Response>& response)
267 : response(response)
268 , heartbeatTimer(core::timer::Timer::intervalTimer(
269 [response] {
270 response->sendFragment(":keep-alive");
271 response->sendFragment();
272 },
273 39)) {
274 }
275
279
281 return response.lock() == other.response.lock();
282 }
283
284} // namespace mqtt::mqttbroker::lib
bool operator==(const EventReceiver &other)
std::weak_ptr< express::Response > response
Definition MqttModel.h:96
EventReceiver(const std::shared_ptr< express::Response > &response)
void publish(const iot::mqtt::packets::Publish &publish)
void sendEvent(const std::string &data, const std::string &event="", const std::string &id="")
std::chrono::time_point< std::chrono::system_clock > onlineSinceTimePoint
Definition MqttModel.h:136
void delClient(const std::string &clientId)
void addClient(const std::string &clientId, Mqtt *mqtt)
static MqttModel & instance()
Definition MqttModel.cpp:76
std::list< EventReceiver > eventReceiverList
Definition MqttModel.h:134
std::map< std::string, MqttModelEntry > modelMap
Definition MqttModel.h:132
void addEventReceiver(const std::shared_ptr< express::Response > &response, const std::string &lastEventId)
std::map< std::string, MqttModelEntry > & getClients()
const Mqtt * getMqtt(const std::string &clientId)
static std::string href(const std::string &text, const std::string &url, const std::string &windowId, uint16_t width, uint16_t height)
Definition MqttModel.cpp:96
static std::string windowId(const std::string &clientId)
Definition MqttModel.cpp:82