Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
ReadSensorViewModel.cs
1using CommunityToolkit.Mvvm.ComponentModel;
2using CommunityToolkit.Mvvm.Input;
9using System.Collections.ObjectModel;
10using System.ComponentModel;
15using Waher.Things;
17
19{
23 public partial class ReadSensorViewModel : XmppViewModel
24 {
25 private readonly ContactInfo? thing;
26 private readonly ThingReference? thingRef;
27 private SensorDataClientRequest? request;
28
34 : base()
35 {
36 this.thing = Args?.Thing;
37
38 this.SensorData = [];
39
40 if (this.thing is not null)
41 {
42 if (string.IsNullOrEmpty(this.thing.NodeId) && string.IsNullOrEmpty(this.thing.SourceId) && string.IsNullOrEmpty(this.thing.Partition))
43 this.thingRef = null;
44 else
45 this.thingRef = new ThingReference(this.thing.NodeId, this.thing.SourceId, this.thing.Partition);
46
47 if (this.thing.MetaData is not null && this.thing.MetaData.Length > 0)
48 {
49 this.SensorData.Add(new HeaderModel(ServiceRef.Localizer[nameof(AppResources.GeneralInformation)]));
50
51 foreach (Property Tag in this.thing.MetaData)
52 this.SensorData.Add(new HumanReadableTag(Tag));
53 }
54
55 this.SupportsSensorEvents = this.thing.SupportsSensorEvents ?? false;
56 }
57 }
58
60 protected override async Task OnInitialize()
61 {
62 await base.OnInitialize();
63
64 this.CalcThingIsOnline();
65
66 ServiceRef.XmppService.OnPresence += this.Xmpp_OnPresence;
67 ServiceRef.TagProfile.Changed += this.TagProfile_Changed;
68
69 string? FullJid = this.GetFullJid();
70
71 if (!string.IsNullOrEmpty(FullJid))
72 {
73 if (this.thingRef is null)
74 this.request = ServiceRef.XmppService.RequestSensorReadout(FullJid, FieldType.All);
75 else
76 this.request = ServiceRef.XmppService.RequestSensorReadout(FullJid, [this.thingRef], FieldType.All);
77
78 this.request.OnStateChanged += this.Request_OnStateChanged;
79 this.request.OnFieldsReceived += this.Request_OnFieldsReceived;
80 this.request.OnErrorsReceived += this.Request_OnErrorsReceived;
81 }
82
83 ServiceRef.XmppService.RegisterPepHandler(typeof(SensorData), this.SensorDataPersonalEventHandler);
84 }
85
86 private Task Request_OnStateChanged(object? Sender, SensorDataReadoutState NewState)
87 {
88 this.Status = NewState switch
89 {
90 SensorDataReadoutState.Requested => ServiceRef.Localizer[nameof(AppResources.SensorDataRequested)],
91 SensorDataReadoutState.Accepted => ServiceRef.Localizer[nameof(AppResources.SensorDataAccepted)],
92 SensorDataReadoutState.Cancelled => ServiceRef.Localizer[nameof(AppResources.SensorDataCancelled)],
93 SensorDataReadoutState.Done => ServiceRef.Localizer[nameof(AppResources.SensorDataDone)],
94 SensorDataReadoutState.Failure => ServiceRef.Localizer[nameof(AppResources.SensorDataFailure)],
95 SensorDataReadoutState.Receiving => ServiceRef.Localizer[nameof(AppResources.SensorDataReceiving)],
96 SensorDataReadoutState.Started => ServiceRef.Localizer[nameof(AppResources.SensorDataStarted)],
97 _ => string.Empty,
98 };
99
100 return Task.CompletedTask;
101 }
102
103 private static string GetFieldTypeString(FieldType Type)
104 {
105 if (Type.HasFlag(FieldType.Identity))
106 return ServiceRef.Localizer[nameof(AppResources.SensorDataHeaderIdentity)];
107 else if (Type.HasFlag(FieldType.Status))
108 return ServiceRef.Localizer[nameof(AppResources.SensorDataHeaderStatus)];
109 else if (Type.HasFlag(FieldType.Momentary))
110 return ServiceRef.Localizer[nameof(AppResources.SensorDataHeaderMomentary)];
111 else if (Type.HasFlag(FieldType.Peak))
112 return ServiceRef.Localizer[nameof(AppResources.SensorDataHeaderPeak)];
113 else if (Type.HasFlag(FieldType.Computed))
114 return ServiceRef.Localizer[nameof(AppResources.SensorDataHeaderComputed)];
115 else if (Type.HasFlag(FieldType.Historical))
116 return ServiceRef.Localizer[nameof(AppResources.SensorDataHeaderHistorical)];
117 else
118 return ServiceRef.Localizer[nameof(AppResources.SensorDataHeaderOther)];
119 }
120
121 private Task Request_OnFieldsReceived(object? Sender, IEnumerable<Field> NewFields)
122 {
123 return this.NewFieldsReported(NewFields);
124 }
125
126 private Task NewFieldsReported(IEnumerable<Field> NewFields)
127 {
128 MainThread.BeginInvokeOnMainThread(() =>
129 {
130 string Category;
131 HeaderModel? CategoryHeader = null;
132 string FieldName;
133 int CategoryIndex = 0;
134 int i, j, c;
135 bool IsMin;
136 bool IsMax;
137
138 foreach (Field Field in NewFields)
139 {
140 FieldName = Field.Name;
141
142 if (Field.Type.HasFlag(FieldType.Historical))
143 {
144 IsMin = false;
145 IsMax = false;
146
147 if (FieldName.EndsWith(", Min", StringComparison.InvariantCultureIgnoreCase))
148 {
149 FieldName = FieldName[0..^5];
150 IsMin = true;
151 }
152 else if (FieldName.EndsWith(", Max", StringComparison.InvariantCultureIgnoreCase))
153 {
154 FieldName = FieldName[0..^5];
155 IsMax = true;
156 }
157
158 Category = FieldName;
159 }
160 else
161 {
162 Category = GetFieldTypeString(Field.Type);
163 IsMin = IsMax = false;
164 }
165
166 if (CategoryHeader is null || CategoryHeader.Label != Category)
167 {
168 CategoryHeader = null;
169 CategoryIndex = 0;
170
171 foreach (object Item in this.SensorData)
172 {
173 if (Item is HeaderModel Header && Header.Label == Category)
174 {
175 CategoryHeader = Header;
176 break;
177 }
178 else
179 CategoryIndex++;
180 }
181
182 if (CategoryHeader is null)
183 {
184 CategoryHeader = new HeaderModel(Category);
185 this.SensorData.Add(CategoryHeader);
186 }
187 }
188
189 if (Field.Type.HasFlag(FieldType.Historical))
190 {
191 for (i = CategoryIndex + 1, c = this.SensorData.Count; i < c; i++)
192 {
193 object Obj = this.SensorData[i];
194
195 if (Obj is GraphModel GraphModel)
196 {
197 j = string.Compare(FieldName, GraphModel.FieldName, StringComparison.OrdinalIgnoreCase);
198 if (j < 0)
199 continue;
200 else
201 {
202 if (j == 0)
203 {
204 if (IsMin)
206 else if (IsMax)
208 else
210 }
211 else if (j > 0 && !IsMin && !IsMax)
212 this.SensorData.Insert(i, new GraphModel(Field));
213
214 break;
215 }
216 }
217 else
218 {
219 this.SensorData.Insert(i, new GraphModel(Field));
220 break;
221 }
222 }
223
224 if (i >= c)
225 this.SensorData.Add(new GraphModel(Field));
226 }
227 else
228 {
229 for (i = CategoryIndex + 1, c = this.SensorData.Count; i < c; i++)
230 {
231 object Obj = this.SensorData[i];
232
233 if (Obj is FieldModel FieldModel)
234 {
235 j = string.Compare(FieldName, FieldModel.Name, StringComparison.OrdinalIgnoreCase);
236 if (j < 0)
237 continue;
238 else
239 {
240 if (j == 0)
241 FieldModel.Field = Field;
242 else if (j > 0)
243 this.SensorData.Insert(i, new FieldModel(Field));
244
245 break;
246 }
247 }
248 else
249 {
250 this.SensorData.Insert(i, new FieldModel(Field));
251 break;
252 }
253 }
254
255 if (i >= c)
256 this.SensorData.Add(new FieldModel(Field));
257 }
258 }
259 });
260
261 return Task.CompletedTask;
262 }
263
264 private Task Request_OnErrorsReceived(object? Sender, IEnumerable<ThingError> NewErrors)
265 {
266 return this.NewErrorsReported(NewErrors);
267 }
268
269 private Task NewErrorsReported(IEnumerable<ThingError> NewErrors)
270 {
271 MainThread.BeginInvokeOnMainThread(() =>
272 {
273 string Errors = ServiceRef.Localizer[nameof(AppResources.Errors)];
274 HeaderModel? CategoryHeader = null;
275 int CategoryIndex = 0;
276 int i, c;
277
278 foreach (object Item in this.SensorData)
279 {
280 if (Item is HeaderModel Header && Header.Label == Errors)
281 {
282 CategoryHeader = Header;
283 break;
284 }
285 else
286 CategoryIndex++;
287 }
288
289 if (CategoryHeader is null)
290 {
291 CategoryHeader = new HeaderModel(Errors);
292 this.SensorData.Add(CategoryHeader);
293 }
294
295 for (i = CategoryIndex + 1, c = this.SensorData.Count; i < c; i++)
296 {
297 object Obj = this.SensorData[i];
298
299 if (Obj is ErrorModel ErrorModel)
300 continue;
301 else
302 {
303 foreach (ThingError Error in NewErrors)
304 {
305 this.SensorData.Insert(i++, new ErrorModel(Error));
306 c++;
307 }
308
309 break;
310 }
311 }
312
313 if (i >= c)
314 {
315 foreach (ThingError Error in NewErrors)
316 this.SensorData.Add(new ErrorModel(Error));
317 }
318 });
319
320 return Task.CompletedTask;
321 }
322
323 private async Task SensorDataPersonalEventHandler(object? Sender, PersonalEventNotificationEventArgs e)
324 {
325 if (e.PersonalEvent is SensorData SensorData &&
326 !string.IsNullOrEmpty(this.thing?.BareJid) &&
327 string.Equals(this.thing.BareJid, e.Publisher, StringComparison.OrdinalIgnoreCase) &&
328 string.IsNullOrEmpty(this.thing.SourceId) &&
329 string.IsNullOrEmpty(this.thing.NodeId) &&
330 string.IsNullOrEmpty(this.thing.Partition))
331 {
332 if (SensorData.Fields is not null)
333 await this.NewFieldsReported(SensorData.Fields);
334
335 if (SensorData.Errors is not null)
336 await this.NewErrorsReported(SensorData.Errors);
337 }
338 }
339
341 protected override async Task OnDispose()
342 {
343 ServiceRef.XmppService.UnregisterPepHandler(typeof(SensorData), this.SensorDataPersonalEventHandler);
344
345 ServiceRef.XmppService.OnPresence -= this.Xmpp_OnPresence;
346 ServiceRef.TagProfile.Changed -= this.TagProfile_Changed;
347
348 if (this.request is not null &&
349 (this.request.State == SensorDataReadoutState.Receiving ||
350 this.request.State == SensorDataReadoutState.Accepted ||
351 this.request.State == SensorDataReadoutState.Requested ||
352 this.request.State == SensorDataReadoutState.Started))
353 {
354 await this.request.Cancel();
355 }
356
357 await base.OnDispose();
358 }
359
360 private Task Xmpp_OnPresence(object? Sender, PresenceEventArgs e)
361 {
362 this.CalcThingIsOnline();
363 return Task.CompletedTask;
364 }
365
366 private void CalcThingIsOnline()
367 {
368 if (this.thing is null)
369 this.IsThingOnline = false;
370 else
371 {
372 RosterItem? Item = ServiceRef.XmppService.GetRosterItem(this.thing.BareJid);
373 this.IsThingOnline = Item is not null && Item.HasLastPresence && Item.LastPresence.IsOnline;
374 }
375 }
376
377 private string? GetFullJid()
378 {
379 if (this.thing is null)
380 return null;
381 else
382 {
383 RosterItem? Item = ServiceRef.XmppService.GetRosterItem(this.thing.BareJid);
384
385 if (Item is null || !Item.HasLastPresence || !Item.LastPresence.IsOnline)
386 return null;
387 else
388 return Item.LastPresenceFullJid;
389 }
390 }
391
392 private void TagProfile_Changed(object? Sender, PropertyChangedEventArgs e)
393 {
394 MainThread.BeginInvokeOnMainThread(this.CalcThingIsOnline);
395 }
396
397 #region Properties
398
402 public ObservableCollection<object> SensorData { get; }
403
407 [ObservableProperty]
408 private bool isThingOnline;
409
413 [ObservableProperty]
414 private bool supportsSensorEvents;
415
419 [ObservableProperty]
420 private bool hasStatus;
421
425 [ObservableProperty]
426 private string? status;
427
429 protected override void OnPropertyChanged(PropertyChangedEventArgs e)
430 {
431 base.OnPropertyChanged(e);
432
433 switch (e.PropertyName)
434 {
435 case nameof(this.Status):
436 this.HasStatus = !string.IsNullOrEmpty(this.Status);
437 break;
438
439 case nameof(this.IsConnected):
440 this.CalcThingIsOnline();
441 break;
442 }
443 }
444
445 #endregion
446
450 [RelayCommand]
451 private static Task Click(object obj)
452 {
453 if (obj is HumanReadableTag Tag)
454 return ViewClaimThingViewModel.LabelClicked(Tag.Name, Tag.Value, Tag.LocalizedValue);
455 else if (obj is FieldModel Field)
457 else if (obj is ErrorModel Error)
458 return ViewClaimThingViewModel.LabelClicked(string.Empty, Error.ErrorMessage, Error.ErrorMessage);
459 else if (obj is string s)
460 return ViewClaimThingViewModel.LabelClicked(string.Empty, s, s);
461 else
462 return Task.CompletedTask;
463 }
464
465 }
466}
Contains information about a contact.
Definition: ContactInfo.cs:21
Base class that references services in the app.
Definition: ServiceRef.cs:31
static IStringLocalizer Localizer
Localization service
Definition: ServiceRef.cs:235
static IXmppService XmppService
The XMPP service for XMPP communication.
Definition: ServiceRef.cs:67
Class used to present a meta-data tag in a human interface.
Represents a set of historical field values.
Definition: GraphModel.cs:18
void Add(Field Field)
Adds a historical field value.
Definition: GraphModel.cs:56
void AddMax(Field Field)
Adds a historical maximum field value.
Definition: GraphModel.cs:84
void AddMin(Field Field)
Adds a historical minimum field value.
Definition: GraphModel.cs:70
The view model to bind to when displaying a thing.
override async Task OnDispose()
Method called when the view is disposed, and will not be used more. Use this method to unregister eve...
ReadSensorViewModel(ViewThingNavigationArgs? Args)
Creates an instance of the ReadSensorViewModel class.
override void OnPropertyChanged(PropertyChangedEventArgs e)
override async Task OnInitialize()
Method called when view is initialized for the first time. Use this method to implement registration ...
The view model to bind to for when displaying thing claim information.
static async Task LabelClicked(string Name, string Value, string LocalizedValue)
Processes the click of a localized meta-data label.
Holds navigation parameters specific to viewing things.
A view model that holds the XMPP state.
Maintains information about an item in the roster.
Definition: RosterItem.cs:75
bool HasLastPresence
If the roster item has received presence from an online resource having the given bare JID.
Definition: RosterItem.cs:425
string LastPresenceFullJid
Full JID of last resource sending online presence.
Definition: RosterItem.cs:343
PresenceEventArgs LastPresence
Last presence received from a resource having this bare JID.
Definition: RosterItem.cs:356
SensorDataReadoutState State
Current state of readout.
Contains personal sensor data.
Definition: SensorData.cs:15
IEnumerable< ThingError > Errors
Thing errors.
Definition: SensorData.cs:86
IEnumerable< Field > Fields
Sensor data fields.
Definition: SensorData.cs:73
Base class for all sensor data fields.
Definition: Field.cs:20
FieldType Type
Field Type flags.
Definition: Field.cs:259
abstract string ValueString
String representation of field value.
Definition: Field.cs:399
string Name
Unlocalized field name.
Definition: Field.cs:279
Contains information about an error on a thing
Definition: ThingError.cs:10
Contains a reference to a thing
class HeaderModel(string Label)
Represents a header
Definition: HeaderModel.cs:7
SensorDataReadoutState
Sensor Data Readout States.
FieldType
Field Type flags
Definition: FieldType.cs:10