Skip to content

Commit

Permalink
use socket.io to update herd data to web UI
Browse files Browse the repository at this point in the history
  • Loading branch information
merlinran committed Jan 4, 2022
1 parent 13ba410 commit f0c9d70
Show file tree
Hide file tree
Showing 17 changed files with 137 additions and 99 deletions.
14 changes: 5 additions & 9 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,13 @@ FROM alpine:3.12
RUN echo "@community http://dl-cdn.alpinelinux.org/alpine/v3.12/community" >> /etc/apk/repositories
RUN apk add --no-cache build-base libffi-dev openssl-dev python3-dev curl krb5-dev linux-headers zeromq-dev lapack-dev blas-dev redis
RUN apk add cmake gcc libxml2 automake g++ subversion libxml2-dev libxslt-dev gfortran jpeg-dev py3-pip
RUN python3 -m pip install --upgrade pip
RUN python3 -m pip install wheel
RUN python3 -m pip install certifi==2020.06.20
RUN apk add tmux vim mercurial iw
RUN apk add bash # required by codecov GH action
RUN apk add raspberrypi; exit 0 # Only succeeds on raspberry pi but not needed otherwise.
RUN apk add py3-scipy py3-numpy-dev
RUN python3 -m pip install --upgrade pip
RUN python3 -m pip install wheel certifi==2020.06.20 pytest coverage[toml]
COPY server/requirements.txt /install/server/
RUN python3 -m pip install -r /install/server/requirements.txt
COPY vehicle/requirements.txt /install/vehicle/
RUN python3 -m pip install -r /install/vehicle/requirements.txt
RUN apk add tmux vim mercurial
RUN python3 -m pip install adafruit-circuitpython-mcp230xx coloredlogs
RUN apk add iw
RUN apk add raspberrypi; exit 0 # Only succeeds on raspberry pi but not needed otherwise.
RUN python3 -m pip install pytest coverage[toml]
RUN apk add --no-cache bash # required by codecov GH action
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,4 @@ docker-image: Dockerfile
docker build -t $(LOCAL_IMAGE) .

Dockerfile: vehicle/requirements.txt server/requirements.txt
@docker build --no-cache -t $(LOCAL_IMAGE) . # Force rebuilding image if requirements files have been changed
@docker build -t $(LOCAL_IMAGE) . && touch Dockerfile # Rebuilding image if requirements files have been changed
2 changes: 1 addition & 1 deletion server/redis.conf
Original file line number Diff line number Diff line change
Expand Up @@ -1057,7 +1057,7 @@ latency-monitor-threshold 0
# By default all notifications are disabled because most users don't need
# this feature and the feature has some overhead. Note that if you don't
# specify at least one of K or E, no events will be delivered.
notify-keyspace-events ""
notify-keyspace-events "E$"

############################### ADVANCED CONFIG ###############################

Expand Down
12 changes: 9 additions & 3 deletions server/redis_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,18 @@ def get_robot_command_key(robot_key):
return bytes(str(robot_key)[2:-1].replace(":key", ":command:key"), encoding='ascii')


def is_robot_key(key):
if ':robot:' in key:
if ':command:' not in key and ':energy_segment' not in key:
return True
return False


def get_robot_keys(redis_client):
robot_keys = []
for key in redis_client.scan_iter():
if ':robot:' in str(key):
if ':command:' not in str(key) and ':energy_segment' not in str(key):
robot_keys.append(key)
if is_robot_key(str(key)):
robot_keys.append(key)
# print(robot_keys)
return robot_keys

Expand Down
6 changes: 4 additions & 2 deletions server/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
redis
csaps
eventlet
flask-redis
flask-socketio
redis
zmq
csaps
41 changes: 36 additions & 5 deletions server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,13 @@
import time
from flask import Flask, render_template, request, send_from_directory, jsonify
from flask_redis import FlaskRedis
from flask_socketio import SocketIO
import re
import pickle
import datetime
import eventlet
eventlet.monkey_patch() # allow flask_socketio to support WebSocket.

