Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
ModbusUnitNode.cs
1using System;
2using System.Collections;
3using System.Collections.Generic;
4using System.Threading.Tasks;
14
15namespace Waher.Things.Modbus
16{
20 public enum UnitReadMode
21 {
25 ReadRegisters,
26
30 ReadChildren
31 }
32
37 {
42 : base()
43 {
44 this.MaxCoilNr = 65535;
45 this.MaxDiscreteInputNr = 65535;
46 this.MaxHoldingRegisterNr = 65535;
47 this.MaxInputRegisterNr = 65535;
48 this.ReadMode = UnitReadMode.ReadRegisters;
49 }
50
54 [Page(4, "Modbus", 100)]
55 [Header(5, "Unit Address:")]
56 [ToolTip(6, "Unit ID on the Modbus network.")]
57 [Range(0, 255)]
58 [Required]
59 public int UnitId { get; set; }
60
64 [Page(4, "Modbus", 100)]
65 [Header(30, "Minimum coil number:")]
66 [ToolTip(31, "Smallest coil number accepted by the device.")]
67 [Range(0, 65535)]
68 [DefaultValue(0)]
69 public int MinCoilNr { get; set; }
70
74 [Page(4, "Modbus", 100)]
75 [Header(32, "Maximum coil number:")]
76 [ToolTip(33, "Largest coil number accepted by the device.")]
77 [Range(0, 65535)]
78 [DefaultValue(65535)]
79 public int MaxCoilNr { get; set; }
80
84 [Page(4, "Modbus", 100)]
85 [Header(34, "Minimum discrete input number:")]
86 [ToolTip(35, "Smallest discrete input number accepted by the device.")]
87 [Range(0, 65535)]
88 [DefaultValue(0)]
89 public int MinDiscreteInputNr { get; set; }
90
94 [Page(4, "Modbus", 100)]
95 [Header(36, "Maximum discrete input number:")]
96 [ToolTip(37, "Largest discrete input number accepted by the device.")]
97 [Range(0, 65535)]
98 [DefaultValue(65535)]
99 public int MaxDiscreteInputNr { get; set; }
100
104 [Page(4, "Modbus", 100)]
105 [Header(38, "Minimum input register number:")]
106 [ToolTip(39, "Smallest input register number accepted by the device.")]
107 [Range(0, 65535)]
108 [DefaultValue(0)]
109 public int MinInputRegisterNr { get; set; }
110
114 [Page(4, "Modbus", 100)]
115 [Header(40, "Maximum input register number:")]
116 [ToolTip(41, "Largest input register number accepted by the device.")]
117 [Range(0, 65535)]
118 [DefaultValue(65535)]
119 public int MaxInputRegisterNr { get; set; }
120
124 [Page(4, "Modbus", 100)]
125 [Header(42, "Minimum holding register number:")]
126 [ToolTip(43, "Smallest holding register number accepted by the device.")]
127 [Range(0, 65535)]
128 [DefaultValue(0)]
129 public int MinHoldingRegisterNr { get; set; }
130
134 [Page(4, "Modbus", 100)]
135 [Header(44, "Maximum holding register number:")]
136 [ToolTip(45, "Largest holding register number accepted by the device.")]
137 [Range(0, 65535)]
138 [DefaultValue(65535)]
139 public int MaxHoldingRegisterNr { get; set; }
140
144 [Page(4, "Modbus", 100)]
145 [Header(46, "Switch byte order.")]
146 [ToolTip(47, "If checked, byte order in registers will be reversed.")]
147 [DefaultValue(false)]
148 public bool SwitchByteOrder { get; set; }
149
153 [Page(4, "Modbus", 100)]
154 [Header(60, "Sensor-Data Read Mode:")]
155 [ToolTip(61, "How the unit should handle sensor-data requests.")]
156 [DefaultValue(UnitReadMode.ReadRegisters)]
157 [Option(UnitReadMode.ReadRegisters, 62, "Read and report Modbus registers raw.")]
158 [Option(UnitReadMode.ReadChildren, 63, "Read child nodes.")]
159 public UnitReadMode ReadMode { get; set; }
160
166 public override Task<string> GetTypeNameAsync(Language Language)
167 {
168 return Language.GetStringAsync(typeof(ModbusGatewayNode), 2, "Modbus Unit");
169 }
170
177 public override async Task<IEnumerable<Parameter>> GetDisplayableParametersAsync(Language Language, RequestOrigin Caller)
178 {
179 LinkedList<Parameter> Result = await base.GetDisplayableParametersAsync(Language, Caller) as LinkedList<Parameter>;
180
181 Result.AddLast(new Int32Parameter("Address", await Language.GetStringAsync(typeof(ModbusGatewayNode), 7, "Address"), this.UnitId));
182
183 return Result;
184 }
185
191 public override Task<bool> AcceptsParentAsync(INode Parent)
192 {
193 return Task.FromResult(Parent is ModbusGatewayNode);
194 }
195
201 public override Task<bool> AcceptsChildAsync(INode Child)
202 {
203 return Task.FromResult(Child is ModbusUnitChildNode);
204 }
205
209 public async Task<ModbusGatewayNode> GetGateway()
210 {
211 if (await this.GetParent() is ModbusGatewayNode GatewayNode)
212 return GatewayNode;
213 else
214 throw new Exception("Modbus Gateway node not found.");
215 }
216
221 public Task StartReadout(ISensorReadout Request)
222 {
223 switch (this.ReadMode)
224 {
225 case UnitReadMode.ReadRegisters:
226 return this.StartReadoutOfRegisters(Request);
227
228 case UnitReadMode.ReadChildren:
229 return this.StartReadoutOfChildren(Request);
230
231 default:
232 Request.ReportErrors(true, new ThingError(this, "Unrecognized read mode."));
233 return Task.CompletedTask;
234 }
235 }
236
237 private async Task StartReadoutOfRegisters(ISensorReadout Request)
238 {
239 ModbusGatewayNode Gateway = await this.GetGateway();
240 ModbusTcpClient Client = await Gateway.GetTcpIpConnection();
241 await Client.Enter();
242 try
243 {
244 LinkedList<Field> Fields = new LinkedList<Field>();
245 DateTime TP = DateTime.UtcNow;
246 int Offset = this.MinInputRegisterNr;
247 int StepSize = 64;
248 int i;
249
250 while ((StepSize = Math.Min(StepSize, this.MaxInputRegisterNr - Offset + 1)) > 0 && Offset <= this.MaxInputRegisterNr)
251 {
252 try
253 {
254 ushort[] Values = await Client.ReadInputRegisters((byte)this.UnitId, (ushort)Offset, (ushort)StepSize);
255 StepSize = Math.Min(StepSize, Values.Length);
256
257 for (i = 0; i < StepSize; i++)
258 Fields.AddLast(new Int32Field(this, TP, "Input Register 3" + (Offset + i).ToString("D5"),
259 ModbusUnitHoldingRegisterNode.CheckOrder(this.SwitchByteOrder, Values[i]), FieldType.Momentary, FieldQoS.AutomaticReadout));
260
261 await Request.ReportFields(false, Fields);
262 Fields.Clear();
263 Offset += StepSize;
264 }
265 catch (ModbusException)
266 {
267 StepSize >>= 1;
268 }
269 }
270
271 Offset = this.MinHoldingRegisterNr;
272 StepSize = 64;
273
274 while ((StepSize = Math.Min(StepSize, this.MaxHoldingRegisterNr - Offset + 1)) > 0 && Offset <= this.MaxHoldingRegisterNr)
275 {
276 try
277 {
278 ushort[] Values = await Client.ReadMultipleRegisters((byte)this.UnitId, (ushort)Offset, (ushort)StepSize);
279 StepSize = Math.Min(StepSize, Values.Length);
280
281 for (i = 0; i < StepSize; i++)
282 {
283 Fields.AddLast(new Int32Field(this, TP, "Holding Register 4" + (Offset + i).ToString("D5"),
284 ModbusUnitHoldingRegisterNode.CheckOrder(this.SwitchByteOrder, Values[i]), FieldType.Momentary, FieldQoS.AutomaticReadout));
285 }
286
287 await Request.ReportFields(false, Fields);
288 Fields.Clear();
289 Offset += StepSize;
290 }
291 catch (ModbusException)
292 {
293 StepSize >>= 1;
294 }
295 }
296
297 Offset = this.MinCoilNr;
298 StepSize = 256;
299
300 while ((StepSize = Math.Min(StepSize, this.MaxCoilNr - Offset + 1)) > 0 && Offset <= this.MaxCoilNr)
301 {
302 try
303 {
304 BitArray Bits = await Client.ReadCoils((byte)this.UnitId, (ushort)Offset, (ushort)StepSize);
305 StepSize = Math.Min(StepSize, Bits.Length);
306
307 for (i = 0; i < StepSize; i++)
308 Fields.AddLast(new BooleanField(this, TP, "Coil 0" + (Offset + i).ToString("D5"), Bits[i], FieldType.Momentary, FieldQoS.AutomaticReadout));
309
310 await Request.ReportFields(false, Fields);
311 Fields.Clear();
312 Offset += StepSize;
313 }
314 catch (ModbusException)
315 {
316 StepSize >>= 1;
317 }
318 }
319
320 Offset = this.MinDiscreteInputNr;
321 StepSize = 256;
322
323 while ((StepSize = Math.Min(StepSize, this.MaxDiscreteInputNr - Offset + 1)) > 0 && Offset <= this.MaxDiscreteInputNr)
324 {
325 try
326 {
327 BitArray Bits = await Client.ReadInputDiscretes((byte)this.UnitId, (ushort)Offset, (ushort)StepSize);
328 StepSize = Math.Min(StepSize, Bits.Length);
329
330 for (i = 0; i < StepSize; i++)
331 Fields.AddLast(new BooleanField(this, TP, "Discrete Input 1" + (Offset + i).ToString("D5"), Bits[i], FieldType.Momentary, FieldQoS.AutomaticReadout));
332
333 await Request.ReportFields(false, Fields);
334 Fields.Clear();
335 Offset += StepSize;
336 }
337 catch (ModbusException)
338 {
339 StepSize >>= 1;
340 }
341 }
342
343 await Request.ReportFields(true);
344 }
345 catch (Exception ex)
346 {
347 await Request.ReportErrors(true, new ThingError(this, ex.Message));
348 }
349 finally
350 {
351 await Client.Leave();
352 }
353 }
354
355 private async Task StartReadoutOfChildren(ISensorReadout Request)
356 {
357 foreach (INode Child in await this.ChildNodes)
358 {
359 if (Child is ISensor Sensor)
360 {
361 TaskCompletionSource<bool> ReadoutCompleted = new TaskCompletionSource<bool>();
362
363 InternalReadoutRequest InternalReadout = new InternalReadoutRequest(this.LogId, null, Request.Types, Request.FieldNames,
364 Request.From, Request.To,
365 (Sender, e) =>
366 {
367 Request.ReportFields(false, e.Fields);
368
369 if (e.Done)
370 ReadoutCompleted.TrySetResult(true);
371
372 return Task.CompletedTask;
373 },
374 (Sender, e) =>
375 {
376 if (!(e.Errors is ThingError[] Errors))
377 {
378 List<ThingError> List = new List<ThingError>();
379
380 foreach (ThingError Error in e.Errors)
381 List.Add(Error);
382
383 Errors = List.ToArray();
384 }
385
386 Request.ReportErrors(false, Errors);
387
388 if (e.Done)
389 ReadoutCompleted.TrySetResult(true);
390
391 return Task.CompletedTask;
392 }, null);
393
394 await Sensor.StartReadout(InternalReadout);
395
396 Task Timeout = Task.Delay(60000);
397
398 Task T = await Task.WhenAny(ReadoutCompleted.Task, Timeout);
399
400 if (ReadoutCompleted.Task.IsCompleted)
401 await Request.ReportFields(true);
402 else
403 await Request.ReportErrors(true, new ThingError(this, "Timeout."));
404 }
405 }
406 }
407
408 }
409}
Task Leave()
Leaves unique access to the TCP client. Must be called exactly one for each call to Enter.
async Task< ushort[]> ReadMultipleRegisters(byte UnitAddress, ushort ReferenceNumber, ushort NrWords)
Reads multiple registers from a Modbus unit.
async Task< BitArray > ReadCoils(byte UnitAddress, ushort ReferenceNumber, ushort NrBits)
Reads coils from a Modbus unit.
async Task< ushort[]> ReadInputRegisters(byte UnitAddress, ushort ReferenceNumber, ushort NrWords)
Reads input registers from a Modbus unit.
Task Enter()
Enters unique access to the TCP client. Must be followed by exactly one Leave call.
async Task< BitArray > ReadInputDiscretes(byte UnitAddress, ushort ReferenceNumber, ushort NrBits)
Reads input discretes from a Modbus unit.
Manages a chat sensor data readout request.
Contains information about a language.
Definition: Language.cs:17
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 ...
Definition: Language.cs:209
async Task< INode > GetParent()
Gets the parent of the node.
Base class for all provisioned metering nodes.
Node representing a TCP/IP connection to a Modbus Gateway
async Task< ModbusTcpClient > GetTcpIpConnection()
Gets the TCP/IP connection associated with this gateway.
Abstract base class for child nodes to Mobus Unit nodes.
Represents a holding register on a Modbus unit node.
Represents a Unit Device on a Modbus network.
ModbusUnitNode()
Represents a Unit Device on a Modbus network.
int MaxDiscreteInputNr
Maximum discrete input number
override Task< string > GetTypeNameAsync(Language Language)
Gets the type name of the node.
int MinDiscreteInputNr
Minimum discrete input number
async Task< ModbusGatewayNode > GetGateway()
Modbus Gateway node.
int MaxCoilNr
Maximum coil number
bool SwitchByteOrder
If the byte order in words should be switched.
override Task< bool > AcceptsParentAsync(INode Parent)
If the node accepts a presumptive parent, i.e. can be added to that parent (if that parent accepts th...
int MinInputRegisterNr
Minimum input register number
Task StartReadout(ISensorReadout Request)
Starts the readout of the sensor.
int UnitId
If the node is provisioned is not. Property is editable.
int MinHoldingRegisterNr
Minimum holding register number
int MaxHoldingRegisterNr
Maximum holding register number
UnitReadMode ReadMode
How the unit should handle sensor-data requests.
override async Task< IEnumerable< Parameter > > GetDisplayableParametersAsync(Language Language, RequestOrigin Caller)
Gets displayable parameters.
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 MinCoilNr
Minimum coil number
int MaxInputRegisterNr
Maximum input register number
Tokens available in request.
Definition: RequestOrigin.cs:9
Represents a boolean value that can be either true or false.
Definition: BooleanField.cs:11
Represents a 32-bit integer value.
Definition: Int32Field.cs:10
Contains information about an error on a thing
Definition: ThingError.cs:10
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
INode Parent
Parent Node, or null if a root node.
Definition: INode.cs:116
string LogId
If provided, an ID for the node, as it would appear or be used in system logs. Can be null,...
Definition: INode.cs:62
Interface for sensor nodes.
Definition: ISensor.cs:9
Interface for classes managing sensor data readouts.
string[] FieldNames
Names of fields to read.
Task ReportErrors(bool Done, params ThingError[] Errors)
Report error states to the client.
FieldType Types
Field Types to read.
DateTime To
To what time readout is to be made. Use DateTime.MaxValue to specify no upper limit.
Task ReportFields(bool Done, params Field[] Fields)
Report read fields to the client.
DateTime From
From what time readout is to be made. Use DateTime.MinValue to specify no lower limit.
UnitReadMode
How the unit should handle sensor-data requests
FieldQoS
Field Quality of Service flags
Definition: FieldQoS.cs:10
FieldType
Field Type flags
Definition: FieldType.cs:10