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;
17using Waher.Things;
19
21{
25 public partial class ReadSensorViewModel : XmppViewModel
26 {
27 private readonly ContactInfo? thing;
28 private readonly ThingReference? thingRef;
29 private SensorDataClientRequest? request;
30
36 : base()
37 {
38 this.thing = Args?.Thing;
39
40 this.SensorData = [];
41
42 if (this.thing is not null)
43 {
44 if (string.IsNullOrEmpty(this.thing.NodeId) && string.IsNullOrEmpty(this.thing.SourceId) && string.IsNullOrEmpty(this.thing.Partition))
45 this.thingRef = null;
46 else
47 this.thingRef = new ThingReference(this.thing.NodeId, this.thing.SourceId, this.thing.Partition);
48
49 if (this.thing.MetaData is not null && this.thing.MetaData.Length > 0)
50 {
52
53 foreach (Property Tag in this.thing.MetaData)
54 this.SensorData.Add(new HumanReadableTag(Tag));
55 }
56
57 this.SupportsSensorEvents = this.thing.SupportsSensorEvents ?? false;
58 }
59 }
60
62 public override async Task OnInitializeAsync()
63 {
64 await base.OnInitializeAsync();
65
66 this.CalcThingIsOnline();
67
68 ServiceRef.XmppService.OnPresence += this.Xmpp_OnPresence;
69 ServiceRef.TagProfile.Changed += this.TagProfile_Changed;
70
71 string? FullJid = this.GetFullJid();
72
73 if (!string.IsNullOrEmpty(FullJid))
74 {
75 if (this.thingRef is null || this.thingRef.IsEmpty)
76 this.request = await ServiceRef.XmppService.RequestSensorReadout(FullJid, FieldType.All);
77 else
78 this.request = await ServiceRef.XmppService.RequestSensorReadout(FullJid, [this.thingRef], FieldType.All);
79
80 this.request.OnStateChanged += this.Request_OnStateChanged;
81 this.request.OnFieldsReceived += this.Request_OnFieldsReceived;
82 this.request.OnErrorsReceived += this.Request_OnErrorsReceived;
83 }
84
85 ServiceRef.XmppService.RegisterPepHandler(typeof(SensorData), this.SensorDataPersonalEventHandler);
86 }
87
88 private Task Request_OnStateChanged(object? Sender, SensorDataReadoutState NewState)
89 {
90 this.Status = NewState switch
91 {
92 SensorDataReadoutState.Requested => ServiceRef.Localizer[nameof(AppResources.SensorDataRequested)],
93 SensorDataReadoutState.Accepted => ServiceRef.Localizer[nameof(AppResources.SensorDataAccepted)],
94 SensorDataReadoutState.Cancelled => ServiceRef.Localizer[nameof(AppResources.SensorDataCancelled)],
95 SensorDataReadoutState.Done => ServiceRef.Localizer[nameof(AppResources.SensorDataDone)],
96 SensorDataReadoutState.Failure => ServiceRef.Localizer[nameof(AppResources.SensorDataFailure)],
97 SensorDataReadoutState.Receiving => ServiceRef.Localizer[nameof(AppResources.SensorDataReceiving)],
98 SensorDataReadoutState.Started => ServiceRef.Localizer[nameof(AppResources.SensorDataStarted)],
99 _ => string.Empty,
100 };
101
102 return Task.CompletedTask;
103 }
104
105 private static string GetFieldTypeString(FieldType Type)
106 {
107 if (Type.HasFlag(FieldType.Identity))
109 else if (Type.HasFlag(FieldType.Status))
111 else if (Type.HasFlag(FieldType.Momentary))
113 else if (Type.HasFlag(FieldType.Peak))
115 else if (Type.HasFlag(FieldType.Computed))
117 else if (Type.HasFlag(FieldType.Historical))
119 else
121 }
122
123 private Task Request_OnFieldsReceived(object? Sender, IEnumerable<Field> NewFields)
124 {
125 return this.NewFieldsReported(NewFields);
126 }
127
128 private Task NewFieldsReported(IEnumerable<Field> NewFields)
129 {
130 MainThread.BeginInvokeOnMainThread(() =>
131 {
132 string Category;
133 HeaderModel? CategoryHeader = null;
134 string FieldName;
135 int CategoryIndex = 0;
136 int i, j, c;
137 bool IsMin;
138 bool IsMax;
139
140 foreach (Field Field in NewFields)
141 {
142 FieldName = Field.Name;
143
144 if (Field.Type.HasFlag(FieldType.Historical))
145 {
146 IsMin = false;
147 IsMax = false;
148
149 if (FieldName.EndsWith(", Min", StringComparison.InvariantCultureIgnoreCase))
150 {
151 FieldName = FieldName[0..^5];
152 IsMin = true;
153 }
154 else if (FieldName.EndsWith(", Max", StringComparison.InvariantCultureIgnoreCase))
155 {
156 FieldName = FieldName[0..^5];
157 IsMax = true;
158 }
159
160 Category = FieldName;
161 }
162 else
163 {
164 Category = GetFieldTypeString(Field.Type);
165 IsMin = IsMax = false;
166 }
167
168 if (CategoryHeader is null || CategoryHeader.Label != Category)
169 {
170 CategoryHeader = null;
171 CategoryIndex = 0;
172
173 foreach (object Item in this.SensorData)
174 {
175 if (Item is HeaderModel Header && Header.Label == Category)
176 {
177 CategoryHeader = Header;
178 break;
179 }
180 else
181 CategoryIndex++;
182 }
183
184 if (CategoryHeader is null)
185 {
186 CategoryHeader = new HeaderModel(Category);
187 this.SensorData.Add(CategoryHeader);
188 }
189 }
190
191 if (Field.Type.HasFlag(FieldType.Historical))
192 {
193 for (i = CategoryIndex + 1, c = this.SensorData.Count; i < c; i++)
194 {
195 object Obj = this.SensorData[i];
196
197 if (Obj is GraphModel GraphModel)
198 {
199 j = string.Compare(FieldName, GraphModel.FieldName, StringComparison.OrdinalIgnoreCase);
200 if (j < 0)
201 continue;
202 else
203 {
204 if (j == 0)
205 {
206 if (IsMin)
208 else if (IsMax)
210 else
212 }
213 else if (j > 0 && !IsMin && !IsMax)
214 this.SensorData.Insert(i, new GraphModel(Field));
215
216 break;
217 }
218 }
219 else
220 {
221 this.SensorData.Insert(i, new GraphModel(Field));
222 break;
223 }
224 }
225
226 if (i >= c)
227 this.SensorData.Add(new GraphModel(Field));
228 }
229 else
230 {
231 for (i = CategoryIndex + 1, c = this.SensorData.Count; i < c; i++)
232 {
233 object Obj = this.SensorData[i];
234
235 if (Obj is FieldModel FieldModel)
236 {
237 j = string.Compare(FieldName, FieldModel.Name, StringComparison.OrdinalIgnoreCase);
238 if (j < 0)
239 continue;
240 else
241 {
242 if (j == 0)
243 FieldModel.Field = Field;
244 else if (j > 0)
245 this.SensorData.Insert(i, new FieldModel(Field));
246
247 break;
248 }
249 }
250 else
251 {
252 this.SensorData.Insert(i, new FieldModel(Field));
253 break;
254 }
255 }
256
257 if (i >= c)
258 this.SensorData.Add(new FieldModel(Field));
259 }
260 }
261 });
262
263 return Task.CompletedTask;
264 }
265
266 private Task Request_OnErrorsReceived(object? Sender, IEnumerable<ThingError> NewErrors)
267 {
268 return this.NewErrorsReported(NewErrors);
269 }
270
271 private Task NewErrorsReported(IEnumerable<ThingError> NewErrors)
272 {
273 MainThread.BeginInvokeOnMainThread(() =>
274 {
275 string Errors = ServiceRef.Localizer[nameof(AppResources.Errors)];
276 HeaderModel? CategoryHeader = null;
277 int CategoryIndex = 0;
278 int i, c;
279
280 foreach (object Item in this.SensorData)
281 {
282 if (Item is HeaderModel Header && Header.Label == Errors)
283 {
284 CategoryHeader = Header;
285 break;
286 }
287 else
288 CategoryIndex++;
289 }
290
291 if (CategoryHeader is null)
292 {
293 CategoryHeader = new HeaderModel(Errors);
294 this.SensorData.Add(CategoryHeader);
295 }
296
297 for (i = CategoryIndex + 1, c = this.SensorData.Count; i < c; i++)
298 {
299 object Obj = this.SensorData[i];
300
301 if (Obj is ErrorModel ErrorModel)
302 continue;
303 else
304 {
305 foreach (ThingError Error in NewErrors)
306 {
307 this.SensorData.Insert(i++, new ErrorModel(Error));
308 c++;
309 }
310
311 break;
312 }
313 }
314
315 if (i >= c)
316 {
317 foreach (ThingError Error in NewErrors)
318 this.SensorData.Add(new ErrorModel(Error));
319 }
320 });
321
322 return Task.CompletedTask;
323 }
324
325 private async Task SensorDataPersonalEventHandler(object? Sender, PersonalEventNotificationEventArgs e)
326 {
328 !string.IsNullOrEmpty(this.thing?.BareJid) &&
329 string.Equals(this.thing.BareJid, e.Publisher, StringComparison.OrdinalIgnoreCase) &&
330 string.IsNullOrEmpty(this.thing.SourceId) &&
331 string.IsNullOrEmpty(this.thing.NodeId) &&
332 string.IsNullOrEmpty(this.thing.Partition))
333 {
334 if (SensorData.Fields is not null)
335 await this.NewFieldsReported(SensorData.Fields);
336
337 if (SensorData.Errors is not null)
338 await this.NewErrorsReported(SensorData.Errors);
339 }
340 }
341
343 public override async Task OnDisposeAsync()
344 {
345 ServiceRef.XmppService.UnregisterPepHandler(typeof(SensorData), this.SensorDataPersonalEventHandler);
346
347 ServiceRef.XmppService.OnPresence -= this.Xmpp_OnPresence;
348 ServiceRef.TagProfile.Changed -= this.TagProfile_Changed;
349
350 if (this.request is not null &&
351 (this.request.State == SensorDataReadoutState.Receiving ||
352 this.request.State == SensorDataReadoutState.Accepted ||
353 this.request.State == SensorDataReadoutState.Requested ||
354 this.request.State == SensorDataReadoutState.Started))
355 {
356 await this.request.Cancel();
357 }
358
359 await base.OnDisposeAsync();
360 }
361
362 private Task Xmpp_OnPresence(object? Sender, PresenceEventArgs e)
363 {
364 this.CalcThingIsOnline();
365 return Task.CompletedTask;
366 }
367
368 private void CalcThingIsOnline()
369 {
370 if (this.thing is null)
371 this.IsThingOnline = false;
372 else
373 {
374 RosterItem? Item = ServiceRef.XmppService.GetRosterItem(this.thing.BareJid);
375 this.IsThingOnline = Item is not null && Item.HasLastPresence && Item.LastPresence.IsOnline;
376 }
377 }
378
379 private string? GetFullJid()
380 {
381 if (this.thing is null)
382 return null;
383 else
384 {
385 RosterItem? Item = ServiceRef.XmppService.GetRosterItem(this.thing.BareJid);
386
387 if (Item is null || !Item.HasLastPresence || !Item.LastPresence.IsOnline)
388 return null;
389 else
390 return Item.LastPresenceFullJid;
391 }
392 }
393
394 private void TagProfile_Changed(object? Sender, PropertyChangedEventArgs e)
395 {
396 MainThread.BeginInvokeOnMainThread(this.CalcThingIsOnline);
397 }
398
399 #region Properties
400
404 public ObservableCollection<object> SensorData { get; }
405
409 [ObservableProperty]
410 private bool isThingOnline;
411
415 [ObservableProperty]
416 private bool supportsSensorEvents;
417
421 [ObservableProperty]
422 private bool hasStatus;
423
427 [ObservableProperty]
428 private string? status;
429
431 protected override void OnPropertyChanged(PropertyChangedEventArgs e)
432 {
433 base.OnPropertyChanged(e);
434
435 switch (e.PropertyName)
436 {
437 case nameof(this.Status):
438 this.HasStatus = !string.IsNullOrEmpty(this.Status);
439 break;
440
441 case nameof(this.IsConnected):
442 this.CalcThingIsOnline();
443 break;
444 }
445 }
446
447 #endregion
448
452 [RelayCommand]
453 private static Task Click(object obj)
454 {
455 if (obj is HumanReadableTag Tag)
456 return ViewClaimThingViewModel.LabelClicked(Tag.Name, Tag.Value, Tag.LocalizedValue);
457 else if (obj is FieldModel Field)
459 else if (obj is ErrorModel Error)
460 return ViewClaimThingViewModel.LabelClicked(string.Empty, Error.ErrorMessage, Error.ErrorMessage);
461 else if (obj is string s)
462 return ViewClaimThingViewModel.LabelClicked(string.Empty, s, s);
463 else
464 return Task.CompletedTask;
465 }
466
467 }
468}
A strongly-typed resource class, for looking up localized strings, etc.
static string SensorDataHeaderMomentary
Looks up a localized string similar to Momentary.
static string SensorDataHeaderOther
Looks up a localized string similar to Other.
static string SensorDataDone
Looks up a localized string similar to Sensor Data readout has completed..
static string SensorDataHeaderPeak
Looks up a localized string similar to Peak.
static string SensorDataAccepted
Looks up a localized string similar to Request has been accepted..
static string SensorDataHeaderIdentity
Looks up a localized string similar to Identity.
static string Errors
Looks up a localized string similar to Errors.
static string SensorDataCancelled
Looks up a localized string similar to Sensor Data readout has been cancelled..
static string SensorDataStarted
Looks up a localized string similar to Sensor Data readout has started..
static string SensorDataHeaderComputed
Looks up a localized string similar to Computed.
static string SensorDataRequested
Looks up a localized string similar to Sensor Data has been requested..
static string SensorDataReceiving
Looks up a localized string similar to Receiving Sensor Data..
static string GeneralInformation
Looks up a localized string similar to General Information.
static string SensorDataFailure
Looks up a localized string similar to Sensor Data readout has failed..
static string SensorDataHeaderStatus
Looks up a localized string similar to Status.
static string SensorDataHeaderHistorical
Looks up a localized string similar to Historical.
Contains information about a contact.
Definition: ContactInfo.cs:22
Base class that references services in the app.
Definition: ServiceRef.cs:43
static IReportingStringLocalizer Localizer
Localization service
Definition: ServiceRef.cs:370
static IXmppService XmppService
The XMPP service for XMPP communication.
Definition: ServiceRef.cs:190
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 OnInitializeAsync()
Method called when view is initialized for the first time. Use this method to implement registration ...
ReadSensorViewModel(ViewThingNavigationArgs? Args)
Creates an instance of the ReadSensorViewModel class.
override void OnPropertyChanged(PropertyChangedEventArgs e)
override async Task OnDisposeAsync()
Method called when the view is disposed, and will not be used more. Use this method to unregister eve...
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.
Event arguments for presence events.
IPersonalEvent PersonalEvent
Parsed personal event, if appropriate type was found.
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
Definition: ImplTypes.g.cs:58
class HeaderModel(string Label)
Represents a header
Definition: HeaderModel.cs:7
class Header(ISimulationNode Parent, Model Model)
Represents an identity property.
Definition: Header.cs:18
SensorDataReadoutState
Sensor Data Readout States.
FieldType
Field Type flags
Definition: FieldType.cs:10