2
2
import logging
3
3
from time import time , ctime
4
4
from dataclasses import dataclass
5
- import ctypes # for windows mouse
6
- import socket # udp networking
7
- import struct # binary unpacking
5
+ import ctypes # for windows mouse
6
+ import socket # udp networking
7
+ import struct # binary unpacking
8
8
9
9
print ("\n \n CLIENT: Starting PhilNav\n " )
10
10
11
11
# parse command line arguments
12
12
parser = argparse .ArgumentParser ()
13
- parser .add_argument ( '-v' ,
14
- '--verbose' ,
15
- action = 'store_true' ,
16
- help = 'provide verbose logging' )
17
- parser .add_argument ( '-H' ,
18
- '--host' ,
19
- type = str ,
20
- default = "0.0.0.0" ,
21
- help = 'bind to ip address, default 0.0.0.0' )
22
- parser .add_argument ( '-p' ,
23
- '--port' ,
24
- type = int ,
25
- default = 4245 ,
26
- help = 'bind to port, default 4245' )
27
- parser .add_argument ( '-s' ,
28
- '--speed' ,
29
- type = int ,
30
- default = 30 ,
31
- help = 'mouse speed, default 30' )
13
+ parser .add_argument (
14
+ "-v" , "--verbose" , action = "store_true" , help = "provide verbose logging"
15
+ )
16
+ parser .add_argument (
17
+ "-H" ,
18
+ "--host" ,
19
+ type = str ,
20
+ default = "0.0.0.0" ,
21
+ help = "bind to ip address, default 0.0.0.0" ,
22
+ )
23
+ parser .add_argument (
24
+ "-p" , "--port" , type = int , default = 4245 , help = "bind to port, default 4245"
25
+ )
26
+ parser .add_argument (
27
+ "-s" , "--speed" , type = int , default = 30 , help = "mouse speed, default 30"
28
+ )
32
29
args = parser .parse_args ()
33
30
34
31
if args .verbose :
35
- logging .getLogger ().setLevel (logging .DEBUG )
36
- logging .info (" Logging verbosely\n " )
32
+ logging .getLogger ().setLevel (logging .DEBUG )
33
+ logging .info (" Logging verbosely\n " )
34
+
37
35
38
36
# returned from ctypes.windll.user32.GetCursorPos
39
37
# simple point.x, point.y
40
38
class POINT (ctypes .Structure ):
41
- _fields_ = [("x" , ctypes .c_long ), ("y" , ctypes .c_long )]
39
+ _fields_ = [("x" , ctypes .c_long ), ("y" , ctypes .c_long )]
40
+
42
41
43
42
# initialize networking
44
- sock = socket .socket (socket .AF_INET , socket .SOCK_DGRAM ) # Read datagrams over UDP
45
- sock .settimeout (1 ) # Without a timeout, this script will "hang" if nothing is received
46
- sock .bind ((args .host , args .port )) # Register our socket
43
+ sock = socket .socket (socket .AF_INET , socket .SOCK_DGRAM ) # Read datagrams over UDP
44
+ sock .settimeout (1 ) # Without a timeout, this script will "hang" if nothing is received
45
+ sock .bind ((args .host , args .port )) # Register our socket
47
46
48
47
# How to get local IP address?
49
48
# Doesn't work for me: socket.gethostbyname(socket.gethostname())
@@ -52,64 +51,74 @@ class POINT(ctypes.Structure):
52
51
# This is not your public Internet address. This is your local area network address.
53
52
# On Windows, make sure it's set to a private network to allow discovery.
54
53
# You'd think public would allow discovery, but when you are *in public* - like at a coffee shop - you don't want strangers to access your PC.
55
- text_listening = f"Listening on { sock .getsockname ()} for mouse data from Raspberry Pi server..."
54
+ text_listening = (
55
+ f"Listening on { sock .getsockname ()} for mouse data from Raspberry Pi server..."
56
+ )
56
57
print (ctime () + " - " + text_listening )
57
58
print ("\n Press Ctrl-C to exit\n " )
58
59
60
+
59
61
# Stats for debugging & performance.
60
62
# The goal is 60 frames per second, or 16.67ms per frame.
61
63
# That leads to a very smooth mouse cursor. (SmartNav was 100 fps)
62
64
# A standard non-gaming monitor is also 60Hz. (TV is 30 fps)
63
65
@dataclass
64
66
class PhilNavDebug :
65
- time_start = time ()
66
- time_debug = time ()
67
- debug_num = 0
68
- msg_time_start = time ()
69
- msg_time_total = 0
70
- msg_num = 0
67
+ time_start = time ()
68
+ time_debug = time ()
69
+ debug_num = 0
70
+ msg_time_start = time ()
71
+ msg_time_total = 0
72
+ msg_num = 0
73
+
71
74
72
75
# Main event loop:
73
76
# 1. Receive mouse delta over UDP
74
77
# 2. Update mouse cursor position
75
78
# 3. Repeat forever until Ctrl-C
76
79
while True :
77
- try :
78
- # 48 bytes of 6 doubles in binary C format. Why? Because it's OpenTrack's protocol.
79
- # x, y, z, pitch, yaw, roll = struct.unpack('dddddd', data)
80
- # PhilNav uses x, y as x_diff, y_diff and moves the mouse relative to its current position.
81
- # https://github.com/opentrack/opentrack/issues/747
82
- data , addr = sock .recvfrom (48 )
83
- except TimeoutError :
84
- if int (time () - PhilNavDebug .time_start ) % 5 == 0 :
85
- logging .info (f" { ctime ()} - { text_listening } " )
86
- continue
87
- else :
88
- # measure time
89
- PhilNavDebug .msg_time_start = time ()
90
- PhilNavDebug .msg_num += 1
91
-
92
- # Using OpenTrack protocol, but PhilNav uses:
93
- # x_diff, y_diff, n/a, n/a, n/a, camera capture time
94
- x , y , z , pitch , yaw , roll = struct .unpack ('dddddd' , data )
95
-
96
- # The Magic Happens Now! eg. move mouse cursor =P
97
- pt = POINT ()
98
- ctypes .windll .user32 .GetCursorPos (ctypes .byref (pt )) # get current mouse position by reference (C++ thing)
99
- # I'm moving the Y axis slightly faster because looking left and right is easier than nodding up and down.
100
- # Also, monitors are wider than they are tall.
101
- x_new = round (pt .x + x * args .speed )
102
- y_new = round (pt .y + y * args .speed * 1.33 )
103
- ctypes .windll .user32 .SetCursorPos (x_new , y_new ) # move mouse cursor
104
-
105
- # I'm trying to measure the total time from capturing the frame on the camera to moving the mouse cursor on my PC. This isn't super accurate. It's sometimes negative (TIME TRAVEL!!!). The clock difference between the Raspberry Pi and my PC seems to be around 10-20ms?
106
- time_diff_ms = int ((time () - roll )* 1000 )
107
-
108
- # it's 60 FPS, so only debug once per second
109
- if time () - PhilNavDebug .time_debug > 1 :
110
- PhilNavDebug .time_debug = time ()
111
- PhilNavDebug .debug_num += 1
112
- # display legend every 5 seconds
113
- if PhilNavDebug .debug_num % 5 == 1 :
114
- logging .info (f" { ctime ()} - Received: ({ 'x_diff' :>8} ,{ 'y_diff' :>8} ,{ 'n/a' :>8} ,{ 'n/a' :>8} ,{ 'loc ns' :>8} ,{ 'net ms' :>8} )" )
115
- logging .info (f" { ctime ()} - Received: ({ x :> 8.2f} ,{ y :> 8.2f} ,{ z :> 8.2f} ,{ pitch :> 8.2f} ,{ (time () - PhilNavDebug .msg_time_start )* 1000 :> 8.2f} ,{ time_diff_ms :> 8} )" )
80
+ try :
81
+ # 48 bytes of 6 doubles in binary C format. Why? Because it's OpenTrack's protocol.
82
+ # x, y, z, pitch, yaw, roll = struct.unpack('dddddd', data)
83
+ # PhilNav uses x, y as x_diff, y_diff and moves the mouse relative to its current position.
84
+ # https://github.com/opentrack/opentrack/issues/747
85
+ data , addr = sock .recvfrom (48 )
86
+ except TimeoutError :
87
+ if int (time () - PhilNavDebug .time_start ) % 5 == 0 :
88
+ logging .info (f" { ctime ()} - { text_listening } " )
89
+ continue
90
+ else :
91
+ # measure time
92
+ PhilNavDebug .msg_time_start = time ()
93
+ PhilNavDebug .msg_num += 1
94
+
95
+ # Using OpenTrack protocol, but PhilNav uses:
96
+ # x_diff, y_diff, n/a, n/a, n/a, camera capture time
97
+ x , y , z , pitch , yaw , roll = struct .unpack ("dddddd" , data )
98
+
99
+ # The Magic Happens Now! eg. move mouse cursor =P
100
+ pt = POINT ()
101
+ ctypes .windll .user32 .GetCursorPos (
102
+ ctypes .byref (pt )
103
+ ) # get current mouse position by reference (C++ thing)
104
+ # I'm moving the Y axis slightly faster because looking left and right is easier than nodding up and down.
105
+ # Also, monitors are wider than they are tall.
106
+ x_new = round (pt .x + x * args .speed )
107
+ y_new = round (pt .y + y * args .speed * 1.33 )
108
+ ctypes .windll .user32 .SetCursorPos (x_new , y_new ) # move mouse cursor
109
+
110
+ # I'm trying to measure the total time from capturing the frame on the camera to moving the mouse cursor on my PC. This isn't super accurate. It's sometimes negative (TIME TRAVEL!!!). The clock difference between the Raspberry Pi and my PC seems to be around 10-20ms?
111
+ time_diff_ms = int ((time () - roll ) * 1000 )
112
+
113
+ # it's 60 FPS, so only debug once per second
114
+ if time () - PhilNavDebug .time_debug > 1 :
115
+ PhilNavDebug .time_debug = time ()
116
+ PhilNavDebug .debug_num += 1
117
+ # display legend every 5 seconds
118
+ if PhilNavDebug .debug_num % 5 == 1 :
119
+ logging .info (
120
+ f" { ctime ()} - Received: ({ 'x_diff' :>8} ,{ 'y_diff' :>8} ,{ 'n/a' :>8} ,{ 'n/a' :>8} ,{ 'loc ns' :>8} ,{ 'net ms' :>8} )"
121
+ )
122
+ logging .info (
123
+ f" { ctime ()} - Received: ({ x :> 8.2f} ,{ y :> 8.2f} ,{ z :> 8.2f} ,{ pitch :> 8.2f} ,{ (time () - PhilNavDebug .msg_time_start )* 1000 :> 8.2f} ,{ time_diff_ms :> 8} )"
124
+ )
0 commit comments