-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathappManager.py
238 lines (197 loc) · 9.34 KB
/
appManager.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
#
# appManager.py
# shotmanager
#
# Handles app connection state and IO.
#
# Created by Will Silva on 3/5/2016.
# Copyright (c) 2016 3D Robotics. All rights reserved.
import errno
import os
import platform
import select
import socket
import string
import sys
import threading
import monotonic
import time
sys.path.append(os.path.realpath(''))
import modes
import settings
import shots
import Queue
import struct
from dronekit.lib import LocationGlobalRelative
from sololink import btn_msg
import app_packet
import GoProManager
import shotLogger
import GeoFenceManager
logger = shotLogger.logger
APP_SERVER_PORT = 5507
APP_TCP_BUFSIZE = 1024
class appManager():
def __init__(self, shotMgr):
self.shotMgr = shotMgr
self.connected = False
self.client = None
self.client_address = None
self.clientQueue = None
self.packetBuffer = ""
self.bindServer()
def bindServer(self):
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
if platform.system() != 'Darwin':
self.server.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) # After 1 second, start KEEPALIVE
self.server.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 1) # TCP Idle true
self.server.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 5) # 5 seconds in between keepalive pings
self.server.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 5) # 5 max fails
# Bind the socket to the port
while True:
try:
self.server.bind(('', APP_SERVER_PORT))
except:
logger.log("[app]: Can't bind, address in use. Retrying in 1 second.")
time.sleep(1.0)
else:
break
logger.log("[app]: Ready for connections from app.")
# Listen for incoming connections
self.server.listen(0)
# establishes a connection to a Solo app
def connectClient(self):
client, client_address = self.server.accept()
if self.isAppConnected():
if client_address[0] != self.client_address[0]:
logger.log("[app]: Already connected to client %s - rejecting %s." % (self.client_address, client_address))
#TO DO:send system INFO packet to app with rejection flag True
client.close()
return
else:
logger.log("[app]: Detected re-connection attempt for client %s - reconnecting.")
self.disconnectClient()
self.client = client
self.client_address = client_address
logger.log("[app]: Connected to App. %s" % (self.client_address,))
#TO DO:send system INFO packet to app with rejection flag False
self.connected = True
self.client.setblocking(0)
self.shotMgr.inputs.append(self.client)
self.clientQueue = Queue.Queue()
self.broadcastShotToApp(self.shotMgr.currentShot)
self.shotMgr.buttonManager.setButtonMappings() # called to un-grey out Artoo buttons
self.shotMgr.goproManager.sendState() # send gopro state to app
def disconnectClient(self):
if self.isAppConnected():
logger.log("[app]: Closing client connection with %s." % (self.client_address,))
self.connected = False
if self.client in self.shotMgr.outputs:
self.shotMgr.outputs.remove(self.client)
if self.client in self.shotMgr.inputs:
self.shotMgr.inputs.remove(self.client)
self.client.close()
self.client = None
self.clientQueue = None
self.shotMgr.buttonManager.setButtonMappings() # called to grey-out Artoo buttons
# if this type of shot requires a client present at all times, then kill the shot
if self.shotMgr.currentShot in shots.ALWAYS_NEEDS_APP_CONNECTION:
self.shotMgr.enterShot(shots.APP_SHOT_NONE)
# Clear Geofence when app disconnect
self.shotMgr.geoFenceManager.clearGeoFence()
else:
logger.log('[app]: Attempted to close app connection, but no app was connected!')
def isAppConnected(self):
return self.connected
def sendPacket(self, pkt):
if self.isAppConnected():
self.clientQueue.put(pkt)
if self.client not in self.shotMgr.outputs:
self.shotMgr.outputs.append(self.client)
else:
logger.log('[app]: Can\'t send packet - app is not connected!')
def broadcastShotToApp(self, shot):
packet = struct.pack('<IIi', app_packet.SOLO_MESSAGE_GET_CURRENT_SHOT, 4, shot)
self.sendPacket(packet)
def exception(self):
logger.log("[app]: Exception with " + self.client.getpeername())
self.appMgr.disconnectClient()
def write(self):
if self.clientQueue:
try:
msg = self.clientQueue.get_nowait()
except Queue.Empty:
# no messages left, stop checking
self.shotMgr.outputs.remove(self.client)
else:
try:
self.client.send(msg)
except Exception as ex:
logger.log("[app]: Exception on send. (%s)" % ex)
self.disconnectClient()
def parse(self):
try:
data = self.client.recv(APP_TCP_BUFSIZE) # grab one kB
if not data:
raise socket.error()
except socket.error:
logger.log('[app]: Data from client %s is nil.' % (self.client_address,))
self.disconnectClient()
return
self.packetBuffer += data
while self.packetBuffer is not '':
if len(self.packetBuffer) < app_packet.SOLO_MESSAGE_HEADER_LENGTH:
logger.log('[app]: Not enough data for a Solo packet header yet.')
return
(packetType, packetLength) = struct.unpack('<II', self.packetBuffer[:app_packet.SOLO_MESSAGE_HEADER_LENGTH])
if len(self.packetBuffer) < app_packet.SOLO_MESSAGE_HEADER_LENGTH + packetLength:
logger.log('[app]: Not enough data for a Solo packet (ID: %s) yet.' % (packetType,))
return
# extract packet value from TLV based on known packetLength and packetType
packetValue = self.packetBuffer[app_packet.SOLO_MESSAGE_HEADER_LENGTH:(app_packet.SOLO_MESSAGE_HEADER_LENGTH+packetLength)]
handled = False
# if a shot is active, pass the TLV packet to the shot's handlePacket function
if self.shotMgr.curController:
handled = self.shotMgr.curController.handlePacket(packetType, packetLength, packetValue)
# if the packet wasn't understood by the shot, then try to handle it
if not handled:
handled = self.handlePacket(packetType, packetLength, packetValue)
# crop out the packet from the buffer and move on
self.packetBuffer = self.packetBuffer[app_packet.SOLO_MESSAGE_HEADER_LENGTH+packetLength:]
def handlePacket(self, packetType, packetLength, packetValue):
try:
if packetType == app_packet.SOLO_MESSAGE_SET_CURRENT_SHOT:
shot = struct.unpack('<i', packetValue)[0]
logger.log("[app]: App requested shot : %s." % shots.SHOT_NAMES[shot])
self.shotMgr.enterShot(shot)
elif packetType == app_packet.SOLO_MESSAGE_GET_BUTTON_SETTING:
# this is a request for the current button mapping.
# fill in the fields and send it back
(button, event, shot, APMmode) = struct.unpack('<iiii', packetValue)
if event == btn_msg.Press:
(mappedShot, mappedMode) = self.shotMgr.buttonManager.getFreeButtonMapping(button)
logger.log("[app]: App requested button mapping for %d"%(button))
# send back to the app
packet = struct.pack('<IIiiii', app_packet.SOLO_MESSAGE_GET_BUTTON_SETTING, 16, button, event, mappedShot, mappedMode)
self.sendPacket(packet)
# app is trying to map a button
elif packetType == app_packet.SOLO_MESSAGE_SET_BUTTON_SETTING:
(button, event, shot, APMmode) = struct.unpack('<iiii', packetValue)
if event == btn_msg.Press:
self.shotMgr.buttonManager.setFreeButtonMapping( button, shot, APMmode )
# Gopromanager handles these messages
elif packetType in GoProManager.GOPROMESSAGES:
self.shotMgr.goproManager.handlePacket( packetType, packetValue )
elif packetType == app_packet.SOLO_REWIND_OPTIONS or packetType == app_packet.SOLO_HOME_LOCATION:
self.shotMgr.rewindManager.handlePacket( packetType, packetLength, packetValue )
# Geofence messages
elif packetType in GeoFenceManager.GEO_FENCE_MESSAGES:
self.shotMgr.geoFenceManager.handleFenceData(packetType, packetValue)
else:
logger.log("[app]: Got an unknown packet type: %d." % (packetType,))
except Exception as e:
logger.log('[app]: Error handling packet. (%s)' % e)
return False
else:
return True