Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
MqttBroker.cs
1using System;
3using System.Text;
4using System.Threading.Tasks;
5using Waher.Events;
10
12{
17 {
18 private static Scheduler scheduler = null;
19 private readonly SortedDictionary<string, MqttTopic> topics = new SortedDictionary<string, MqttTopic>();
20 private readonly MqttBrokerNode node;
21 private MqttClient mqttClient;
22 private bool connectionOk = false;
23 private readonly string host;
24 private readonly int port;
25 private readonly bool tls;
26 private readonly bool trustServer;
27 private readonly string userName;
28 private readonly string password;
29 private readonly string connectionSubscription;
30 private string willTopic;
31 private string willData;
32 private bool willRetain;
33 private MqttQualityOfService willQoS;
34 private DateTime nextCheck;
35
39 public MqttBroker(MqttBrokerNode Node, string Host, int Port, bool Tls, bool TrustServer, string UserName, string Password,
40 string ConnectionSubscription, string WillTopic, string WillData, bool WillRetain, MqttQualityOfService WillQoS)
41 {
42 this.node = Node;
43 this.host = Host;
44 this.port = Port;
45 this.tls = Tls;
46 this.trustServer = TrustServer;
47 this.userName = UserName;
48 this.password = Password;
49 this.connectionSubscription = ConnectionSubscription;
50 this.willTopic = WillTopic;
51 this.willData = WillData;
52 this.willRetain = WillRetain;
53 this.willQoS = WillQoS;
54
55 this.Open();
56 }
57
58 internal MqttClient Client => this.mqttClient;
59
63 public MqttBrokerNode Node => this.node;
64
65 private void Open()
66 {
67 this.mqttClient = new MqttClient(this.host, this.port, this.tls, this.userName, this.password, this.willTopic,
68 this.willQoS, this.willRetain, Encoding.UTF8.GetBytes(this.willData))
69 {
70 TrustServer = this.trustServer
71 };
72
73 this.mqttClient.OnConnectionError += this.MqttClient_OnConnectionError;
74 this.mqttClient.OnContentReceived += this.MqttClient_OnContentReceived;
75 this.mqttClient.OnStateChanged += this.MqttClient_OnStateChanged;
76
77 this.nextCheck = Scheduler.Add(DateTime.Now.AddMinutes(1), this.CheckOnline, null);
78 }
79
80 private async Task Close()
81 {
82 if (!(this.mqttClient is null))
83 {
84 Scheduler.Remove(this.nextCheck);
85
86 this.mqttClient.OnConnectionError -= this.MqttClient_OnConnectionError;
87 this.mqttClient.OnContentReceived -= this.MqttClient_OnContentReceived;
88 this.mqttClient.OnStateChanged -= this.MqttClient_OnStateChanged;
89
90 await this.mqttClient.DisposeAsync();
91 this.mqttClient = null;
92 }
93 }
94
98 [Obsolete("Use the DisposeAsync() method.")]
99 public void Dispose()
100 {
101 this.DisposeAsync().Wait();
102 }
103
107 public Task DisposeAsync()
108 {
109 return this.Close();
110 }
111
112 private async void CheckOnline(object _)
113 {
114 try
115 {
116 if (!(this.mqttClient is null) && !NetworkingModule.Stopping)
117 {
118 MqttState State = this.mqttClient.State;
119 if (State == MqttState.Offline || State == MqttState.Error || State == MqttState.Authenticating)
120 await this.mqttClient.Reconnect();
121 }
122 }
123 catch (Exception ex)
124 {
125 Log.Exception(ex);
126 }
127 finally
128 {
129 this.nextCheck = Scheduler.Add(DateTime.Now.AddMinutes(1), this.CheckOnline, null);
130 }
131 }
132
140 public Task Publish(string Topic, MqttQualityOfService QoS, bool Retain, byte[] Data)
141 {
142 return this.Client.PUBLISH(Topic, QoS, Retain, Data);
143 }
144
152 public Task Publish(string Topic, MqttQualityOfService QoS, bool Retain, string Data)
153 {
154 return this.Publish(Topic, QoS, Retain, Encoding.UTF8.GetBytes(Data));
155 }
156
160 public async Task DataReceived(MqttContent Content)
161 {
162 MqttTopic Topic = await this.GetTopic(Content.Topic, true, true);
163 if (!(Topic is null))
164 await Topic.DataReported(Content);
165 }
166
170 public async Task SetWill(string WillTopic, string WillData, bool WillRetain, MqttQualityOfService WillQoS)
171 {
172 if (this.willTopic != WillTopic || this.willData != WillData || this.willRetain != WillRetain || this.willQoS != WillQoS)
173 {
174 await this.Close();
175
176 this.willTopic = WillTopic;
177 this.willData = WillData;
178 this.willRetain = WillRetain;
179 this.willQoS = WillQoS;
180
181 this.Open();
182 }
183 }
184
185 private async Task MqttClient_OnStateChanged(object Sender, MqttState NewState)
186 {
187 try
188 {
189 switch (NewState)
190 {
191 case MqttState.Connected:
192 this.connectionOk = true;
193 await this.node.RemoveErrorAsync("Offline");
194 await this.node.RemoveErrorAsync("Error");
195
196 if (!string.IsNullOrEmpty(this.connectionSubscription))
197 {
198 string[] Parts = this.connectionSubscription.Split(',');
199 int i, c = Parts.Length;
200
201 for (i = 0; i < c; i++)
202 Parts[i] = Parts[i].Trim();
203
204 await this.mqttClient.SUBSCRIBE(MqttQualityOfService.AtLeastOnce, Parts);
205 }
206 break;
207
208 case MqttState.Error:
209 await this.node.LogErrorAsync("Error", "Connection to broker failed.");
210
212 await this.Reconnect();
213 break;
214
215 case MqttState.Offline:
216 await this.node.LogErrorAsync("Offline", "Connection is closed.");
217
219 await this.Reconnect();
220 break;
221 }
222 }
223 catch (Exception ex)
224 {
225 Log.Exception(ex);
226 }
227 }
228
229 private Task MqttClient_OnContentReceived(object Sender, MqttContent Content)
230 {
231 lock (this.topics)
232 {
233 if (this.processing)
234 {
235 this.queue.AddLast(Content);
236 return Task.CompletedTask;
237 }
238 else
239 this.processing = true;
240 }
241
242 this.Process(Content);
243
244 return Task.CompletedTask;
245 }
246
247 private readonly LinkedList<MqttContent> queue = new LinkedList<MqttContent>();
248 private bool processing = false;
249
250 private async void Process(MqttContent Content)
251 {
252 try
253 {
254 while (true)
255 {
256 MqttTopic Topic = await this.GetTopic(Content.Topic, true, true);
257 if (!(Topic is null))
258 await Topic.DataReported(Content);
259
260 lock (this.topics)
261 {
262 if (this.queue.First is null)
263 {
264 this.processing = false;
265 break;
266 }
267 else
268 {
269 Content = this.queue.First.Value;
270 this.queue.RemoveFirst();
271 }
272 }
273 }
274 }
275 catch (Exception ex)
276 {
277 Log.Exception(ex);
278 this.processing = false;
279 }
280 }
281
282 private async Task MqttClient_OnConnectionError(object Sender, Exception Exception)
283 {
285 await this.Reconnect();
286 }
287
288 private async Task Reconnect()
289 {
290 if (this.connectionOk)
291 {
292 this.connectionOk = false;
293
294 if (!(this.mqttClient is null) && !NetworkingModule.Stopping)
295 await this.mqttClient.Reconnect();
296 }
297 }
298
302 public async Task<MqttTopic> GetTopic(string TopicString, bool CreateNew, bool IgnoreGuids)
303 {
304 if (string.IsNullOrEmpty(TopicString))
305 return null;
306
307 MqttTopicRepresentation Representation = new MqttTopicRepresentation(TopicString, TopicString.Split('/'), 0);
308 MqttTopic Topic = await this.GetLocalTopic(Representation, CreateNew);
309
310 if (Topic is null)
311 return null;
312 else if (Representation.MoveNext(Topic))
313 return await Topic.GetTopic(Representation, CreateNew, IgnoreGuids, this);
314 else
315 return Topic;
316 }
317
318 private async Task<MqttTopic> GetLocalTopic(MqttTopicRepresentation Representation, bool CreateNew)
319 {
320 string CurrentSegment = Representation.CurrentSegment;
321 MqttTopic Topic, Topic2;
322
323 lock (this.topics)
324 {
325 if (this.topics.TryGetValue(CurrentSegment, out Topic))
326 return Topic;
327 }
328
329 if (Guid.TryParse(CurrentSegment.Replace('_', '-'), out Guid _))
330 return null;
331
332 if (this.node.HasChildren)
333 {
334 foreach (INode Child in await this.node.ChildNodes)
335 {
336 if (Child is IMqttTopicNode TopicNode && TopicNode.LocalTopic == CurrentSegment)
337 {
338 lock (this.topics)
339 {
340 if (this.topics.TryGetValue(CurrentSegment, out Topic2))
341 return Topic2;
342 else
343 {
344 Topic = new MqttTopic(TopicNode, CurrentSegment, CurrentSegment, null, this);
345 this.topics[CurrentSegment] = Topic;
346 return Topic;
347 }
348 }
349 }
350 }
351 }
352
353 if (!CreateNew)
354 return null;
355
356 IMqttTopicNode AddNode = Types.FindBest<IMqttTopicNode, MqttTopicRepresentation>(Representation);
357 if (AddNode is null)
358 return null;
359
360 AddNode = await AddNode.CreateNew(Representation);
361 Topic = new MqttTopic(AddNode, AddNode.LocalTopic, AddNode.LocalTopic, null, this);
362
363 lock (this.topics)
364 {
365 if (this.topics.TryGetValue(CurrentSegment, out Topic2))
366 return Topic2;
367 else
368 this.topics[CurrentSegment] = Topic;
369 }
370
371 await this.node.AddAsync(AddNode);
372
373 return Topic;
374 }
375
381 public bool Remove(string LocalTopic)
382 {
383 if (!(LocalTopic is null))
384 {
385 lock (this.topics)
386 {
387 return this.topics.Remove(LocalTopic);
388 }
389 }
390 else
391 return false;
392 }
393
397 public static Scheduler Scheduler
398 {
399 get
400 {
401 if (scheduler is null)
402 {
403 if (Types.TryGetModuleParameter("Scheduler", out Scheduler Scheduler))
404 scheduler = Scheduler;
405 else
406 {
407 scheduler = new Scheduler();
408
409 Log.Terminating += (Sender, e) =>
410 {
411 scheduler?.Dispose();
412 scheduler = null;
413
414 return Task.CompletedTask;
415 };
416 }
417 }
418
419 return scheduler;
420 }
421 }
422
423 }
424}
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1657
Manages an MQTT connection. Implements MQTT v3.1.1, as defined in http://docs.oasis-open....
Definition: MqttClient.cs:30
MqttState State
Current state of connection.
Definition: MqttClient.cs:821
async Task Reconnect()
Reconnects a client after an error or if it's offline. Reconnecting, instead of creating a completely...
Definition: MqttClient.cs:214
Task< ushort > PUBLISH(string Topic, MqttQualityOfService QoS, bool Retain, byte[] Data)
Publishes information on a topic.
Definition: MqttClient.cs:876
Task< ushort > SUBSCRIBE(string Topic, MqttQualityOfService QoS)
Subscribes to information from a topic. Topics can include wildcards.
Definition: MqttClient.cs:1013
async Task DisposeAsync()
Closes the connection and disposes of all resources.
Definition: MqttClient.cs:1192
Information about content received from the MQTT server.
Definition: MqttContent.cs:9
Module that controls the life cycle of communication.
static bool Stopping
If the system is stopping.
Static class that dynamically manages types and interfaces available in the runtime environment.
Definition: Types.cs:15
static bool TryGetModuleParameter(string Name, out object Value)
Tries to get a module parameter value.
Definition: Types.cs:607
Class that can be used to schedule events in time. It uses a timer to execute tasks at the appointed ...
Definition: Scheduler.cs:14
bool Remove(DateTime When)
Removes an event scheduled for a given point in time.
Definition: Scheduler.cs:186
void Dispose()
IDisposable.Dispose
Definition: Scheduler.cs:34
DateTime Add(DateTime When, Action< object > Callback, object State)
Adds an event.
Definition: Scheduler.cs:54
MQTT Broker connection object.
Definition: MqttBroker.cs:17
Task Publish(string Topic, MqttQualityOfService QoS, bool Retain, string Data)
Publishes text data to a topic.
Definition: MqttBroker.cs:152
async Task DataReceived(MqttContent Content)
TODO
Definition: MqttBroker.cs:160
static Scheduler Scheduler
Scheduler for asynchronous tasks.
Definition: MqttBroker.cs:398
bool Remove(string LocalTopic)
Removes a child topic
Definition: MqttBroker.cs:381
Task DisposeAsync()
Closes the connection and disposes of all resources.
Definition: MqttBroker.cs:107
MqttBroker(MqttBrokerNode Node, string Host, int Port, bool Tls, bool TrustServer, string UserName, string Password, string ConnectionSubscription, string WillTopic, string WillData, bool WillRetain, MqttQualityOfService WillQoS)
MQTT Broker connection object.
Definition: MqttBroker.cs:39
async Task SetWill(string WillTopic, string WillData, bool WillRetain, MqttQualityOfService WillQoS)
TODO
Definition: MqttBroker.cs:170
Task Publish(string Topic, MqttQualityOfService QoS, bool Retain, byte[] Data)
Publishes binary data to a topic.
Definition: MqttBroker.cs:140
MqttBrokerNode Node
Reference to broker node.
Definition: MqttBroker.cs:63
async Task< MqttTopic > GetTopic(string TopicString, bool CreateNew, bool IgnoreGuids)
Gets the Node responsible for managing a Topic
Definition: MqttBroker.cs:302
MQTT Topic information.
Definition: MqttTopic.cs:19
async Task DataReported(MqttContent Content)
Called when new data has been published.
Definition: MqttTopic.cs:176
Node representing a connection to an MQTT broker.
Contains information about an MQTT topic
bool MoveNext(MqttTopic NewParent)
Moves to the next segment.
string CurrentSegment
Current segment being processed.
Interface for asynchronously disposable objects.
Interface for nodes that are published through the concentrator interface.
Definition: INode.cs:49
Task< IEnumerable< INode > > ChildNodes
Child nodes. If no child nodes are available, null is returned.
Definition: INode.cs:140
Interface for MQTT Topic nodes.
string LocalTopic
Local Topic segment
Definition: ImplTypes.g.cs:58
MqttQualityOfService
MQTT Quality of Service level.
MqttState
State of MQTT connection.
Definition: MqttState.cs:11