-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,270 @@ | ||
''' | ||
Cesium map module | ||
Samuel Dudley | ||
Jan 2016 | ||
''' | ||
|
||
import subprocess, os, json, time, sys, uuid | ||
|
||
from MAVProxy.modules.lib import mp_module | ||
from MAVProxy.modules.lib import mp_settings | ||
|
||
from pymavlink import mavutil | ||
import threading, Queue | ||
|
||
from autobahn.twisted.websocket import WebSocketServerProtocol, WebSocketServerFactory | ||
from twisted.python import log | ||
from twisted.internet import reactor | ||
|
||
class ServerProtocol(WebSocketServerProtocol): | ||
|
||
def onConnect(self, request): | ||
print("Client connecting: {0}".format(request.peer)) | ||
|
||
def onOpen(self): | ||
print("WebSocket connection open") | ||
self.id = uuid.uuid4() | ||
self.factory.data[self.id]=self | ||
payload = {'new_connection':self.id} | ||
self.factory.message_queue.put(payload) | ||
|
||
def onMessage(self, payload, isBinary): | ||
if isBinary: | ||
# TODO: handle binary | ||
pass | ||
else: | ||
# It's text based (JSON) | ||
payload = json.loads(payload) | ||
self.factory.message_queue.put(payload) | ||
|
||
def onClose(self, wasClean, code, reason): | ||
print("WebSocket connection closed: {0}".format(reason)) | ||
del self.factory.data[self.id] | ||
|
||
|
||
class CesiumModule(mp_module.MPModule): | ||
|
||
def __init__(self, mpstate): | ||
super(CesiumModule, self).__init__(mpstate, "cesium", "Cesium map module", public = True) | ||
self.add_command('cesium', self.cmd_cesium, [""]) | ||
|
||
self.data_stream = ['NAV_CONTROLLER_OUTPUT', 'VFR_HUD', 'ATTITUDE', 'GLOBAL_POSITION_INT', 'SYS_STATUS', 'MISSION_CURRENT', 'STATUSTEXT', 'FENCE_STATUS'] | ||
|
||
|
||
self.wp_change_time = 0 | ||
self.fence_change_time = 0 | ||
self.rally_change_time = 0 | ||
self.flightmode = None | ||
|
||
self.cesium_settings = mp_settings.MPSettings( | ||
[ ('localserver', bool, True), | ||
('debug', bool, True)]) | ||
|
||
self.aircraft = {'lat':None, 'lon':None, 'alt_wgs84':None, | ||
'roll':None, 'pitch':None, 'yaw':None} | ||
self.pos_target = {'lat':None, 'lon':None, 'alt_wgs84':None} | ||
self.fence = {} | ||
self.mission = {} | ||
|
||
self.web_server_process = None | ||
self.socket_server_thread = None | ||
|
||
self.run_web_server() | ||
self.run_socket_server() | ||
|
||
def run_socket_server(self): | ||
# log.startLogging(sys.stdout) | ||
self.factory = WebSocketServerFactory(u"ws://0.0.0.0:9000") | ||
self.factory.protocol = ServerProtocol | ||
self.factory.setProtocolOptions(maxConnections=100) | ||
self.factory.data = {} | ||
self.factory.message_queue = Queue.Queue() | ||
|
||
reactor.listenTCP(9000, self.factory) | ||
self.socket_server_thread = threading.Thread(target=reactor.run, args=(False,)) | ||
self.socket_server_thread.daemon = True | ||
self.socket_server_thread.start() | ||
|
||
def run_web_server(self): | ||
'''optionally launch the webserver on the local machine''' | ||
if self.cesium_settings.localserver: | ||
path_name = os.path.dirname(__file__) | ||
server_path = os.path.join(path_name,'app','cesium_io_server.py') | ||
|
||
if self.cesium_settings.debug: | ||
self.web_server_process = subprocess.Popen(['python', server_path]) | ||
else: | ||
server_fh = open(os.devnull,"w") | ||
self.web_server_process = subprocess.Popen(['python', server_path], stdout = server_fh, stderr = server_fh) | ||
server_fh.close() | ||
|
||
def send_data(self, data, target = None): | ||
'''push json data to the browser''' | ||
payload = json.dumps(data).encode('utf8') | ||
if target is not None: | ||
connection = self.factory.data[target] | ||
reactor.callFromThread(WebSocketServerProtocol.sendMessage, connection, payload) | ||
else: | ||
for connection in self.factory.data.values(): | ||
reactor.callFromThread(WebSocketServerProtocol.sendMessage, connection, payload) | ||
|
||
def cmd_cesium(self, args): | ||
'''cesium command parser''' | ||
usage = "usage: cesium <restart> <set> (CESIUMSETTING)" | ||
if len(args) == 0: | ||
print(usage) | ||
return | ||
if args[0] == "set": | ||
self.cesium_settings.command(args[1:]) | ||
elif args[0] == "count": | ||
print('%u connected' % int(len(self.factory.data))) | ||
elif args[0] == "restart": | ||
self.restart() | ||
else: | ||
print(usage) | ||
|
||
def send_defines(self, target = None): | ||
'''get the current mav defines and send them''' | ||
miss_cmds = {} | ||
frame_enum = {0: "Abs", 3: "Rel", 10: "AGL"} | ||
|
||
# auto-generate the list of mission commands | ||
for cmd in mavutil.mavlink.enums['MAV_CMD']: | ||
enum = mavutil.mavlink.enums['MAV_CMD'][cmd] | ||
name = enum.name | ||
name = name.replace('MAV_CMD_','') | ||
if name == 'ENUM_END': | ||
continue | ||
miss_cmds[cmd] = name | ||
|
||
self.defines = {} | ||
self.defines['frame_enum'] = frame_enum | ||
self.defines['mission_commands'] = miss_cmds | ||
self.send_data({"defines":self.defines}, target = target) | ||
|
||
|
||
def send_fence(self): | ||
'''load and draw the fence in cesium''' | ||
self.fence = {} | ||
self.fence_points_to_send = self.mpstate.public_modules['fence'].fenceloader.points | ||
for point in self.fence_points_to_send: | ||
point_dict = point.to_dict() | ||
iidx = point_dict['idx'] | ||
del point_dict['idx'] | ||
if idx != 0: # dont include the return location | ||
self.fence[idx] = point_dict | ||
self.send_data({"fence_data":self.fence}) | ||
|
||
def send_mission(self): | ||
'''load and draw the mission in cesium''' | ||
self.mission = {} | ||
self.mission_points_to_send = self.mpstate.public_modules['wp'].wploader.wpoints | ||
for point in self.mission_points_to_send: | ||
point_dict = point.to_dict() | ||
seq = point_dict['seq'] | ||
del point_dict['seq'] | ||
self.mission[seq] = point_dict | ||
self.send_data({"mission_data":self.mission}) | ||
|
||
def send_flightmode(self): | ||
self.send_data({"flightmode":self.master.flightmode}) | ||
self.flightmode = self.master.flightmode | ||
|
||
def restart(self): | ||
'''restart the web server''' | ||
if self.socket_server_thread is not None: | ||
reactor.callFromThread(reactor.stop) # Kill the socket server talking to the browser | ||
if self.web_server_process is not None: | ||
self.web_server_process.kill() # Kill the web server hosting the Cesium display | ||
|
||
self.run_web_server() | ||
self.run_socket_server() | ||
|
||
def mavlink_packet(self, m): | ||
'''handle an incoming mavlink packet''' | ||
if self.master.flightmode != self.flightmode: | ||
self.send_flightmode() | ||
|
||
if m.get_type() == 'POSITION_TARGET_GLOBAL_INT': | ||
msg_dict = m.to_dict() | ||
self.pos_target['lat']= msg_dict['lat_int'] | ||
self.pos_target['lon'] = msg_dict['lon_int'] | ||
self.pos_target['alt_wgs84'] = msg_dict['alt'] | ||
|
||
if None not in self.pos_target.values(): | ||
self.send_data({"pos_target_data":self.pos_target}) | ||
|
||
if m.get_type() in self.data_stream: | ||
msg_dict = m.to_dict() | ||
msg_dict['timestamp'] = m._timestamp | ||
self.send_data({'mav_data':msg_dict}) | ||
|
||
|
||
# if the waypoints have changed, redisplay | ||
last_wp_change = self.module('wp').wploader.last_change | ||
if self.wp_change_time != last_wp_change and abs(time.time() - last_wp_change) > 1: | ||
self.wp_change_time = last_wp_change | ||
self.send_mission() | ||
|
||
#this may have affected the landing lines from the rally points: | ||
self.rally_change_time = time.time() | ||
|
||
# if the fence has changed, redisplay | ||
if self.fence_change_time != self.module('fence').fenceloader.last_change: | ||
self.fence_change_time = self.module('fence').fenceloader.last_change | ||
self.send_fence() | ||
|
||
def idle_task(self): | ||
'''called on idle''' | ||
while not self.factory.message_queue.empty(): | ||
payload = self.factory.message_queue.get_nowait() | ||
if self.cesium_settings.debug: | ||
print payload | ||
|
||
if 'new_connection' in payload.keys(): | ||
self.send_defines(target=payload['new_connection']) | ||
self.send_fence() | ||
self.send_mission() | ||
self.send_flightmode() | ||
|
||
elif 'mode_set' in payload.keys(): | ||
self.mpstate.functions.process_stdin('%s' % (payload['mode_set'])) | ||
|
||
elif 'wp_set' in payload.keys(): | ||
self.mpstate.functions.process_stdin('wp set %u' % int(payload['wp_set'])) | ||
|
||
elif 'wp_move' in payload.keys(): | ||
self.mpstate.functions.process_stdin('wp move %u %f %f' % ( | ||
int(payload['wp_move']['idx']), | ||
float(payload['wp_move']['lat']), | ||
float(payload['wp_move']['lon']) | ||
) | ||
) | ||
|
||
elif 'wp_remove' in payload.keys(): | ||
self.mpstate.functions.process_stdin('wp remove %u' % int(payload['wp_remove'])) | ||
|
||
elif 'wp_list' in payload.keys(): | ||
self.mpstate.functions.process_stdin('wp list') | ||
|
||
elif 'fence_list' in payload.keys(): | ||
self.mpstate.functions.process_stdin('fence list') | ||
|
||
else: | ||
pass | ||
|
||
def unload(self): | ||
'''unload module''' | ||
if self.socket_server_thread is not None: | ||
reactor.callFromThread(reactor.stop) | ||
|
||
if self.web_server_process is not None: | ||
self.web_server_process.kill() # Kill the web server hosting the cesium display | ||
|
||
|
||
def init(mpstate): | ||
'''initialise module''' | ||
return CesiumModule(mpstate) | ||
|
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
python-dev |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
#!/usr/bin/env python | ||
|
||
|
||
|
||
async_mode = 'eventlet' | ||
|
||
import eventlet | ||
eventlet.monkey_patch() | ||
|
||
|
||
import time, os, binascii, datetime, subprocess, sys | ||
from os import environ | ||
|
||
|
||
import json | ||
import glob | ||
from uuid import uuid4 | ||
|
||
from flask import ( | ||
Flask, | ||
session, | ||
redirect, | ||
render_template, | ||
request, | ||
url_for, | ||
send_from_directory, | ||
Response, | ||
) | ||
|
||
from flask_socketio import ( | ||
SocketIO, | ||
emit, | ||
join_room, | ||
leave_room, | ||
close_room, | ||
rooms, | ||
disconnect, | ||
) | ||
|
||
|
||
|
||
app = Flask(__name__) | ||
app.secret_key = str(uuid4()) | ||
|
||
APP_ROOT = os.path.dirname(os.path.abspath(__file__)) | ||
APP_STATIC = os.path.join(APP_ROOT, 'static') | ||
with open(os.path.join(APP_ROOT, 'api_keys.txt', )) as fid: | ||
api_keys = json.load(fid) | ||
|
||
socketio = SocketIO(app, async_mode=async_mode) | ||
|
||
|
||
@app.route('/') | ||
def index(): | ||
return render_template('index.html', bing_api_key=api_keys['bing']) | ||
|
||
@app.route('/context/', methods=['POST']) | ||
def get_current_context(): | ||
markers = [str(request.values.get('markers')).lstrip('"').rstrip('"')] | ||
if 'null' in markers: | ||
markers = False | ||
return render_template('context_menu.html', markers=markers) | ||
|
||
@app.route('/exit', methods=["GET"]) | ||
def exit(): | ||
sys.exit() | ||
return "setting exit flag" | ||
|
||
|
||
def start_server(): | ||
|
||
socketio.run(app, host='0.0.0.0',port=5000) | ||
|
||
|
||
if __name__ == '__main__': | ||
start_server() | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
Flask-SocketIO | ||
eventlet | ||
autobahn[twisted] |
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<TileMap version="1.0.0" tilemapservice="http://tms.osgeo.org/1.0.0"> | ||
<Title>NE2_HR_LC_SR_W_DR_recolored.tif</Title> | ||
<Abstract></Abstract> | ||
<SRS>EPSG:4326</SRS> | ||
<BoundingBox miny="-90.00000000000000" minx="-180.00000000000000" maxy="90.00000000000000" maxx="180.00000000000000"/> | ||
<Origin y="-90.00000000000000" x="-180.00000000000000"/> | ||
<TileFormat width="256" height="256" mime-type="image/jpg" extension="jpg"/> | ||
<TileSets profile="geodetic"> | ||
<TileSet href="0" units-per-pixel="0.70312500000000" order="0"/> | ||
<TileSet href="1" units-per-pixel="0.35156250000000" order="1"/> | ||
<TileSet href="2" units-per-pixel="0.17578125000000" order="2"/> | ||
</TileSets> | ||
</TileMap> |
Large diffs are not rendered by default.