Projekt

Obecné

Profil

Stáhnout (15.7 KB) Statistiky
| Větev: | Tag: | Revize:
1 a9ef8967 Oto Šťáva
using System;
2
using System.Net.Sockets;
3
using System.Threading;
4
using System.IO;
5
using System.Linq;
6
using System.Text;
7 49e751db Oto Šťáva
using UnityEngine;
8 a9ef8967 Oto Šťáva
9
namespace DeltaRobotVr
10
{
11 04839796 Oto Šťáva
    
12
    /// <summary>
13
    /// Singleton class responsible for communication with the Deltarobot server.
14
    /// </summary>
15 a9ef8967 Oto Šťáva
    public sealed class Client
16
    {
17
        // singleton instance
18
        public static readonly Client Instance = new Client();
19
20
        // default server
21 c6ecc85a Jakub Hejman
        private const string DefaultHost = "127.0.0.1";
22 a9ef8967 Oto Šťáva
        private const int DefaultPort = 4242;
23
24
        // protocol constants
25
        private const UInt16 ProtocolMagicId = 0x3001;
26
        private const UInt16 ProtocolVersionId = 0x2002;
27
        private const UInt16 PingId = 0x3004;
28 6d9543aa Jakub Hejman
        private const UInt16 PongId = 0x3005;
29 a9ef8967 Oto Šťáva
        private const UInt16 EotId = 0xF006;
30
        private const UInt16 CurrentActuatorPositionId = 0x4003;
31 306eb3ca kovi
        private const UInt16 CurrentDirectionVectorId = 0x4007;
32
        private const UInt16 DesiredDirectionVectorId = 0x4008;
33
        private const UInt16 CurveId = 0xF009;
34 a9ef8967 Oto Šťáva
35
        private const UInt16 Size8 = 0x0000;
36
        private const UInt16 Size16 = 0x1000;
37
        private const UInt16 Size32 = 0x2000;
38
        private const UInt16 Size64 = 0x3000;
39
        private const UInt16 Size128 = 0x4000;
40
        private const UInt16 SizeVariable = 0xF000;
41
        
42
        private static readonly byte[] ProtocolMagicValue = Encoding.ASCII.GetBytes("DeltaRVr");
43
        private const UInt32 ProtocolVersionValue = 1;
44
45 306eb3ca kovi
        private const UInt32 PointLength = 12;
46
47 49e751db Oto Šťáva
        private const int ReconnectPollMillis = 200;
48
        private const int ReconnectTimeMillis = 3000;
49 a5c3168d Oto Šťáva
        private const int ConnectTimeoutMillis = 3000;
50 a9ef8967 Oto Šťáva
        
51
        private Thread _thread;
52
        
53
        private readonly object _isRunningLock = new object();
54
        private bool _isRunning;
55
56
        private readonly object _actuatorPositionLock = new object();
57
        private Single3 _actuatorPosition;
58
59 306eb3ca kovi
        private readonly object _currentDirectionVectorLock = new object();
60
        private Single3 _currentDirectionVector;
61
62
        private readonly object _desiredDirectionVectorLock = new object();
63
        private Single3 _desiredDirectionVector;
64
65
        private readonly object _curveLock = new object();
66
        private Single3[] _curve;
67
        private long _curveCounter = 0;
68
69 a9ef8967 Oto Šťáva
        private volatile bool _isConnected;
70 04839796 Oto Šťáva
        private volatile string _eotReason = string.Empty;
71 a9ef8967 Oto Šťáva
72 49e751db Oto Šťáva
        private readonly object _reconnectTimeLock = new object();
73
        private DateTime _reconnectTime = DateTime.Now;
74
75 a9ef8967 Oto Šťáva
        static Client()
76
        {
77
        }
78
79
        private Client()
80
        {
81
        }
82
83 04839796 Oto Šťáva
        /// <summary>
84
        /// Starts the client thread and connects to the server.
85
        /// </summary>
86 a9ef8967 Oto Šťáva
        public void Start()
87
        {
88
            IsRunning = true;
89 f4e5d893 Oto Šťáva
            _thread = new Thread(ThreadProcedure);
90 a9ef8967 Oto Šťáva
            _thread.Start();
91
        }
92
93 04839796 Oto Šťáva
        /// <summary>
94
        /// Gracefully stops the client thread. Blocks until the thread actually stops.
95
        /// </summary>
96 a9ef8967 Oto Šťáva
        public void Stop()
97
        {
98
            IsRunning = false;
99 f4e5d893 Oto Šťáva
            if (_thread != null)
100
            {
101
                _thread.Join();
102
                _thread = null;
103
            }
104 a9ef8967 Oto Šťáva
        }
105
106 04839796 Oto Šťáva
        /// <summary>
107
        /// Contains a reason received in an end-of-transmission message.
108
        /// </summary>
109
        public string EotReason
110 a9ef8967 Oto Šťáva
        {
111 04839796 Oto Šťáva
            get => _eotReason;
112
            private set => _eotReason = value;
113 a9ef8967 Oto Šťáva
        }
114
115 04839796 Oto Šťáva
        /// <summary>
116
        /// Whether the client is currently maintaining an active connection to the server.
117
        /// </summary>
118 a9ef8967 Oto Šťáva
        public bool IsConnected
119
        {
120
            get => _isConnected;
121
            private set => _isConnected = value;
122
        }
123
124 04839796 Oto Šťáva
        /// <summary>
125
        /// Current position of the actuator (in server's world space).
126
        /// </summary>
127 a9ef8967 Oto Šťáva
        public Single3 ActuatorPosition
128
        {
129
            get
130
            {
131
                lock (_actuatorPositionLock)
132
                {
133
                    return _actuatorPosition;
134
                }
135
            }
136
            private set
137
            {
138
                lock (_actuatorPositionLock)
139
                {
140
                    _actuatorPosition = value;
141
                }
142
            } 
143
        }
144
145 04839796 Oto Šťáva
        /// <summary>
146
        /// The actual direction the actuator is currently moving in.
147
        /// </summary>
148
        public Single3 ActualDirectionVector
149 306eb3ca kovi
        {
150
            get
151
            {
152
                lock (_currentDirectionVectorLock)
153
                {
154
                    return _currentDirectionVector;
155
                }
156
            }
157
            private set
158
            {
159
                lock (_currentDirectionVectorLock)
160
                {
161
                    _currentDirectionVector = value;
162
                }
163
            }
164
        }
165
166 04839796 Oto Šťáva
        /// <summary>
167
        /// The target direction the actuator should be moving in so that it draws the curve.
168
        /// </summary>
169
        public Single3 TargetDirectionVector
170 306eb3ca kovi
        {
171
            get
172
            {
173
                lock (_desiredDirectionVectorLock)
174
                {
175
                    return _desiredDirectionVector;
176
                }
177
            }
178
            private set
179
            {
180
                lock (_desiredDirectionVectorLock)
181
                {
182
                    _desiredDirectionVector = value;
183
                }
184
            }
185
        }
186
187 04839796 Oto Šťáva
        /// <summary>
188
        /// The current exercise curve.
189
        /// </summary>
190 306eb3ca kovi
        public Single3[] Curve
191
        {
192
            get
193
            {
194
                lock (_curveLock)
195
                {
196
                    return _curve;
197
                }
198
            }
199
            private set
200
            {
201
                lock (_curveLock)
202
                {
203
                    _curve = value;
204
                }
205
            }
206
        }
207
208 04839796 Oto Šťáva
        /// <summary>
209
        /// A counter used to determine whether the curve has changed. The curve remains the same while the counter is
210
        /// the same.
211
        /// </summary>
212 306eb3ca kovi
        public long CurveCounter
213
        {
214
            get
215
            {
216
                lock (_curveLock)
217
                {
218
                    return _curveCounter;
219
                }
220
            }
221
            private set
222
            {
223
                lock (_curveLock)
224
                {
225
                    _curveCounter = value;
226
                }
227
            }
228
        }
229
230 04839796 Oto Šťáva
        /// <summary>
231
        /// Whether the client thread should keep on reading data from the server.
232
        /// </summary>
233 a9ef8967 Oto Šťáva
        public bool IsRunning
234
        {
235
            get {
236
                lock (_isRunningLock)
237
                {
238
                    return _isRunning;
239
                }
240
            }
241
            private set
242
            {
243
                lock (_isRunningLock)
244
                {
245
                    _isRunning = value;
246
                }
247
            }
248
        }
249
250 04839796 Oto Šťáva
        /// <summary>
251
        /// The time at which the client will attempt to reconnect to the server again.
252
        /// </summary>
253 49e751db Oto Šťáva
        public DateTime ReconnectTime
254
        {
255
            get
256
            {
257
                lock (_reconnectTimeLock)
258
                {
259
                    return _reconnectTime;
260
                }
261
            }
262
            private set
263
            {
264
                lock (_reconnectTimeLock)
265
                {
266
                    _reconnectTime = value;
267
                }
268
            }
269
        }
270
271 04839796 Oto Šťáva
        /// <summary>
272
        /// The client thread function.
273
        /// </summary>
274 a9ef8967 Oto Šťáva
        private void ThreadProcedure()
275
        {
276 49e751db Oto Šťáva
            while (IsRunning)
277 a9ef8967 Oto Šťáva
            {
278
                try
279
                {
280 c6ecc85a Jakub Hejman
                    var host = ConfigurationProvider.Instance.Host;
281
                    var port = ConfigurationProvider.Instance.Port;
282
283
                    if (port < 0 || host.Length == 0)
284
                    {
285
                        // Obviously invalid values
286
                        host = DefaultHost;
287
                        port = DefaultPort;
288
                    }
289
290 a5c3168d Oto Šťáva
                    using var client = new TcpClient();
291
                    var asyncConnect = client.BeginConnect(host, port, null, null);
292
293
                    var connected = asyncConnect.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(ConnectTimeoutMillis));
294
                    if (!connected)
295
                    {
296
                        Debug.LogWarning("Connection not established - timed out");
297
                        continue;
298
                    }
299
                    
300 49e751db Oto Šťáva
                    using var stream = client.GetStream();
301
                    BinaryReader reader = new BinaryReader(stream);
302
                    BinaryWriter writer = new BinaryWriter(stream);
303
                    IsConnected = true;
304
305
                    SendProtocolPreamble(writer);
306
307
                    // main message reception loop
308 a9ef8967 Oto Šťáva
                    while (IsRunning && IsConnected)
309
                    {
310 49e751db Oto Šťáva
                        ReadMessages(reader, writer);
311 a9ef8967 Oto Šťáva
                    }
312 285c6fe5 Oto Šťáva
313
                    if (client.Connected)
314
                    {
315
                        SendEot(writer, "Client stopped by user");
316
                    }
317 a9ef8967 Oto Šťáva
                }
318 49e751db Oto Šťáva
                catch (Exception e)
319 a9ef8967 Oto Šťáva
                {
320 49e751db Oto Šťáva
                    if (e is SocketException || e is IOException)
321
                    {
322
                        Debug.LogWarning($"Connection error:\n{e}");
323
                    }
324
                    else
325
                    {
326
                        Debug.LogError($"Exception in communication thread:\n{e}");
327
                    }
328 285c6fe5 Oto Šťáva
                }
329
                finally
330
                {
331
                    IsConnected = false;
332 49e751db Oto Šťáva
                }
333
                
334
                // wait before reconnection - short polls to prevent blocking if application is closed
335
                ReconnectTime = DateTime.Now.AddMilliseconds(ReconnectTimeMillis);
336
                while (IsRunning && DateTime.Now < ReconnectTime)
337
                {
338
                    Thread.Sleep(ReconnectPollMillis);
339 a9ef8967 Oto Šťáva
                }
340
            }
341 49e751db Oto Šťáva
        }
342
343
344
        private void ReadMessages(BinaryReader reader, BinaryWriter writer)
345
        {
346
            var messageIdentifier = reader.ReadUInt16();
347
            switch (messageIdentifier)
348 a9ef8967 Oto Šťáva
            {
349 49e751db Oto Šťáva
                case ProtocolMagicId:
350
                    ProcessProtocolMagic(reader);
351
                    break;
352
                case ProtocolVersionId:
353
                    ProcessProtocolVersion(reader);
354
                    break;
355
                case PingId:
356
                    ProcessPing(reader, writer);
357
                    break;
358
                case EotId:
359
                    ProcessEot(reader);
360
                    break;
361
                case CurrentActuatorPositionId:
362 04839796 Oto Šťáva
                    ProcessActuatorPosition(reader);
363 49e751db Oto Šťáva
                    break;
364
                case CurrentDirectionVectorId:
365 04839796 Oto Šťáva
                    ProcessActualDirectionVector(reader);
366 49e751db Oto Šťáva
                    break;
367
                case DesiredDirectionVectorId:
368 04839796 Oto Šťáva
                    ProcessTargetDirectionVector(reader);
369 49e751db Oto Šťáva
                    break;
370
                case CurveId:
371
                    ProcessCurve(reader);
372
                    break;
373
                default: // unknown message
374
                    var sizeIdentifier = (UInt16) (messageIdentifier & 0xF000);
375
                    switch (sizeIdentifier)
376
                    {
377
                        case Size8:
378
                            Skip8(reader);
379
                            break;
380
                        case Size16:
381
                            Skip16(reader);
382
                            break;
383
                        case Size32:
384
                            Skip32(reader);
385
                            break;
386
                        case Size64:
387
                            Skip64(reader);
388
                            break;
389
                        case Size128:
390
                            Skip128(reader);
391
                            break;
392
                        case SizeVariable:
393
                            SkipVariableLength(reader);
394
                            break;
395
                    }
396
397
                    break;
398 a9ef8967 Oto Šťáva
            }
399
        }
400
401 49e751db Oto Šťáva
        private void SendProtocolPreamble(BinaryWriter writer)
402
        {
403
            writer.Write(ProtocolMagicId);
404
            writer.Write(ProtocolMagicValue);
405
            writer.Write(ProtocolVersionId);
406
            writer.Write(ProtocolVersionValue);
407
        }
408 a9ef8967 Oto Šťáva
409
        private void Skip8(BinaryReader reader)
410
        {
411
            reader.ReadByte();
412
        }
413
414
        private void Skip16(BinaryReader reader)
415
        {
416
            reader.ReadUInt16();
417
        }
418
419
        private void Skip32(BinaryReader reader)
420
        {
421
            reader.ReadUInt32();
422
        }
423
424
        private void Skip64(BinaryReader reader)
425
        {
426
            reader.ReadUInt64();
427
        }
428
429
        private void Skip128(BinaryReader reader)
430
        {
431
            reader.ReadUInt64();
432
            reader.ReadUInt64();
433
        }
434
435
        private void SkipVariableLength(BinaryReader reader)
436
        {
437
            UInt32 numberOfBytes = reader.ReadUInt32();
438
            reader.ReadBytes((int) numberOfBytes);
439
        }
440
441 04839796 Oto Šťáva
        private void ProcessActuatorPosition(BinaryReader reader)
442 a9ef8967 Oto Šťáva
        {
443
            var x = reader.ReadSingle();
444
            var y = reader.ReadSingle();
445
            var z = reader.ReadSingle();
446
            reader.ReadSingle(); // skip unused
447
448
            ActuatorPosition = new Single3(x, y, z);
449
        }
450
451 04839796 Oto Šťáva
        private void ProcessActualDirectionVector(BinaryReader reader)
452 306eb3ca kovi
        {
453
            var x = reader.ReadSingle();
454
            var y = reader.ReadSingle();
455
            var z = reader.ReadSingle();
456
            reader.ReadSingle(); // skip unused
457
458 04839796 Oto Šťáva
            ActualDirectionVector = new Single3(x, y, z);
459 306eb3ca kovi
        }
460
461 04839796 Oto Šťáva
        private void ProcessTargetDirectionVector(BinaryReader reader)
462 306eb3ca kovi
        {
463
            var x = reader.ReadSingle();
464
            var y = reader.ReadSingle();
465
            var z = reader.ReadSingle();
466
            reader.ReadSingle(); // skip unused
467
468 04839796 Oto Šťáva
            TargetDirectionVector = new Single3(x, y, z);
469 306eb3ca kovi
        }
470
471
        private void ProcessCurve(BinaryReader reader)
472
        {
473
            UInt32 numberOfBytes = reader.ReadUInt32();
474
            UInt32 numberOfPoints = numberOfBytes / PointLength;
475
            Single3[] curve = new Single3[numberOfPoints];
476
477
            for(int i = 0; i < numberOfPoints; i++)
478
            {
479 3c2987e2 Oto Šťáva
                var x = reader.ReadSingle();
480
                var y = reader.ReadSingle();
481
                var z = reader.ReadSingle();
482 306eb3ca kovi
                curve[i] = new Single3(x, y, z);
483
            }
484
485 3c2987e2 Oto Šťáva
            lock(_curveLock)
486
            {
487
                CurveCounter++;
488
                Curve = curve;
489 306eb3ca kovi
            }
490
        }
491
492 a9ef8967 Oto Šťáva
        private void ProcessEot(BinaryReader reader)
493
        {
494
            UInt32 numberOfBytes = reader.ReadUInt32();
495
            Byte[] buffer = reader.ReadBytes((int) numberOfBytes);
496 04839796 Oto Šťáva
            EotReason = Encoding.ASCII.GetString(buffer);
497 a9ef8967 Oto Šťáva
            IsConnected = false;
498
        }
499
500
        private void ProcessProtocolMagic(BinaryReader reader)
501
        {
502
            byte[] value = reader.ReadBytes(8);
503
            if (!value.SequenceEqual(ProtocolMagicValue))
504
            {
505 306eb3ca kovi
                IsConnected = false;
506 a9ef8967 Oto Šťáva
            }
507
        }
508
509
        private void ProcessProtocolVersion(BinaryReader reader)
510
        {
511
            UInt32 value = reader.ReadUInt32();
512
            if (value != ProtocolVersionValue)
513
            {
514 306eb3ca kovi
                IsConnected = false;
515 a9ef8967 Oto Šťáva
            }
516
        }
517
518
        private void ProcessPing(BinaryReader reader, BinaryWriter writer)
519
        {
520
            UInt64 pingValue = reader.ReadUInt64();
521 6d9543aa Jakub Hejman
522
            writer.Write(PongId);
523 a9ef8967 Oto Šťáva
            writer.Write(pingValue);
524
        }
525 285c6fe5 Oto Šťáva
526
        private void SendEot(BinaryWriter writer, string reason)
527
        {
528
            writer.Write(EotId);
529
            writer.Write((UInt32) reason.Length);
530
            writer.Write(Encoding.ASCII.GetBytes(reason));
531
        }
532 a9ef8967 Oto Šťáva
    }
533
}