3using System.ComponentModel;
6using System.Threading.Tasks;
30 private const string TotalNodeCount =
" TotalNodeCount ";
31 private const string NodeCount =
" NodeCount ";
43 [Header(2,
"Parallel readouts:", 10)]
45 [ToolTip(3,
"Maximum number of parallel readouts.")]
54 [Header(68,
"Timeout (s):", 20)]
56 [ToolTip(69,
"If a sensor does not respond within this time, the readout will be cancelled.")]
65 [Header(4,
"Momentary values.", 30)]
67 [ToolTip(5,
"Check, if momentary values should be read.")]
74 [Header(6,
"Identity values.", 40)]
76 [ToolTip(7,
"Check, if identity values should be read.")]
83 [Header(8,
"Status values.", 50)]
85 [ToolTip(9,
"Check, if status values should be read.")]
87 public bool Status {
get;
set; } =
false;
92 [Header(10,
"Computed values.", 60)]
94 [ToolTip(11,
"Check, if computed values should be read.")]
101 [Header(12,
"Peak values.", 70)]
103 [ToolTip(13,
"Check, if peak values should be read.")]
104 [DefaultValue(
false)]
105 public bool Peak {
get;
set; } =
false;
110 [Header(14,
"Historical values.", 80)]
112 [ToolTip(15,
"Check, if historical values should be read.")]
113 [DefaultValue(
false)]
150 [Header(16,
"Field names to read:", 90)]
152 [ToolTip(17,
"Leave blank to read all fields.")]
153 [ContentType(
"text/plain")]
159 [Header(18,
"From:", 100)]
161 [ToolTip(19,
"Read historical data from this point in time.")]
167 [Header(20,
"To:", 110)]
169 [ToolTip(21,
"Read historical data to this point in time.")]
189 return Task.FromResult(
204 await this.ReportStart(
Status);
207 await
Status.Query.BeginSection(await this.GetString(
Status, 57,
"Overview"));
209 ISensor[] Sensors = await this.FindNodes<ISensor>();
210 if (Sensors is
null || Sensors.Length == 0)
212 await this.ReportMessage(
Status, 33,
"No readable sensors found to read.");
213 await this.ReportDone(
Status);
219 foreach (
ISensor Sensor
in Sensors)
222 ReadableSensors.Add(Sensor);
225 Sensors = ReadableSensors.ToArray();
226 if (Sensors.Length == 0)
228 await this.ReportMessage(
Status, 33,
"No readable sensors found to read.");
229 await this.ReportDone(
Status);
233 Status.Variables[TotalNodeCount] = Sensors.Length;
234 Status.Variables[NodeCount] = 0.0;
236 if (Sensors.Length == 1)
237 await this.ReportMessage(
Status, 52,
"**1** readable sensor found to read.");
239 await this.ReportMessage(
Status, 34,
"**%0%** readable sensors found to read.", Sensors.Length);
243 if (SensorDataProcessors.Length == 1)
244 await this.ReportMessage(
Status, 53,
"**1** sensor data processor will be used.");
246 await this.ReportMessage(
Status, 35,
"**%0%** sensor data processors will be used.", SensorDataProcessors.Length);
250 if (ErrorProcessors.Length == 1)
251 await this.ReportMessage(
Status, 70,
"**1** thing error processor will be used.");
253 await this.ReportMessage(
Status, 71,
"**%0%** thing error processors will be used.", SensorDataProcessors.Length);
257 if (Outputs.Length == 1)
258 await this.ReportMessage(
Status, 54,
"Sensor Data will be output to **1** output.");
260 await this.ReportMessage(
Status, 36,
"Sensor Data will be output to **%0%** outputs.", Outputs.Length);
264 await
Status.Query.EndSection();
265 await
Status.Query.BeginSection(await this.GetString(
Status, 58,
"Readout"));
266 await this.ReportStatus(
Status, 37,
"Starting readout.");
271 await
Status.Query.NewTable(
"Summary",
273 new Column(
"NodeId", await this.GetString(
Status, 39,
"Node ID"),
275 new Column(
"NrFields", await this.GetString(
Status, 40,
"#Fields"),
277 new Column(
"NrErrors", await this.GetString(
Status, 41,
"#Errors"),
283 foreach (
ISensor Sensor
in Sensors)
284 Processor.Queue(
new ReadoutWorkItem(Sensor,
this,
Status,
285 SensorDataProcessors, ErrorProcessors, Outputs));
287 await Processor.WaitUntilIdle();
290 await
Status.Query.TableDone(
"Summary");
294 await
Status.Query.EndSection();
295 await this.ReportStatus(
Status, 55,
"Readout completed.");
302 await
Status.Job.LogErrorAsync(
"ReadoutErrors", JobErrors.Count.ToString() +
" errors reported during readout.");
305 await
Status.Job.RemoveErrorAsync(
"ReadoutErrors");
310 await
Status.Query.LogMessage(ex);
312 await
Status.Job.LogErrorAsync(
"ReadoutErrors", ex.Message);
316 await this.ReportDone(
Status);
324 await
Status.Query.Start();
326 await this.ReportStatus(
Status, 32,
"Starting sensor data readout job task.");
333 await
Status.Query.Done();
339 await
Status.Query.SetTitle(Title);
345 return this.ReportMessage(
Status, StringId, Message, (
string[])
null);
349 string Message, params
object[] Parameters)
351 return this.ReportMessage(
Status, StringId, Message,
ToString(Parameters));
355 string Message, params
string[] Parameters)
360 await this.GetString(
Status, StringId, Message, Parameters)));
367 return this.ReportStatus(
Status, StringId, Message, (
string[])
null);
371 string Message, params
object[] Parameters)
373 return this.ReportStatus(
Status, StringId, Message,
ToString(Parameters));
377 string Message, params
string[] Parameters)
380 await
Status.Query.SetStatus(await this.GetString(
Status, StringId, Message, Parameters));
385 return this.GetString(
Status, StringId, Message, (
string[])
null);
388 private static string[]
ToString(
object[] Parameters)
390 if (Parameters is
null)
393 int i, c = Parameters.Length;
394 string[] Result =
new string[c];
396 for (i = 0; i < c; i++)
397 Result[i] = Parameters[i]?.
ToString();
403 params
string[] Parameters)
407 if (!(Parameters is
null))
409 int i, c = Parameters.Length;
411 for (i = 0; i < c; i++)
412 Message = Message.Replace(
"%" + i.ToString() +
"%", Parameters[i]);
425 if ((SensorDataProcessors?.Length ?? 0) > 0)
432 if ((
Fields?.Length ?? 0) == 0)
437 await Processor.
LogErrorAsync(
"ProcessingError", ex.Message);
442 if ((Outputs?.Length ?? 0) > 0 && (
Fields?.Length ?? 0) > 0)
458 if ((Errors?.Length ?? 0) > 0)
460 if ((ErrorProcessors?.Length ?? 0) > 0)
467 if ((Errors?.Length ?? 0) == 0)
472 await Processor.
LogErrorAsync(
"ProcessingError", ex.Message);
477 if ((Errors?.Length ?? 0) > 0)
482 JobErrors.AddRange(Errors);
489 switch (
Status.ReportDetail)
499 if ((
Fields?.Length ?? 0) > 0)
501 string TableId =
"Fields: " + Sensor.
NodeId;
502 await
Status.Query.NewTable(TableId,
503 await this.GetString(
Status, 50,
"Reported Sensor Data"),
504 new Column(
"Timestamp", await this.GetString(
Status, 44,
"Timestamp"),
506 new Column(
"FieldName", await this.GetString(
Status, 45,
"Field Name"),
508 new Column(
"FieldType", await this.GetString(
Status, 46,
"Field Type"),
510 new Column(
"Value", await this.GetString(
Status, 47,
"Value"),
512 new Column(
"QoS", await this.GetString(
Status, 48,
"QoS"),
534 await
Status.Query.TableDone(TableId);
537 await this.ReportMessage(
Status, 42,
"No sensor data reported.");
539 if ((Errors?.Length ?? 0) > 0)
541 string TableId =
"Errors: " + Sensor.
NodeId;
542 await
Status.Query.NewTable(TableId,
543 await this.GetString(
Status, 51,
"Reported Errors"),
544 new Column(
"Timestamp", await this.GetString(
Status, 44,
"Timestamp"),
546 new Column(
"Error", await this.GetString(
Status, 49,
"Error Message"),
567 await
Status.Query.TableDone(TableId);
570 await this.ReportMessage(
Status, 43,
"No errors reported.");
572 await
Status.Query.EndSection();
576 if (
Status.Variables.TryGetVariable(TotalNodeCount, out
Variable v) &&
577 v.ValueObject is
double TotalNrNodes &&
578 Status.Variables.TryGetVariable(NodeCount, out v) &&
579 v.ValueObject is
double NrNodes)
582 Status.Variables[NodeCount] = NrNodes;
584 await this.ReportStatus(
Status, 56,
"%0% of %1% nodes processed.",
585 (
int)NrNodes, (
int)TotalNrNodes);
594 private class ReadoutWorkItem :
WorkItem
596 private readonly
ISensor sensor;
609 this.sensor = Sensor;
610 this.task = TaskNode;
612 this.sensorDataProcessors = SensorDataProcessors;
613 this.errorProcessors = ErrorProcessors;
614 this.outputs = Outputs;
617 public override async Task
Execute(CancellationToken Cancel)
619 TaskCompletionSource<bool> Completed =
new TaskCompletionSource<bool>();
620 JobReadout Readout =
new JobReadout(
this, Completed);
623 await this.sensor.StartReadout(Readout);
625 _ = Task.Delay(this.task.SensorTimeoutSeconds * 1000).ContinueWith(
626 (
_) => Completed.TrySetResult(
false));
628 if (!await Completed.Task)
629 await Readout.ReportErrors(
true,
new ThingError(this.sensor,
"Sensor did not respond."));
633 await Readout.ReportErrors(
true,
new ThingError(this.sensor, ex.Message));
638 if (this.errors is
null)
642 StringBuilder sb =
new StringBuilder();
652 sb.Append(
Error.ErrorMessage);
659 await this.task.SensorReadoutCompleted(this.sensor, this.fields?.ToArray(),
660 this.errors?.ToArray(), this.status, this.sensorDataProcessors,
661 this.errorProcessors, this.outputs);
666 private readonly TaskCompletionSource<bool> completed;
667 private readonly ReadoutWorkItem item;
668 private readonly DateTime from;
669 private readonly DateTime to;
671 public JobReadout(ReadoutWorkItem Item, TaskCompletionSource<bool> Completed)
674 this.from = Item.status.StartTime - this.item.task.From;
675 this.to = Item.status.StartTime - this.item.task.To;
676 this.completed = Completed;
680 public FieldType Types => this.item.task.FieldTypes;
681 public string[] FieldNames => this.item.task.FieldNames;
682 public DateTime From => this.from;
683 public DateTime To => this.to;
684 public DateTime When => this.item.status.StartTime;
685 public string Actor => this.item.task.
NodeId;
686 public string ServiceToken =>
string.Empty;
687 public string DeviceToken =>
string.Empty;
688 public string UserToken =>
string.Empty;
690 public bool IsIncluded(
string FieldName)
692 if ((this.item.task.FieldNames?.Length ?? 0) == 0)
695 return Array.IndexOf(this.item.task.FieldNames, FieldName) >= 0;
698 public bool IsIncluded(DateTime Timestamp)
700 return Timestamp.ToUniversalTime() >= this.from && Timestamp <= this.to;
705 return (this.item.task.FieldTypes & Type) != 0;
708 public bool IsIncluded(
string FieldName,
FieldType Type)
710 return this.IsIncluded(FieldName) && this.IsIncluded(Type);
713 public bool IsIncluded(
string FieldName, DateTime Timestamp,
FieldType Type)
715 return this.IsIncluded(FieldName) && this.IsIncluded(Type) && this.IsIncluded(Timestamp);
718 public Task ReportErrors(
bool Done, params
ThingError[] Errors)
721 this.item.errors.AddRange(Errors);
724 this.completed.TrySetResult(
true);
726 return Task.CompletedTask;
729 public Task ReportErrors(
bool Done, IEnumerable<ThingError> Errors)
732 this.item.errors.AddRange(Errors);
735 this.completed.TrySetResult(
true);
737 return Task.CompletedTask;
740 public Task ReportFields(
bool Done, params
Field[]
Fields)
743 this.item.fields.AddRange(
Fields);
746 this.completed.TrySetResult(
true);
748 return Task.CompletedTask;
751 public Task ReportFields(
bool Done, IEnumerable<Field>
Fields)
754 this.item.fields.AddRange(
Fields);
757 this.completed.TrySetResult(
true);
759 return Task.CompletedTask;
764 return Task.CompletedTask;
Class that can be used to encapsulate Markdown to be returned from a Web Service, bypassing any encod...
Contains information about the execution of a job.
override string ToString()
A reference to a metering group.
A reference to a metering node.
A reference to an output.
A reference to a processor.
Sensor data readout task node.
override Task< string > GetTypeNameAsync(Language Language)
Gets the type name of the node.
bool Identity
If identity values should be read.
Duration From
From when data should be read.
override Task< bool > AcceptsChildAsync(INode Child)
If the node accepts a presumptive child, i.e. can receive as a child (if that child accepts the node ...
int SensorTimeoutSeconds
Maximum number of parallel readouts.
bool Momentary
If momentary values should be read.
int ParallelReadouts
Maximum number of parallel readouts.
bool Historical
If historical values should be read.
bool Status
If status values should be read.
bool Computed
If computed values should be read.
Duration To
To when data should be read.
override async Task ExecuteTask(JobExecutionStatus Status)
Executes the task.
FieldType FieldTypes
Types of fields to read.
bool Peak
If peak values should be read.
string[] FieldNames
Field names to read.
SensorDataReadoutTaskNode()
Sensor data readout task node.
Abstract bast class for job tasks.
A chunked list is a linked list of chunks of objects of type T .
void Clear()
Clears the collection.
int Count
Number of elements in collection.
void Add(T Item)
Adds an item to the collection.
T[] ToArray()
Returns an array containing all elements of the collection.
Contains information about a language.
Task< string > GetStringAsync(Type Type, int Id, string Default)
Gets the string value of a string ID. If no such string exists, a string is created with the default ...
Processes work tasks, in an asynchronous manner.
Represents an asynchronous operation to be performed.
Task Execute()
Executes the operation.
Extract the fields of a type or an object.
Makes sure an expression is defined. Otherwise, an exception is thrown.
int Length
Length of expression covered by node.
Contains information about a variable.
Base class for all metering nodes.
virtual Task LogErrorAsync(string Body)
Logs an error message on the node.
virtual Task< bool > RemoveErrorAsync()
Removes error messages with an empty event ID from the node.
Defines the Metering Topology data source. This data source contains a tree structure of persistent r...
const string SourceID
Source ID for the metering topology data source.
Defines a column in a table.
Defines a record in a table.
Base class for all sensor data fields.
FieldQoS QoS
Field Quality of Service flags.
FieldType Type
Field Type flags.
abstract object ObjectValue
Field value, boxed as an object reference.
string Name
Unlocalized field name.
DateTime Timestamp
Timestamp of field value.
Contains information about an error on a thing
Task LogErrorAsync(string Body)
Logs an error message on the node.
Base Interface for all sensor-data output nodes.
Task OutputFields(ISensor Sensor, Field[] Fields)
Outputs a collection of sensor data fields.
Task LogErrorAsync(string Body)
Logs an error message on the node.
Base Interface for all sensor-data processor nodes.
Task< Field[]> ProcessFields(ISensor Sensor, Field[] Fields)
Process a collection of sensor data fields.
Base Interface for all thing error processor nodes.
Task< ThingError[]> ProcessErrors(INode Device, ThingError[] Errors)
Process a collection of thing errors.
Interface for nodes that are published through the concentrator interface.
bool IsReadable
If the node can be read.
Interface for sensor nodes.
Interface for classes managing sensor data readouts.
Interface for thing references.
JobReportDetail
How much detail to include in job reports.
ColumnAlignment
Column alignment.
FieldType
Field Type flags
Represents a duration value, as defined by the xsd:duration data type: http://www....
static readonly Duration Zero
Zero value