import redis_utils
from model import RobotCommand
break
Expand All @@ -42,6 +46,7 @@
app = Flask(__name__, template_folder="templates")
app.config['REDIS_URL'] = "redis://:@localhost:6379/0"
redis_client = FlaskRedis(app)
socketio = SocketIO(app)

volatile_path = [] # When the robot is streaming a path, save it in RAM here.
active_site = "twistedfields"
Expand Down Expand Up @@ -309,13 +314,39 @@ def get_dense_path():
return "No keys found"


@socketio.on_error()
def socket_io_error_handler(e):
print('SocketIO error: ' + str(e))


def redis_change_handler(msg):
key = msg['data'].decode()
if redis_utils.is_robot_key(key):
print(key)
data = robots_to_json([key])
socketio.emit('herd-data', data, callback=lambda x: print(x))


def exception_handler(ex, pubsub, thread):
print(ex)
thread.stop()
thread.join(timeout=1.0)
pubsub.close()


if __name__ == "__main__":
while True:
try:
app.run(debug=True,
use_reloader=True,
host="0.0.0.0",
port=int("80"))
except BaseException:
pubsub = redis_client.pubsub()
# see https://redis.io/topics/notifications
pubsub.psubscribe(**{'__keyevent@0__:set': redis_change_handler})
thread = pubsub.run_in_thread(exception_handler=exception_handler)
socketio.run(app,
debug=True,
use_reloader=True,
host="0.0.0.0",
port=int("80"))
except Exception as e:
print(e)
print("Server had some error. Restarting...")
time.sleep(5)
65 changes: 27 additions & 38 deletions server/static/js/actions.js
Original file line number Diff line number Diff line change
@@ -1,43 +1,32 @@
// actions are triggered by UI events. It mostly involves calling backend API, and as a result, updating the store.

var INTERVAL = 2000;

