diff --git a/Dockerfile b/Dockerfile index dbc0dfe..0594b26 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/Makefile b/Makefile index e7ecbf0..810348b 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/server/redis.conf b/server/redis.conf index 1731632..0e83285 100644 --- a/server/redis.conf +++ b/server/redis.conf @@ -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 ############################### diff --git a/server/redis_utils.py b/server/redis_utils.py index 40b06cc..f371f9b 100644 --- a/server/redis_utils.py +++ b/server/redis_utils.py @@ -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 diff --git a/server/requirements.txt b/server/requirements.txt index 0dec800..0b0828d 100644 --- a/server/requirements.txt +++ b/server/requirements.txt @@ -1,4 +1,6 @@ -redis +csaps +eventlet flask-redis +flask-socketio +redis zmq -csaps diff --git a/server/server.py b/server/server.py index cf3af59..b2559ec 100644 --- a/server/server.py +++ b/server/server.py @@ -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 @@ -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" @@ -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) diff --git a/server/static/js/actions.js b/server/static/js/actions.js index ecdf361..0868edd 100644 --- a/server/static/js/actions.js +++ b/server/static/js/actions.js @@ -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) { diff --git a/server/static/js/app.js b/server/static/js/app.js index e2f3c2c..054760d 100644 --- a/server/static/js/app.js +++ b/server/static/js/app.js @@ -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 @@ -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) +}); diff --git a/server/static/js/components.js b/server/static/js/components.js index 98b7d20..4c0cc7c 100644 --- a/server/static/js/components.js +++ b/server/static/js/components.js @@ -1,5 +1,5 @@ Vue.component("control-panel", { - props: ["show-plots", "map", "path-names"], + props: ["path-names"], data: function () { return { isDrawingPolygon: false, @@ -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: { @@ -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, }) }, }, diff --git a/server/static/js/lib/socket.io.min.js b/server/static/js/lib/socket.io.min.js new file mode 100644 index 0000000..282fb15 --- /dev/null +++ b/server/static/js/lib/socket.io.min.js @@ -0,0 +1,7 @@ +/*! + * Socket.IO v4.4.0 + * (c) 2014-2021 Guillermo Rauch + * Released under the MIT License. + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).io=e()}(this,(function(){"use strict";function t(e){return t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},t(e)}function e(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function n(t,e){for(var n=0;nt.length)&&(e=t.length);for(var n=0,r=new Array(e);n=t.length?{done:!0}:{done:!1,value:t[r++]}},e:function(t){throw t},f:o}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var i,s=!0,a=!1;return{s:function(){n=n.call(t)},n:function(){var t=n.next();return s=t.done,t},e:function(t){a=!0,i=t},f:function(){try{s||null==n.return||n.return()}finally{if(a)throw i}}}}var d=/^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/,y=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],v=function(t){var e=t,n=t.indexOf("["),r=t.indexOf("]");-1!=n&&-1!=r&&(t=t.substring(0,n)+t.substring(n,r).replace(/:/g,";")+t.substring(r,t.length));for(var o,i,s=d.exec(t||""),a={},c=14;c--;)a[y[c]]=s[c]||"";return-1!=n&&-1!=r&&(a.source=e,a.host=a.host.substring(1,a.host.length-1).replace(/;/g,":"),a.authority=a.authority.replace("[","").replace("]","").replace(/;/g,":"),a.ipv6uri=!0),a.pathNames=function(t,e){var n=/\/{2,9}/g,r=e.replace(n,"/").split("/");"/"!=e.substr(0,1)&&0!==e.length||r.splice(0,1);"/"==e.substr(e.length-1,1)&&r.splice(r.length-1,1);return r}(0,a.path),a.queryKey=(o=a.query,i={},o.replace(/(?:^|&)([^&=]*)=?([^&]*)/g,(function(t,e,n){e&&(i[e]=n)})),i),a};var m={exports:{}};try{m.exports="undefined"!=typeof XMLHttpRequest&&"withCredentials"in new XMLHttpRequest}catch(t){m.exports=!1}var g=m.exports,k="undefined"!=typeof self?self:"undefined"!=typeof window?window:Function("return this")();function b(t){var e=t.xdomain;try{if("undefined"!=typeof XMLHttpRequest&&(!e||g))return new XMLHttpRequest}catch(t){}if(!e)try{return new(k[["Active"].concat("Object").join("X")])("Microsoft.XMLHTTP")}catch(t){}}function w(t){for(var e=arguments.length,n=new Array(e>1?e-1:0),r=1;r1?{type:O[n],data:t.substring(1)}:{type:O[n]}:S},M=function(t,e){if(I){var n=function(t){var e,n,r,o,i,s=.75*t.length,a=t.length,c=0;"="===t[t.length-1]&&(s--,"="===t[t.length-2]&&s--);var u=new ArrayBuffer(s),h=new Uint8Array(u);for(e=0;e>4,h[c++]=(15&r)<<4|o>>2,h[c++]=(3&o)<<6|63&i;return u}(t);return U(n,e)}return{base64:!0,data:t}},U=function(t,e){return"blob"===e&&t instanceof ArrayBuffer?new Blob([t]):t},V=String.fromCharCode(30),H=function(t){i(o,t);var n=h(o);function o(t){var r;return e(this,o),(r=n.call(this)).writable=!1,A(c(r),t),r.opts=t,r.query=t.query,r.readyState="",r.socket=t.socket,r}return r(o,[{key:"onError",value:function(t,e){var n=new Error(t);return n.type="TransportError",n.description=e,f(s(o.prototype),"emit",this).call(this,"error",n),this}},{key:"open",value:function(){return"closed"!==this.readyState&&""!==this.readyState||(this.readyState="opening",this.doOpen()),this}},{key:"close",value:function(){return"opening"!==this.readyState&&"open"!==this.readyState||(this.doClose(),this.onClose()),this}},{key:"send",value:function(t){"open"===this.readyState&&this.write(t)}},{key:"onOpen",value:function(){this.readyState="open",this.writable=!0,f(s(o.prototype),"emit",this).call(this,"open")}},{key:"onData",value:function(t){var e=F(t,this.socket.binaryType);this.onPacket(e)}},{key:"onPacket",value:function(t){f(s(o.prototype),"emit",this).call(this,"packet",t)}},{key:"onClose",value:function(){this.readyState="closed",f(s(o.prototype),"emit",this).call(this,"close")}}]),o}(R),K="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_".split(""),Y={},z=0,$=0;function W(t){var e="";do{e=K[t%64]+e,t=Math.floor(t/64)}while(t>0);return e}function J(){var t=W(+new Date);return t!==D?(z=0,D=t):t+"."+W(z++)}for(;$<64;$++)Y[K[$]]=$;J.encode=W,J.decode=function(t){var e=0;for($=0;$0&&void 0!==arguments[0]?arguments[0]:{};return o(t,{xd:this.xd,xs:this.xs},this.opts),new nt(this.uri(),t)}},{key:"doWrite",value:function(t,e){var n=this,r=this.request({method:"POST",data:t});r.on("success",e),r.on("error",(function(t){n.onError("xhr post error",t)}))}},{key:"doPoll",value:function(){var t=this,e=this.request();e.on("data",this.onData.bind(this)),e.on("error",(function(e){t.onError("xhr poll error",e)})),this.pollXhr=e}}]),s}(Q),nt=function(t){i(o,t);var n=h(o);function o(t,r){var i;return e(this,o),A(c(i=n.call(this)),r),i.opts=r,i.method=r.method||"GET",i.uri=t,i.async=!1!==r.async,i.data=void 0!==r.data?r.data:null,i.create(),i}return r(o,[{key:"create",value:function(){var t=this,e=w(this.opts,"agent","pfx","key","passphrase","cert","ca","ciphers","rejectUnauthorized","autoUnref");e.xdomain=!!this.opts.xd,e.xscheme=!!this.opts.xs;var n=this.xhr=new b(e);try{n.open(this.method,this.uri,this.async);try{if(this.opts.extraHeaders)for(var r in n.setDisableHeaderCheck&&n.setDisableHeaderCheck(!0),this.opts.extraHeaders)this.opts.extraHeaders.hasOwnProperty(r)&&n.setRequestHeader(r,this.opts.extraHeaders[r])}catch(t){}if("POST"===this.method)try{n.setRequestHeader("Content-type","text/plain;charset=UTF-8")}catch(t){}try{n.setRequestHeader("Accept","*/*")}catch(t){}"withCredentials"in n&&(n.withCredentials=this.opts.withCredentials),this.opts.requestTimeout&&(n.timeout=this.opts.requestTimeout),n.onreadystatechange=function(){4===n.readyState&&(200===n.status||1223===n.status?t.onLoad():t.setTimeoutFn((function(){t.onError("number"==typeof n.status?n.status:0)}),0))},n.send(this.data)}catch(e){return void this.setTimeoutFn((function(){t.onError(e)}),0)}"undefined"!=typeof document&&(this.index=o.requestsCount++,o.requests[this.index]=this)}},{key:"onSuccess",value:function(){this.emit("success"),this.cleanup()}},{key:"onData",value:function(t){this.emit("data",t),this.onSuccess()}},{key:"onError",value:function(t){this.emit("error",t),this.cleanup(!0)}},{key:"cleanup",value:function(t){if(void 0!==this.xhr&&null!==this.xhr){if(this.xhr.onreadystatechange=Z,t)try{this.xhr.abort()}catch(t){}"undefined"!=typeof document&&delete o.requests[this.index],this.xhr=null}}},{key:"onLoad",value:function(){var t=this.xhr.responseText;null!==t&&this.onData(t)}},{key:"abort",value:function(){this.cleanup()}}]),o}(R);if(nt.requestsCount=0,nt.requests={},"undefined"!=typeof document)if("function"==typeof attachEvent)attachEvent("onunload",rt);else if("function"==typeof addEventListener){addEventListener("onpagehide"in k?"pagehide":"unload",rt,!1)}function rt(){for(var t in nt.requests)nt.requests.hasOwnProperty(t)&&nt.requests[t].abort()}var ot="function"==typeof Promise&&"function"==typeof Promise.resolve?function(t){return Promise.resolve().then(t)}:function(t,e){return e(t,0)},it=k.WebSocket||k.MozWebSocket,st="undefined"!=typeof navigator&&"string"==typeof navigator.product&&"reactnative"===navigator.product.toLowerCase(),at=function(t){i(o,t);var n=h(o);function o(t){var r;return e(this,o),(r=n.call(this,t)).supportsBinary=!t.forceBase64,r}return r(o,[{key:"name",get:function(){return"websocket"}},{key:"doOpen",value:function(){if(this.check()){var t=this.uri(),e=this.opts.protocols,n=st?{}:w(this.opts,"agent","perMessageDeflate","pfx","key","passphrase","cert","ca","ciphers","rejectUnauthorized","localAddress","protocolVersion","origin","maxPayload","family","checkServerIdentity");this.opts.extraHeaders&&(n.headers=this.opts.extraHeaders);try{this.ws=st?new it(t,e,n):e?new it(t,e):new it(t)}catch(t){return this.emit("error",t)}this.ws.binaryType=this.socket.binaryType||"arraybuffer",this.addEventListeners()}}},{key:"addEventListeners",value:function(){var t=this;this.ws.onopen=function(){t.opts.autoUnref&&t.ws._socket.unref(),t.onOpen()},this.ws.onclose=this.onClose.bind(this),this.ws.onmessage=function(e){return t.onData(e.data)},this.ws.onerror=function(e){return t.onError("websocket error",e)}}},{key:"write",value:function(t){var e=this;this.writable=!1;for(var n=function(n){var r=t[n],o=n===t.length-1;x(r,e.supportsBinary,(function(t){try{e.ws.send(t)}catch(t){}o&&ot((function(){e.writable=!0,e.emit("drain")}),e.setTimeoutFn)}))},r=0;r1&&void 0!==arguments[1]?arguments[1]:{};return e(this,a),r=s.call(this),n&&"object"===t(n)&&(i=n,n=null),n?(n=v(n),i.hostname=n.host,i.secure="https"===n.protocol||"wss"===n.protocol,i.port=n.port,n.query&&(i.query=n.query)):i.host&&(i.hostname=v(i.host).host),A(c(r),i),r.secure=null!=i.secure?i.secure:"undefined"!=typeof location&&"https:"===location.protocol,i.hostname&&!i.port&&(i.port=r.secure?"443":"80"),r.hostname=i.hostname||("undefined"!=typeof location?location.hostname:"localhost"),r.port=i.port||("undefined"!=typeof location&&location.port?location.port:r.secure?"443":"80"),r.transports=i.transports||["polling","websocket"],r.readyState="",r.writeBuffer=[],r.prevBufferLen=0,r.opts=o({path:"/engine.io",agent:!1,withCredentials:!1,upgrade:!0,timestampParam:"t",rememberUpgrade:!1,rejectUnauthorized:!0,perMessageDeflate:{threshold:1024},transportOptions:{},closeOnBeforeunload:!0},i),r.opts.path=r.opts.path.replace(/\/$/,"")+"/","string"==typeof r.opts.query&&(r.opts.query=G.decode(r.opts.query)),r.id=null,r.upgrades=null,r.pingInterval=null,r.pingTimeout=null,r.pingTimeoutTimer=null,"function"==typeof addEventListener&&(r.opts.closeOnBeforeunload&&addEventListener("beforeunload",(function(){r.transport&&(r.transport.removeAllListeners(),r.transport.close())}),!1),"localhost"!==r.hostname&&(r.offlineEventListener=function(){r.onClose("transport close")},addEventListener("offline",r.offlineEventListener,!1))),r.open(),r}return r(a,[{key:"createTransport",value:function(t){var e=function(t){var e={};for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e}(this.opts.query);e.EIO=4,e.transport=t,this.id&&(e.sid=this.id);var n=o({},this.opts.transportOptions[t],this.opts,{query:e,socket:this,hostname:this.hostname,secure:this.secure,port:this.port});return new ct[t](n)}},{key:"open",value:function(){var t,e=this;if(this.opts.rememberUpgrade&&a.priorWebsocketSuccess&&-1!==this.transports.indexOf("websocket"))t="websocket";else{if(0===this.transports.length)return void this.setTimeoutFn((function(){e.emitReserved("error","No transports available")}),0);t=this.transports[0]}this.readyState="opening";try{t=this.createTransport(t)}catch(t){return this.transports.shift(),void this.open()}t.open(),this.setTransport(t)}},{key:"setTransport",value:function(t){var e=this;this.transport&&this.transport.removeAllListeners(),this.transport=t,t.on("drain",this.onDrain.bind(this)).on("packet",this.onPacket.bind(this)).on("error",this.onError.bind(this)).on("close",(function(){e.onClose("transport close")}))}},{key:"probe",value:function(t){var e=this,n=this.createTransport(t),r=!1;a.priorWebsocketSuccess=!1;var o=function(){r||(n.send([{type:"ping",data:"probe"}]),n.once("packet",(function(t){if(!r)if("pong"===t.type&&"probe"===t.data){if(e.upgrading=!0,e.emitReserved("upgrading",n),!n)return;a.priorWebsocketSuccess="websocket"===n.name,e.transport.pause((function(){r||"closed"!==e.readyState&&(f(),e.setTransport(n),n.send([{type:"upgrade"}]),e.emitReserved("upgrade",n),n=null,e.upgrading=!1,e.flush())}))}else{var o=new Error("probe error");o.transport=n.name,e.emitReserved("upgradeError",o)}})))};function i(){r||(r=!0,f(),n.close(),n=null)}var s=function(t){var r=new Error("probe error: "+t);r.transport=n.name,i(),e.emitReserved("upgradeError",r)};function c(){s("transport closed")}function u(){s("socket closed")}function h(t){n&&t.name!==n.name&&i()}var f=function(){n.removeListener("open",o),n.removeListener("error",s),n.removeListener("close",c),e.off("close",u),e.off("upgrading",h)};n.once("open",o),n.once("error",s),n.once("close",c),this.once("close",u),this.once("upgrading",h),n.open()}},{key:"onOpen",value:function(){if(this.readyState="open",a.priorWebsocketSuccess="websocket"===this.transport.name,this.emitReserved("open"),this.flush(),"open"===this.readyState&&this.opts.upgrade&&this.transport.pause)for(var t=0,e=this.upgrades.length;t0;case bt.ACK:case bt.BINARY_ACK:return Array.isArray(n)}}}]),a}(R);var Et=function(){function t(n){e(this,t),this.packet=n,this.buffers=[],this.reconPack=n}return r(t,[{key:"takeBinaryData",value:function(t){if(this.buffers.push(t),this.buffers.length===this.reconPack.attachments){var e=gt(this.reconPack,this.buffers);return this.finishedReconstruction(),e}return null}},{key:"finishedReconstruction",value:function(){this.reconPack=null,this.buffers=[]}}]),t}(),At=Object.freeze({__proto__:null,protocol:5,get PacketType(){return bt},Encoder:wt,Decoder:_t});function Rt(t,e,n){return t.on(e,n),function(){t.off(e,n)}}var Tt=Object.freeze({connect:1,connect_error:1,disconnect:1,disconnecting:1,newListener:1,removeListener:1}),Ct=function(t){i(o,t);var n=h(o);function o(t,r,i){var s;return e(this,o),(s=n.call(this)).connected=!1,s.disconnected=!0,s.receiveBuffer=[],s.sendBuffer=[],s.ids=0,s.acks={},s.flags={},s.io=t,s.nsp=r,i&&i.auth&&(s.auth=i.auth),s.io._autoConnect&&s.open(),s}return r(o,[{key:"subEvents",value:function(){if(!this.subs){var t=this.io;this.subs=[Rt(t,"open",this.onopen.bind(this)),Rt(t,"packet",this.onpacket.bind(this)),Rt(t,"error",this.onerror.bind(this)),Rt(t,"close",this.onclose.bind(this))]}}},{key:"active",get:function(){return!!this.subs}},{key:"connect",value:function(){return this.connected||(this.subEvents(),this.io._reconnecting||this.io.open(),"open"===this.io._readyState&&this.onopen()),this}},{key:"open",value:function(){return this.connect()}},{key:"send",value:function(){for(var t=arguments.length,e=new Array(t),n=0;n1?e-1:0),r=1;r0&&t.jitter<=1?t.jitter:0,this.attempts=0}St.prototype.duration=function(){var t=this.ms*Math.pow(this.factor,this.attempts++);if(this.jitter){var e=Math.random(),n=Math.floor(e*this.jitter*t);t=0==(1&Math.floor(10*e))?t-n:t+n}return 0|Math.min(t,this.max)},St.prototype.reset=function(){this.attempts=0},St.prototype.setMin=function(t){this.ms=t},St.prototype.setMax=function(t){this.max=t},St.prototype.setJitter=function(t){this.jitter=t};var Bt=function(n){i(s,n);var o=h(s);function s(n,r){var i,a;e(this,s),(i=o.call(this)).nsps={},i.subs=[],n&&"object"===t(n)&&(r=n,n=void 0),(r=r||{}).path=r.path||"/socket.io",i.opts=r,A(c(i),r),i.reconnection(!1!==r.reconnection),i.reconnectionAttempts(r.reconnectionAttempts||1/0),i.reconnectionDelay(r.reconnectionDelay||1e3),i.reconnectionDelayMax(r.reconnectionDelayMax||5e3),i.randomizationFactor(null!==(a=r.randomizationFactor)&&void 0!==a?a:.5),i.backoff=new Ot({min:i.reconnectionDelay(),max:i.reconnectionDelayMax(),jitter:i.randomizationFactor()}),i.timeout(null==r.timeout?2e4:r.timeout),i._readyState="closed",i.uri=n;var u=r.parser||At;return i.encoder=new u.Encoder,i.decoder=new u.Decoder,i._autoConnect=!1!==r.autoConnect,i._autoConnect&&i.open(),i}return r(s,[{key:"reconnection",value:function(t){return arguments.length?(this._reconnection=!!t,this):this._reconnection}},{key:"reconnectionAttempts",value:function(t){return void 0===t?this._reconnectionAttempts:(this._reconnectionAttempts=t,this)}},{key:"reconnectionDelay",value:function(t){var e;return void 0===t?this._reconnectionDelay:(this._reconnectionDelay=t,null===(e=this.backoff)||void 0===e||e.setMin(t),this)}},{key:"randomizationFactor",value:function(t){var e;return void 0===t?this._randomizationFactor:(this._randomizationFactor=t,null===(e=this.backoff)||void 0===e||e.setJitter(t),this)}},{key:"reconnectionDelayMax",value:function(t){var e;return void 0===t?this._reconnectionDelayMax:(this._reconnectionDelayMax=t,null===(e=this.backoff)||void 0===e||e.setMax(t),this)}},{key:"timeout",value:function(t){return arguments.length?(this._timeout=t,this):this._timeout}},{key:"maybeReconnectOnOpen",value:function(){!this._reconnecting&&this._reconnection&&0===this.backoff.attempts&&this.reconnect()}},{key:"open",value:function(t){var e=this;if(~this._readyState.indexOf("open"))return this;this.engine=new ut(this.uri,this.opts);var n=this.engine,r=this;this._readyState="opening",this.skipReconnect=!1;var o=Rt(n,"open",(function(){r.onopen(),t&&t()})),i=Rt(n,"error",(function(n){r.cleanup(),r._readyState="closed",e.emitReserved("error",n),t?t(n):r.maybeReconnectOnOpen()}));if(!1!==this._timeout){var s=this._timeout;0===s&&o();var a=this.setTimeoutFn((function(){o(),n.close(),n.emit("error",new Error("timeout"))}),s);this.opts.autoUnref&&a.unref(),this.subs.push((function(){clearTimeout(a)}))}return this.subs.push(o),this.subs.push(i),this}},{key:"connect",value:function(t){return this.open(t)}},{key:"onopen",value:function(){this.cleanup(),this._readyState="open",this.emitReserved("open");var t=this.engine;this.subs.push(Rt(t,"ping",this.onping.bind(this)),Rt(t,"data",this.ondata.bind(this)),Rt(t,"error",this.onerror.bind(this)),Rt(t,"close",this.onclose.bind(this)),Rt(this.decoder,"decoded",this.ondecoded.bind(this)))}},{key:"onping",value:function(){this.emitReserved("ping")}},{key:"ondata",value:function(t){this.decoder.add(t)}},{key:"ondecoded",value:function(t){this.emitReserved("packet",t)}},{key:"onerror",value:function(t){this.emitReserved("error",t)}},{key:"socket",value:function(t,e){var n=this.nsps[t];return n||(n=new Ct(this,t,e),this.nsps[t]=n),n}},{key:"_destroy",value:function(t){for(var e=0,n=Object.keys(this.nsps);e=this._reconnectionAttempts)this.backoff.reset(),this.emitReserved("reconnect_failed"),this._reconnecting=!1;else{var n=this.backoff.duration();this._reconnecting=!0;var r=this.setTimeoutFn((function(){e.skipReconnect||(t.emitReserved("reconnect_attempt",e.backoff.attempts),e.skipReconnect||e.open((function(n){n?(e._reconnecting=!1,e.reconnect(),t.emitReserved("reconnect_error",n)):e.onreconnect()})))}),n);this.opts.autoUnref&&r.unref(),this.subs.push((function(){clearTimeout(r)}))}}},{key:"onreconnect",value:function(){var t=this.backoff.attempts;this._reconnecting=!1,this.backoff.reset(),this.emitReserved("reconnect",t)}}]),s}(R),Nt={};function xt(e,n){"object"===t(e)&&(n=e,e=void 0);var r,o=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",n=arguments.length>2?arguments[2]:void 0,r=t;n=n||"undefined"!=typeof location&&location,null==t&&(t=n.protocol+"//"+n.host),"string"==typeof t&&("/"===t.charAt(0)&&(t="/"===t.charAt(1)?n.protocol+t:n.host+t),/^(https?|wss?):\/\//.test(t)||(t=void 0!==n?n.protocol+"//"+t:"https://"+t),r=v(t)),r.port||(/^(http|ws)$/.test(r.protocol)?r.port="80":/^(http|ws)s$/.test(r.protocol)&&(r.port="443")),r.path=r.path||"/";var o=-1!==r.host.indexOf(":")?"["+r.host+"]":r.host;return r.id=r.protocol+"://"+o+":"+r.port+e,r.href=r.protocol+"://"+o+(n&&n.port===r.port?"":":"+r.port),r}(e,(n=n||{}).path||"/socket.io"),i=o.source,s=o.id,a=o.path,c=Nt[s]&&a in Nt[s].nsps;return n.forceNew||n["force new connection"]||!1===n.multiplex||c?r=new Bt(i,n):(Nt[s]||(Nt[s]=new Bt(i,n)),r=Nt[s]),o.query&&!n.query&&(n.query=o.queryKey),r.socket(o.path,n)}return o(xt,{Manager:Bt,Socket:Ct,io:xt,connect:xt}),xt})); +//# sourceMappingURL=socket.io.min.js.map diff --git a/server/static/js/store.js b/server/static/js/store.js index b35fbdf..aee5b0c 100644 --- a/server/static/js/store.js +++ b/server/static/js/store.js @@ -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: [], diff --git a/server/templates/acorn.html b/server/templates/acorn.html index 8946a7a..71dcb64 100644 --- a/server/templates/acorn.html +++ b/server/templates/acorn.html @@ -38,6 +38,7 @@ + diff --git a/server/templates/control-panel.html b/server/templates/control-panel.html index 4d4e6d2..1c729d5 100644 --- a/server/templates/control-panel.html +++ b/server/templates/control-panel.html @@ -1,8 +1,4 @@ - +

Acorn control

diff --git a/server/templates/map.html b/server/templates/map.html index 58a9c39..00199d6 100644 --- a/server/templates/map.html +++ b/server/templates/map.html @@ -1,6 +1,6 @@ -
+
- +
diff --git a/server/templates/robot-cardview.html b/server/templates/robot-cardview.html index 77f1202..c293ed2 100644 --- a/server/templates/robot-cardview.html +++ b/server/templates/robot-cardview.html @@ -1,6 +1,6 @@ diff --git a/vehicle/master_process.py b/vehicle/master_process.py index e98e92e..4e6ddc3 100644 --- a/vehicle/master_process.py +++ b/vehicle/master_process.py @@ -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 @@ -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: diff --git a/vehicle/requirements.txt b/vehicle/requirements.txt index 3d413bc..4fe0cd8 100644 --- a/vehicle/requirements.txt +++ b/vehicle/requirements.txt @@ -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