-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathowl.py
2471 lines (2180 loc) · 89.3 KB
/
owl.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/python
#
# Copyright (c) PhaseSpace, Inc ${YEAR}
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# PHASESPACE, INC BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# ChangeLog
# =========
# * 5.2.461 (2019-05-03)
# * fixed udp broadcast socket open error on windows
# * 5.2.459 (2019-05-02)
# * added Context.SendBufferSize to adjust for sending of large profiles.
# * changed _Protocol.send() logic to account for chunked data
# * fixed improper slot in Planes
# * set event.info=1 by default on initialize()
# * 5.2.440 (2019-02-12)
# * fixed a documentation bug
# * added print logic for deviceinfo events in main example
# * 5.2.439 (2019-02-12)
# * fixed an exception handler using python3 incompatible syntax
# * stopped event.info flag from defaulting to on in initialize() to reduce spam
# * 5.2.437 (2019-02-06)
# * added Context.findTrackerinfo() and Context.findDeviceInfo()
# * added Context.deviceOptions() to allow setting individual device options.
# * 5.2.418 (2018-07-05)
# * added json tracker loading to main example code. Users can now specify json
# files from Master Client on the commandline to create trackers.
# * fixed order of arguments in example code in assignMarkers
# * 5.2.328 (2017-05-03)
# * corrected indentation on docstrings
# * added '_' prefix to imports and helper classes to decrease namespace pollution
# when importing with 'from owl import *' syntax.
# * changed LIBOWL_REV to OWL_VERSION
# * added extra check for name when receiving camera events
# * tweaks to example code
# * fixed property unset
"""
libowl2 API Python Implementation
PhaseSpace, Inc. ${YEAR}
Example 1 (command-line, see __main__):
python owl.py -h
python owl.py --device localhost --timeout 5000000
Example 2 (python):
#!/usr/bin/python
import owl
context = owl.Context()
context.open("localhost")
context.initialize()
context.streaming(1)
while context.isOpen() and context.property("initialized"):
event = context.nextEvent()
if not event: continue
if event.type_id == Type.CAMERA:
for camera in event.data:
print((camera))
elif event.type_id == Type.FRAME:
if "markers" in event:
for m in event.markers:
if m.cond > 0: print((m))
if "rigids" in event:
for r in event.rigids:
if r.cond > 0: print((r))
elif event.type_id == Type.ERROR or event.name == "done":
break
context.done()
context.close()
"""
OWL_PROTOCOL_VERSION = "2"
OWL_VERSION = "5.2.${LIBOWL_REV}"
__version__ = OWL_VERSION
import sys as _sys
import time as _time
import struct as _struct
import socket as _socket
import errno as _errno
import select as _select
import os as _os
import collections as _collections
import re as _re
import ctypes as _ctypes
try:
import json
pass
except ImportError:
pass
if _sys.version_info.major <= 2 and _sys.version_info.minor <= 7 and _sys.version_info.micro < 6:
raise Exception("minimum python version 2.7.6 is required")
if _sys.version_info.major >= 3:
import urllib.parse as _urllib
_urlunquote = _urllib.unquote
pass
elif _sys.version_info.major >= 2:
import urllib as _urllib
_urlunquote = _urllib.unquote
pass
#
class Type:
INVALID = 0
BYTE = 1
STRING = BYTE
INT = 2
FLOAT = 3
ERROR = 0x7F
EVENT = 0x80
FRAME = EVENT
CAMERA = 0x81
PEAK = 0x82
PLANE = 0x83
MARKER = 0x84
RIGID = 0x85
INPUT = 0x86
MARKERINFO = 0x87
TRACKERINFO = 0x88
FILTERINFO = 0x89
DEVICEINFO = 0x8A
PACKINFO = 0x8B
pass
#
class OWLError(Exception):
def __init__(self, s = None):
if s != None: Exception.__init__(self, s)
pass
pass
#
class RecvError(OWLError):
def __init__(self, s = None):
if s != None: OWLError.__init__(self, s)
pass
pass
#
class SendError(OWLError):
def __init__(self, s = None):
if s != None: OWLError.__init__(self, s)
pass
pass
#
class OpenError(OWLError):
def __init__(self, s = None):
if s != None: OWLError.__init__(self, s)
pass
pass
#
class InitError(OWLError):
def __init__(self, s = None):
if s != None: OWLError.__init__(self, s)
pass
pass
#
class Event():
"""
The primary data structure returned by the server. An Event instance stores
metadata and data from the capture hardware in its dictionary.
Frame-synced data will arrive as members inside a FRAME event.
Unsynchronized data will arrive as independent events.
Notably, system errors are returned asynchronously from the server as Events.
Members:
type_id = Event type as enumerated in Type
id = Session dependent ID of the event
flags = See PhaseSpace documentation
time = System time of data
type_name = Name of event type
name = Name of event
Example:
event = context.nextEvent()
if event.type_id == Type.CAMERA:
for c in event.data:
print(c)
if event.type_id == Type.FRAME:
if "markers" in event:
for m in event.markers:
print(m)
if event.type_id == Type.ERROR:
raise OWLError("ERROR")
"""
def __init__(self, type_id, id, flags=0, time=0, type_name=None, name=None, **kwargs):
self.type_id = type_id
self.id = id
self.flags = flags
self.time = time
self.type_name = type_name
self.name = name
for k,v in kwargs.items(): self[k] = v
pass
def __str__(self):
exclude = ["type_id", "id", "flags", "time", "type_name", "name"]
keys = filter(lambda x: x not in exclude, self.__dict__.keys())
keys = ",".join(keys)
return "Event(type_id={} id={}, flags={}, time={}, type_name=\"{}\", name=\"{}\" keys={})".format(self.type_id, self.id, self.flags, self.time, self.type_name, self.name, keys)
def __getitem__(self, key):
return self.__dict__[key]
def __setitem__(self, key, item):
self.__dict__[key] = item
pass
def __contains__(self, key):
return key in self.__dict__
pass
#
class Camera():
"""
A data structure representing a single calibrated camera.
Members:
id = Numeric ID of the camera
flags = See PhaseSpace hardware documentation
pose = 3D position and quaternion rotation of the camera (x, y, z, s, a, b ,c)
cond = Condition number of the camera. <=0 may mean the camera is decalibrated or missing.
"""
def __init__(self, id, flags=0, pose=[0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], cond=-1.0):
self.id = id
self.flags = flags
self.pose = pose
self.cond = cond
pass
def __str__(self):
return "Camera(id={}, flags={}, pose={}, cond={})".format(self.id, self.flags, self.pose, self.cond)
pass
#
class Peak():
"""
(Advanced users only)
A data structure representing a raw peak as captured by the camera hardware
Members:
id = Numeric ID of the peak
flags = See PhaseSpace hardware documentation
time = System time when the data was captured
camera = ID of the camera this peak came from
detector = Detector ID of the camera this peak came from
width = Width of the peak, in pixels
pos = Normalized position of the peak on the detector
amp = Amplitude of the peak
"""
__slots__ = ['id', 'flags', 'time', 'camera', 'detector', 'width', 'pos', 'amp']
def __init__(self, id, flags=0, time=0, camera=0, detector=0, width=0, pos=0.0, amp=0.0):
self.id = id
self.flags = flags
self.time = time
self.camera = camera
self.detector = detector
self.width = width
self.pos = pos
self.amp = amp
pass
def __str__(self):
return "Peak(id={}, flags={}, time={}, camera={}, detector={}, width={}, pos={}, amp={})".format(self.id, self.flags, self.time, self.camera, self.detector, self.width, self.pos, self.amp)
pass
#
class Plane():
"""
(Advanced users only)
A data structure representing a projected plane from a camera.
Members:
id = numeric id of the plane
flags = see PhaseSpace hardware documentation
time = system time when the data was captured
camera = id of the camera this plane came from
detector = detector id of the camera this plane came from
plane = 4 floating point numbers representing the plane
distance = intersection error. <=0 means invalid.
"""
__slots__ = ['id', 'flags', 'time', 'camera', 'detector', 'plane', 'distance']
def __init__(self, id, flags=0, time=-1, camera=0, detector=0, plane=[0.0, 0.0, 0.0, 0.0], distance=-1.0):
self.id = id
self.flags = flags
self.time = time
self.camera = camera
self.detector = detector
self.plane = plane
self.distance = distance
pass
def __str__(self):
return "Plane(id={}, flags={}, time={}, camera={}, detector={}, plane={}, distance={})".format(self.id, self.flags, self.time, self.camera, self.detector, self.plane, self.distance)
pass
#
class Marker():
"""
A data structure representing a marker.
Members:
id = Numeric ID of the marker
flags = See PhaseSpace hardware documentation
time = System time when the data was captured
x = X-axis position of the marker
y = Y-axis position of the marker
z = Z-axis position of the marker
cond = condition number of the data. <=0 means invalid
"""
__slots__ = ['id', 'flags', 'time', 'x', 'y', 'z', 'cond']
def __init__(self, id, flags=0, time=-1, x=0.0, y=0.0, z=0.0, cond=-1.0):
self.id = id
self.flags = flags
self.time = time
self.x = x
self.y = y
self.z = z
self.cond = cond
pass
def __str__(self):
return "Marker(id={}, flags={}, time={}, x={}, y={}, z={}, cond={})".format(self.id, self.flags, self.time, self.x, self.y, self.z, self.cond)
pass
#
class Rigid():
"""
A data structure representing a rigid body.
Members:
id = Numeric ID of the rigid body
flags = See PhaseSpace hardware documentation
time = System time when the data was captured
pose = 3D position and quaternion rotation of the rigid body (x, y, z, s, a, b ,c)
cond = Condition number of the data. <=0 means invalid
"""
__slots__ = ['id', 'flags', 'time', 'pose', 'cond']
def __init__(self, id, flags=0, time=-1, pose=[0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], cond=-1.0):
self.id = id
self.flags = flags
self.time = time
self.pose = pose
self.cond = cond
pass
def __str__(self):
return "Rigid(id={}, flags={}, time={}, pose={}, cond={})".format(self.id, self.flags, self.time, self.pose, self.cond)
pass
#
class Input():
"""
(Advanced users only)
A data structure representing auxiliary input data
Members:
hw_id = ID of the device this data came from
flags = See PhaseSpace hardware documentation
time = Device time of captured data
data = Raw bytes of the captured data
"""
def __init__(self, hw_id, flags=0, time=-1, data=None):
self.hw_id = hw_id
self.flags = flags
self.time = time
self.data = data
pass
def __str__(self):
n = 8
_data = ",".join(["0x%x" % d for d in self.data[:n]])
_data = str.format("\"{}...({} bytes)\"", _data, len(self.data))
return "Input(hw_id={}, flags={}, time={}, data={})".format(self.hw_id, self.flags, self.time, _data)
pass
#
class MarkerInfo():
"""
A data structure containing information about configured markers.
Members:
id = Numeric ID of the marker
tracker_id = ID of the tracker that the marker belongs to
name = User configured name of the marker
options = String of space separated list of options. Each option is an '=' separated key-value pair.
Supported options:
pos Comma separated list of 3 floats representing the 3D local
position of the marker in the assigned rigid body tracker
Example:
t_id = 2
m_id = 1
context.createTracker(t_id, "rigid", "mytracker")
context.assignMarkers(MarkerInfo(m_id, t_id, "mymarker", "pos=100,0,-200"))
"""
def __init__(self, id, tracker_id, name=None, options=""):
self.id = id
self.tracker_id = tracker_id
self.name = name
self.options = options
pass
def __str__(self):
return "MarkerInfo(id={}, tracker_id={}, name=\"{}\", options=\"{}\")".format(self.id, self.tracker_id, self.name, self.options)
pass
#
class TrackerInfo():
"""
A data structure containing information about configured trackers
Members:
id = Numeric id of tracker
type = Type of tracker. Supported types include "point" and "rigid"
name = User configured name
options = String of space separated list of options. Each option is an '=' separated key-value pair.
marker_ids = List of ids of markers assigned to this tracker
Example:
context.createTrackers([TrackerInfo(1, "rigid", "mytracker01", "", [0, 1, 2, 3]),
TrackerInfo(2, "rigid", "mytracker02", "", [4, 5, 6, 7])])
"""
def __init__(self, id, type, name=None, options="", marker_ids=[]):
self.id = id
self.type = type
self.name = name
self.options = options
self.marker_ids = marker_ids
pass
def __str__(self):
return "TrackerInfo(id={} type={} name=\"{}\" options=\"{}\" marker_ids={} )".format(self.id, self.type, self.name, self.options, self.marker_ids)
pass
#
class FilterInfo():
"""
A data structure containing information on configured filters
Members:
period = Period of the filter
name = User-defined name of the filter
options = String of space separated list of options. Each option is an '=' separated key-value pair.
Supported options:
type Name of the filter type. Supported types: lerp, spline
Example:
context.filters(FilterInfo(120, "myfilter", "type=lerp"))
"""
def __init__(self, period, name, options=""):
self.period = period
self.name = name
self.options = options
pass
def __str__(self):
return "FilterInfo(period={}, name=\"{}\", options=\"{}\")".format(self.period, self.name, self.options)
pass
#
class DeviceInfo():
"""
A data structure containing information on devices attached to the server
Members:
hw_id = ID of the device
time = Time
name = Reported name of device
options = Device options
status = Device status
"""
def __init__(self, hw_id, time=-1, type=None, name=None, options="", status=""):
self.hw_id = hw_id
self.time = time
self.type = type
self.name = name
self.options = options
self.status = status
pass
def __str__(self):
return "DeviceInfo(hw_id={}, time={}, type={}, name=\"{}\", options=\"{}\", status=\"{}\")".format(self.hw_id, self.time, self.type, self.name, self.options, self.status)
pass
#
class Context:
"""
Main class for communicating with a PhaseSpace Impulse server through the libowl2 API.
Instantiate a Context to connect to a single server.
Members:
debug = Set to True to enable debugging output (warning: very verbose)
Example:
see package documentation (Ex: help(owl))
"""
class STATS:
def __init__(self):
self.udp_packet_count = 0
self.udp_bcast_packet_count = 0
self.tcp_packet_count = 0
pass
pass
def __init__(self):
""" """
# print debugging output
self.debug = False
# for internal testing
self.flag = 0
# note: these may be limited by OS settings (net.core.rmem_max)
self.ReceiveBufferSize = 4*1024*1024
self.SendBufferSize = 1*1024*1024
#
self.stats = Context.STATS()
# main tcp socket
self.socket = None
# udp receive sockets
self.udp = None
self.broadcast = None
# open state
self.__port_offset = 0;
self.__connect_state = 0
# session state
self.__properties = {}
self.__types = {}
self.__names = {}
self.__trackers = {}
self.__markers = {}
self.__filters = {}
self.__devices = {}
self.__options = {}
self.__events = _collections.deque()
self.__newFrame = None
self.__clear()
self.__socket_connected = False
return
def __log(self, *args):
if self.debug: print("[%12d] %s" % (int(_time.time() * 1E9)," ".join((str(a) for a in args))))
return
def __clear(self):
self.__log("clear()");
self.__properties = {}
self.__properties["opened"] = 0
self.__properties["name"] = ""
self.__properties["initialized"] = 0
self.__properties["streaming"] = 0
self.__properties["mode"] = ""
self.__properties["local"] = 0
self.__properties["systemtimebase"]= [0, 0]
self.__properties["timebase"]= [0, 0]
self.__properties["maxfrequency"] = 960.0
self.__properties["systemfrequency"] = 0.0
self.__properties["frequency"] = 0.0
self.__properties["scale"] = 1.0
self.__properties["slave"] = 0
self.__properties["systempose"] = [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]
self.__properties["pose"] = [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]
self.__properties["options"] = ""
self.__properties["cameras"] = {}
self.__properties["markers"] = 0
self.__properties["markerinfo"] = {}
self.__properties["trackers"] = {}
self.__properties["trackerinfo"] = {}
self.__properties["filters"] = {}
self.__properties["filterinfo"] = {}
self.__properties["deviceinfo"] = {}
self.__properties["profiles"] = ""
self.__properties["defaultprofile"] = ""
self.__properties["profiles.json"] = ""
self.newFrame = None
self.__trackers = {}
self.__markers = {}
self.__filters = {}
self.__devices = {}
self.__options = {}
self.__connect_state = 0
self.__events.clear()
return
def open(self, name, open_options=""):
"""
Connect to a PhaseSpace Impulse server via TCP/IP.
Arguments:
name = Address or hostname of server device
open_options = String of space separated list of options. Each option is a key-value pair separated by an '=' character.
Supported open_options:
timeout Number of microseconds to wait before timing out.
Default is 5000000. If timeout is zero, then open will
attempt to connect in asynchronous mode and will return zero
immediately. Connection state must be polled continuously
until success.
Returns:
0 if attempting asynchronous connection. User must poll the connection by
calling open again repeatedly until nonzero is returned.
1 on success
Raises an exception on error
Example:
context = Context()
context.open("localhost", "timeout=5000000")
"""
try:
self.__log("open(%s, %s)" % (name, open_options))
if self.__properties["opened"] == 1:
self.__log(" already open.");
return 1
# send/receive buffers
params = {"ReceiveBufferSize" : self.ReceiveBufferSize,
"SendBufferSize" : self.SendBufferSize }
self.__protocol = _Protocol(**params)
self.__protocol_udp = _ProtocolUdp(**params)
# parse options
opts = _utils.tomap(open_options)
timeout_usec = 10000000
if "timeout" in opts: timeout_usec = int(opts["timeout"])
timeout = timeout_usec * 1E-6
# connect to server
if self.__connect_state == 0:
self.__clear()
self.__properties["opening"] = 1
# parse host and port offset
DEFAULT_PORT = 8000
self.__port_offset = 0
name = name.split(":")
self.__port_offset = 0
if len(name) > 1:
self.__port_offset = int(name[1])
pass
port = DEFAULT_PORT + self.__port_offset
self.__properties["name"] = name[0]
self.socket = _socket.socket(_socket.AF_INET, _socket.SOCK_STREAM, 0)
# configure socket
self.socket.setsockopt(_socket.IPPROTO_TCP, _socket.TCP_NODELAY, 1)
self.socket.setsockopt(_socket.SOL_SOCKET, _socket.SO_RCVBUF, self.ReceiveBufferSize)
self.socket.setsockopt(_socket.SOL_SOCKET, _socket.SO_SNDBUF, self.SendBufferSize)
self.socket.setblocking(0)
# start asynchronous connect
self.__log(" connecting to %s:%d" % (name[0], port))
ret = self.socket.connect_ex((name[0], port))
if ret == _errno.EAGAIN or ret == _errno.EWOULDBLOCK or ret == _errno.EALREADY or ret == _errno.EINPROGRESS:
# connect needs more time
self.__connect_state = 1
if timeout_usec == 0: return 0
pass
elif ret == 0:
self.__connect_state = 2
pass
else: raise OpenError("unknown socket error: %d" % ret)
pass
# wait for socket to be ready
if self.__connect_state == 1:
r,w,e = _select.select([], [self.socket], [self.socket], timeout)
if len(e) > 0: raise OpenError("socket select error")
if len(w) == 0:
if timeout == 0: return 0
raise OpenError("connection timed out")
err = _utils.getsocketerror(self.socket) #TODO fix
if err != 0 and err not in (_errno.EWOULDBLOCK, _errno.EALREADY, _errno.EINPROGRESS):
raise _socket.error("socket error: %d\n" % err)
self.__connect_state += 1
pass
if self.__connect_state == 2:
self.__log(" waiting for server state")
self.__socket_connected = True
self.__connect_state += 1
pass
# recieve state from server
if self.__connect_state == 3:
def wait_func():
self.__recv(0)
return self.__properties["opened"] == 1
if self.__wait(timeout, wait_func):
self.__connect_state += 1
pass
pass
if self.__properties["opened"] == 1:
self.__log(" success")
del self.__properties["opening"]
# upload version info
if self.__connect_state == 4:
self.__send(Type.BYTE, "internal", "protocol=%s libowl=%s" % (OWL_PROTOCOL_VERSION, OWL_VERSION))
self.__connect_state += 1
pass
# tcp connection established, open udp receive socket
try:
self.udp = _socket.socket(_socket.AF_INET, _socket.SOCK_DGRAM, 0)
self.udp.setblocking(0)
self.udp.setsockopt(_socket.SOL_SOCKET, _socket.SO_RCVBUF, self.ReceiveBufferSize)
self.udp.setsockopt(_socket.SOL_SOCKET, _socket.SO_REUSEADDR, 1)
self.__log("binding to udp port %s" % self.socket.getsockname()[1])
self.udp.bind(self.socket.getsockname())
pass
except Exception as e:
self.__log("udp bind error");
udp = None
pass
return 1
if timeout == 0: return 0
raise OpenError("connection timed out (state: %d)" % self.__connect_state)
except Exception as e:
if "opening" in self.__properties: del self.__properties["opening"]
self.close()
raise
return
def close(self):
"""
Disconnect from the current server.
"""
try:
self.__log("close()")
# shutdown tcp socket
if self.__socket_connected: self.socket.shutdown(_socket.SHUT_RDWR);
self.socket.close()
# shutdown udp socket
if self.broadcast != None:
self.broadcast.close()
self.broadcast = None
pass
if self.udp != None:
self.udp.close()
self.udp = None
pass
pass
except Exception as e:
_sys.stderr.write(str(e))
pass
finally:
self.__clear()
self.__socket_connected = False
pass
return
def isOpen(self):
"""
Query whether this Context is connected to a server.
"""
if not self.socket: return False
return self.__socket_connected and self.property("opened") == 1;
def initialize(self, init_options=""):
"""
Set up a streaming session with the server. Must be called after a successful call to open(). Note, a successful return does not guarantee the system is configured correctly. The server may send error Events back to the client asynchronously that must be checked with the nextEvent() function.
Arguments:
init_options = String of space separated list of options. Each option is a key-value pair separated by an '=' character.
Supported init_options:
timeout Number of microseconds to wait for the operation.
Default is 5000000.
frequency frequency to send capture data to this client at.
streaming See streaming().
slave 0 or 1. Enables or disables slave mode.
event.raw 0 or 1. Enables or disables streaming of raw unfiltered
data. Default is 1.
event.markers 0 or 1. Enables or disables streaming of marker data.
Default is 1.
event.rigids 0 or 1. Enables or disables streaming of rigid body data.
Default is 1.
event.peaks 0 or 1. Enables or disables streaming of peak data.
Default is 0.
event.info 0 or 1 Enables or disables info events.
Default is 0.
event.planes Advanced users only
event.inputs Advanced users only
Returns:
>0 on success
Raises an exception on error
Example:
context = Context()
context.open("192.168.1.230")
context.initialize("event.markers=1 event.rigids=0")
"""
try:
self.__log("initialize(%s)" % init_options)
if not self.isOpen(): raise InitError("no connection")
if self.property("initialized") == 1:
self.__log(" already initialized")
return 1
# parse options
opts = _utils.tomap(init_options)
timeout_usec = 10000000
if "timeout" in opts: timeout_usec = int(opts["timeout"])
timeout = timeout_usec * 1E-6
if not "initializing" in self.__properties:
name = self.__properties["name"]
profiles = self.__properties["profiles"]
defaultprofile = self.__properties["defaultprofile"]
profiles_json = self.__properties["profiles.json"]
self.__clear()
self.__properties["opened"] = 1
self.__properties["name"] = name
self.__properties["profiles"] = profiles
self.__properties["defaultprofile"] = defaultprofile
self.__properties["profiles.json"] = profiles_json
self.__properties["initializing"] = 1
init_options = "event.raw=1 event.markers=1 event.rigids=1 event.info=1"+(" "+init_options if init_options else "")
self.__log(" uploading options: %s" % init_options)
# tell server to initialize
self.__send(Type.BYTE, "initialize", init_options)
if timeout_usec == 0: return 0
self.__log(" waiting for response")
pass
def wait_func():
self.__recv(timeout)
return self.property("initialized") == 1
# wait for server state
if not self.__wait(timeout, wait_func) and timeout_usec == 0:
return 0
if self.property("initialized") == 0:
self.__log(" failed")
if timeout_usec > 0:
raise InitError("timed out")
raise InitError("init failed")
if self.flag == 1: raise InitError("test")
except Exception as e:
if "initializing" in self.__properties: del self.__properties["initializing"]
self.__properties["initialized"] = 0
raise
self.__log(" success")
return 1
def done(self, done_options=""):
"""
Inform the server that the streaming session is done. initialize() can be called again after a session has been terminated.
Arguments:
done_options = String of space separated list of options. Each option is a key-value pair separated by an '=' character.
Supported done_options:
timeout Number of microseconds to wait before timing out.
Default is 1000000. If timeout is zero, then done() will
send a done packet to the server asynchronously and return
before the server responds. The user can call done() again
to poll for the server response.
keepalive Keep session alive, even if no other clients are connected.
This allows future initialization calls to complete faster.
Options for the next initialization operation are ignored.
Returns:
1 on success, 0 if the timeout option is >0 and server has not responded within timeout.
-1 is there is no connection to server.
Raises an exception on error any other error.
Example:
context = Context()
context.open("192.168.1.230")
context.initialize()
context.done("timeout=10000000")
"""
self.__log("done %s" % (self.property("initialized")))
if not self.isOpen(): return -1
opts = _utils.tomap(done_options)
timeout = 10000000
if "timeout" in opts: timeout = int(opts["timeout"])
timeout *= 1E-6
if property("initialized") == 0:
if "flushing" in self.__properties:
del self.__properties["flushing"]
return 1
if not "flushing" in self.__properties:
self.__properties["flushing"] = 1
# inform server that this session is done
self.__send(Type.BYTE, "done", done_options)
pass
def wait_func():
if self.flag == 1: return False
self.__recv(0)
return self.property("initialized") == 0
self.__wait(timeout, wait_func)
if self.property("initialized") == 1:
if timeout > 0: return -1
return 0
if "flushing" in self.__properties:
del self.__properties["flushing"]
return 1
def streaming(self, enable=None):
"""
Get or set the streaming property of this context.
The streaming property instructs the server to start sending data to the client.
This is an asynchronous operation when called with arguments.
Arguments:
enable None to obtain the current value
0 to disable streaming
1 to enable tcp streaming
2 to enable udp streaming
3 to enable udp broadcast
Returns:
Returns the current streaming value if called with no parameters
No return value if called with a parameter.
Raises an exception on error.
Example:
if not context.streaming():
context.streaming(1)
"""
return self.__getset_prop(Type.INT, "streaming", enable)
def frequency(self, freq=None):
"""
Get or set the frequency property of this context.
The frequency property controls the frequency of the data sent to this client.
This is an asynchronous operation when called with arguments.
Arguments:
freq = A floating point number to set the frequency, or None to query the current frequency.
Returns:
The current frequency if called with no parameters.
No return value if called with a parameter.
Raises an exception on error.
Example:
context.frequency(960)
while context.frequency() != 960:
_time.sleep(0)
"""
if freq != None: freq = float(freq)
return self.__getset_prop(Type.FLOAT, "frequency", freq)
def timeBase(self, num=None, denom=None):
"""
Get or set the timebase property of this context.
The timebase property controls the time scale of the returned data.
This is an asynchronous operation when called with arguments.
Arguments:
num = Integer numerator of the timebase, or None
denom = Integer denominator of the timebase, or None
Returns:
The current timebase as a list of integers if called with no parameters
No return value if called with parameters.
Raises an exception on error.
Example:
context.timeBase(1, 1000000) # set time domain to microseconds
"""
if num != None and denom != None: value = [int(num), int(denom)]
else: value = None
ret = self.__getset_prop(Type.INT, "timebase", value)
if ret == None: return
return ret
def scale(self, scale=None):
"""
Get or set the scale property of this context.
The scale property controls the scale factor applied to incoming data.
This is an asynchronous operation when called with arguments.