function getRobotData() {
api("get_herd_data")
.then((resp) => resp.json())
.then(function (herd) {
store.robots = herd;

//loop through herd data from server
for (const robot of herd) {
const date = Date.parse(robot.time_stamp);
robot.data_age_sec = (new Date() - date) / 1000.0;

// clear autonomy once on page open?
if (store.have_cleared_autonomy[robot.name] == false) {
store.have_cleared_autonomy[robot.name] = true;
modifyAutonomyHold(`${robot.name}`, false);
}

// if(robot.autonomy_hold)
// {
// let speed = 0.0;
// let active = false;
// updateVehicleAutonomy(`${robot.name}`, speed, active);
// }
//console.log(robot.autonomy_hold)
//console.log(robot.strafeD)

if (robot.gps_path_data.length != store.gpsPathLength) {
store.gps_path = robot.gps_path_data;
store.gpsPathLength = robot.gps_path_data.length;
}
}
}).catch(function (error) {
console.log(error);
});

window.setTimeout(getRobotData, INTERVAL);
function updateHerdData(herd) {
//loop through herd data from server
for (const robot of herd) {
const date = Date.parse(robot.time_stamp);
robot.data_age_sec = (new Date() - date) / 1000.0;
Vue.set(store.robots, robot.name, robot);

// clear autonomy once on page open?
if (!store.have_cleared_autonomy[robot.name]) {
Vue.set(store.have_cleared_autonomy,robot.name, true);
modifyAutonomyHold(`${robot.name}`, false);
}

// if(robot.autonomy_hold)
// {
// let speed = 0.0;
// let active = false;
// updateVehicleAutonomy(`${robot.name}`, speed, active);
// }
//console.log(robot.autonomy_hold)
//console.log(robot.strafeD)

if (robot.gps_path_data.length != store.gpsPathLength) {
store.gps_path = robot.gps_path_data;
store.gpsPathLength = robot.gps_path_data.length;
}
}
}

function loadPath(pathname) {
Expand Down
15 changes: 13 additions & 2 deletions server/static/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//
// http://192.168.1.170:8090/api/projects/3/tasks/3116cce4-4215-4de9-9e9a-0e9c93df87f6/orthophoto/tiles/{Z}/{X}/{Y}.png

var app = new Vue({
const app = new Vue({
el: "#app",
data: store,
delimiters: ["${", "}"], // the default, "{{-}}", conflicts with Jinjia2
Expand All @@ -25,6 +25,17 @@ var app = new Vue({
})
.then((access_token_data) => {store.access_token_data = access_token_data})
.catch(()=>{})
.finally(getRobotData); // kick off the ball after the map gets setup.
},
});

const socket = io();
socket.on("connect", () => {
console.log("socket connected");
});

socket.on("disconnect", () => {
console.log("socket diconnected");
});
socket.on("herd-data", (data) => {
updateHerdData(data)
});
14 changes: 7 additions & 7 deletions server/static/js/components.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Vue.component("control-panel", {
props: ["show-plots", "map", "path-names"],
props: ["path-names"],
data: function () {
return {
isDrawingPolygon: false,
Expand Down Expand Up @@ -285,7 +285,7 @@ Vue.component("plots", {
});

Vue.component("map-canvas", {
props: ["robots", "current_robot_index",
props: ["robots", "current_robot_name",
"displayed_path", "displayed_dense_path", "drawing_polygon",
"path_start", "path_end", "path_point_to_remove"],
components: {
Expand Down Expand Up @@ -318,18 +318,18 @@ Vue.component("map-canvas", {
},
methods: {
selectRobot(index) {
if (store.current_robot_index == index) {
store.current_robot_index = null
if (store.current_robot_name == index) {
store.current_robot_name = null
} else {
store.current_robot_index = index
store.current_robot_name = index
}
},
robotIcon(index) {
robotIcon(name) {
return L.icon({
iconUrl: "/static/images/robot.png",
iconSize: [30, 40],
iconAnchor: [15, 20],
className: this.current_robot_index === index ? "selected-acorn": null,
className: this.current_robot_name === name ? "selected-acorn": null,
})
},
},
Expand Down
7 changes: 7 additions & 0 deletions server/static/js/lib/socket.io.min.js

Large diffs are not rendered by default.

6 changes: 2 additions & 4 deletions server/static/js/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@
//

var store = {
debug: true,
map: null,
showPlots: false,
drawing_polygon: false,
robots: [],
current_robot_index: 0, // select the first robot by default
robots: {},
current_robot_name: '',
pathNames: [],
gps_path: [],
displayed_path: [],
Expand Down
1 change: 1 addition & 0 deletions server/templates/acorn.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
<!--app-->

<script src="{{ url_for('static', filename='js/lib/bootstrap.bundle.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/lib/socket.io.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/lib/vue.js') }}"></script>
<script src="{{ url_for('static', filename='js/lib/leaflet.js') }}"></script>
<script src="{{ url_for('static', filename='js/lib/Leaflet.Editable.js') }}"></script>
Expand Down
6 changes: 1 addition & 5 deletions server/templates/control-panel.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
<control-panel
v-bind:path-names="pathNames"
v-bind:map="map"
inline-template
>
<control-panel v-bind:path-names="pathNames" inline-template>
<div class="row">
<div class="col-sm-3">
<h1>Acorn control</h1>
Expand Down
6 changes: 3 additions & 3 deletions server/templates/map.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<map-canvas
v-bind:robots="robots"
v-bind:current_robot_index="current_robot_index"
v-bind:current_robot_name="current_robot_name"
v-bind:displayed_path="displayed_path"
v-bind:displayed_dense_path="displayed_dense_path"
v-bind:drawing_polygon="drawing_polygon"
Expand All @@ -18,12 +18,12 @@
v-bind:path_point_to_remove="path_point_to_remove"
></path-point>
<debug-path-point v-for="(point, i) in displayed_dense_path" :key="'dense-' + i" :index="i" :point="point"></debug-path-point>
<div v-for="(robot, index) in robots" :key="'robot-' + index">
<div v-for="(robot, name) in robots" :key="'robot-' + name">
<debug-path-point v-for="(point, i) in robot.debug_points" :key="'debug-' + i" :index="i" :point="point"></debug-path-point>
<gps-point v-for="(point, i) in robot.gps_path_data" :key="'gps-' + i" :pt="point"></gps-point>
<l-polyline :lat-lngs="robot.live_path_data" :color="'#00FF00'"></l-polyline>
<l-marker :lat-lng="[robot.lat, robot.lon]" :icon="arrowIcon" :options="{rotationAngle: robot.turn_intent_degrees + robot.heading}"></l-marker>
<l-marker :lat-lng="[robot.lat, robot.lon]" :icon="robotIcon(index)" :options="{rotationAngle: robot.heading}" v-on:click="selectRobot(index)"></l-marker>
<l-marker :lat-lng="[robot.lat, robot.lon]" :icon="robotIcon(name)" :options="{rotationAngle: robot.heading}" v-on:click="selectRobot(name)"></l-marker>
</div>
</l-map>
</map-canvas>
4 changes: 2 additions & 2 deletions server/templates/robot-cardview.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<robot-cardview
v-if="current_robot_index !== null && robots[current_robot_index]"
v-bind:robot="robots[current_robot_index]"
v-if="current_robot_name && robots[current_robot_name]"
v-bind:robot="robots[current_robot_name]"
v-bind:show-plots="showPlots"
inline-template
>
Expand Down
13 changes: 6 additions & 7 deletions vehicle/master_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,10 @@

_MAX_GPS_DISTANCES = 1000

_SIMULATION_UPDATE_PERIOD = 0.1
_SIMULATION_SERVER_REPLY_TIMEOUT_MILLISECONDS = 300

_SIMULATION_UPDATE_PERIOD = 0.5
_SIMULATION_SERVER_REPLY_TIMEOUT_SECONDS = 0.3
_UPDATE_PERIOD = 2.0
_SERVER_REPLY_TIMEOUT_MILLISECONDS = 3000
_SERVER_REPLY_TIMEOUT_SECONDS = 3

_MAX_ALLOWED_SERVER_COMMS_OUTAGE_SEC = 60

Expand Down Expand Up @@ -270,10 +269,10 @@ def get_path(self, pathkey, robot):
time.sleep(0.5)
while attempts < 5:
if self.simulation:
timeout = _SIMULATION_SERVER_REPLY_TIMEOUT_MILLISECONDS
timeout = _SIMULATION_SERVER_REPLY_TIMEOUT_SECONDS
else:
timeout = _SERVER_REPLY_TIMEOUT_MILLISECONDS
if self.server_comms_parent_conn.poll(timeout=timeout / 1000.0):
timeout = _SERVER_REPLY_TIMEOUT_SECONDS
if self.server_comms_parent_conn.poll(timeout=timeout):
self.logger.info("READING PATH DATA")
command, msg = self.server_comms_parent_conn.recv()
if command == _CMD_READ_KEY_REPLY:
Expand Down
22 changes: 12 additions & 10 deletions vehicle/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
svgpathtools
zmq
odrive==0.4.12
evdev
geopy==1.22.0
Adafruit_ADS1x15
pyyaml
scipy
click
psutil
netifaces
RPi.GPIO
adafruit-blinka
adafruit-circuitpython-mcp230xx
click
coloredlogs
csaps
evdev
geopy==1.22.0
netifaces
odrive==0.4.12
psutil
pyyaml
scipy
svgpathtools
zmq

0 comments on commit f0c9d70

Please sign in to comment.