Commit 54755962 authored by Léo-Paul Géneau's avatar Léo-Paul Géneau 👾

software/js-drone: add operator script

Handle operator script parameter.
parent d86726e4
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
* droneNetIf: Drone network interface used for multicast traffic * droneNetIf: Drone network interface used for multicast traffic
* debug: Must be set to 'true' to send drone logs through OPC-UA * debug: Must be set to 'true' to send drone logs through OPC-UA
* multicastIp: IPv6 of the multicast group of the swarm * multicastIp: IPv6 of the multicast group of the swarm
* operatorScript: URL of operator's script to prepare the flight
* mapJson: URL of terrain's map used by the operator script
* flightScript: URL of user's script to execute to fly drone swarm * flightScript: URL of user's script to execute to fly drone swarm
* loopPeriod: Minimal period (in milliseconds) between 2 executions of the flight script loop * loopPeriod: Minimal period (in milliseconds) between 2 executions of the flight script loop
* subscriberGuidList: List of computer id on which a GUI must be deployed * subscriberGuidList: List of computer id on which a GUI must be deployed
...@@ -54,7 +56,7 @@ For each drone is displayed: ...@@ -54,7 +56,7 @@ For each drone is displayed:
### Buttons ### Buttons
* Start: sends a "start" message to the swarm and changes into a stop button * Start: loads operator script and changes into a stop button
* Stop: sends a "stop" message to the swarm * Stop: sends a "stop" message to the swarm
* Switch leader: sends a "switch" message to the swarm, it is usually used to change the leader * Switch leader: sends a "switch" message to the swarm, it is usually used to change the leader
* Quit: exits (closes websocket and stops pub/sub) * Quit: exits (closes websocket and stops pub/sub)
......
...@@ -22,19 +22,19 @@ md5sum = 01425a1c77e79788e1948398b9136724 ...@@ -22,19 +22,19 @@ md5sum = 01425a1c77e79788e1948398b9136724
[instance-profile] [instance-profile]
filename = instance.cfg.in filename = instance.cfg.in
md5sum = b9f2301782bee12f9ff257a8f0a192f5 md5sum = 44d78f9ee8bb4475d521bce8694cc963
[instance-root] [instance-root]
filename = instance-root.cfg.jinja2 filename = instance-root.cfg.jinja2
md5sum = ed319ac31addb65842e38d5b20d2d8e7 md5sum = 2bec05e86d88d27c3c672a6be9a0d0b4
[instance-subscriber] [instance-subscriber]
filename = instance-subscriber.cfg.in filename = instance-subscriber.cfg.in
md5sum = 8559dc8c95e9232060be6db3e0af4379 md5sum = 9418396fc75e820b9dd5e913855aafe2
[main] [main]
_update_hash_filename_ = drone-scripts/main.js.jinja2 _update_hash_filename_ = drone-scripts/main.js.jinja2
md5sum = 85135842e42a1f48a474467508723c0c md5sum = 4d13e382ba6462e19b67aa67207324dc
[pubsub] [pubsub]
_update_hash_filename_ = drone-scripts/pubsub.js.jinja2 _update_hash_filename_ = drone-scripts/pubsub.js.jinja2
...@@ -46,4 +46,4 @@ md5sum = c3858b5ec7373a0932fcda911a8177b5 ...@@ -46,4 +46,4 @@ md5sum = c3858b5ec7373a0932fcda911a8177b5
[worker] [worker]
_update_hash_filename_ = drone-scripts/worker.js.jinja2 _update_hash_filename_ = drone-scripts/worker.js.jinja2
md5sum = f7f94c4e323a84796977f139b8ab4b9b md5sum = 4624b7a0a71c6abe6086b0c628074498
...@@ -39,11 +39,11 @@ import { err, exit, open, out } from "std"; ...@@ -39,11 +39,11 @@ import { err, exit, open, out } from "std";
(function (arm, console, dup2, err, exec, exit, kill, open, out, pipe, read, (function (arm, console, dup2, err, exec, exit, kill, open, out, pipe, read,
scriptArgs, setLog, setReadHandler, setTimeout, signal, start, stop, scriptArgs, setLog, setReadHandler, setTimeout, signal, start, stop,
stopPubsub, waitpid, Worker, SIGINT, SIGTERM, WNOHANG) { stopPubsub, waitpid, Worker, SIGINT, SIGTERM, WNOHANG) {
{% else %} {% else -%}
(function (console, dup2, err, exec, exit, kill, open, out, pipe, read, (function (console, dup2, err, exec, exit, kill, open, out, pipe, read,
scriptArgs, setReadHandler, setTimeout, signal, stopPubsub, waitpid, scriptArgs, setReadHandler, setTimeout, signal, stopPubsub, waitpid,
Worker, SIGINT, SIGTERM, WNOHANG) { Worker, SIGINT, SIGTERM, WNOHANG) {
{% endif %} {% endif -%}
"use strict"; "use strict";
var CONF_PATH = "{{ configuration }}", var CONF_PATH = "{{ configuration }}",
...@@ -61,6 +61,10 @@ import { err, exit, open, out } from "std"; ...@@ -61,6 +61,10 @@ import { err, exit, open, out } from "std";
tail_pid, tail_pid,
worker, worker,
user_script = scriptArgs[1], user_script = scriptArgs[1],
{% if not isADrone -%}
operator_script = scriptArgs[2],
map_json = scriptArgs[3],
{% endif -%}
LOOP_EXECUTION_PERIOD = configuration.loopPeriod, LOOP_EXECUTION_PERIOD = configuration.loopPeriod,
previous_timestamp, previous_timestamp,
can_update = false; can_update = false;
...@@ -99,9 +103,9 @@ import { err, exit, open, out } from "std"; ...@@ -99,9 +103,9 @@ import { err, exit, open, out } from "std";
function quit(exit_code) { function quit(exit_code) {
worker.onmessage = null; worker.onmessage = null;
stopPubsub(); stopPubsub();
{% if isADrone %} {% if isADrone -%}
stop(); stop();
{% endif %} {% endif -%}
QUICKJS_LOG_FILE.close(); QUICKJS_LOG_FILE.close();
if (log_r_pipe) { if (log_r_pipe) {
setReadHandler(log_r_pipe[0], null); setReadHandler(log_r_pipe[0], null);
...@@ -128,7 +132,7 @@ import { err, exit, open, out } from "std"; ...@@ -128,7 +132,7 @@ import { err, exit, open, out } from "std";
} }
} }
{% if isADrone %} {% if isADrone -%}
function connect() { function connect() {
var address = configuration.autopilotIp + ":" + configuration.autopilotPort; var address = configuration.autopilotIp + ":" + configuration.autopilotPort;
console.log("Will connect to", address); console.log("Will connect to", address);
...@@ -145,7 +149,7 @@ import { err, exit, open, out } from "std"; ...@@ -145,7 +149,7 @@ import { err, exit, open, out } from "std";
console.log("Connecting to aupilot\n"); console.log("Connecting to aupilot\n");
connect(); connect();
{% endif %} {% endif -%}
pubsubWorker = new Worker("{{ pubsub_script }}"); pubsubWorker = new Worker("{{ pubsub_script }}");
pubsubWorker.onmessage = function (e) { pubsubWorker.onmessage = function (e) {
...@@ -157,9 +161,9 @@ import { err, exit, open, out } from "std"; ...@@ -157,9 +161,9 @@ import { err, exit, open, out } from "std";
worker.postMessage({type: "initPubsub"}); worker.postMessage({type: "initPubsub"});
function load() { function load() {
{% if isADrone %} {% if isADrone -%}
exitOnFail(arm(), "Failed to arm"); exitOnFail(arm(), "Failed to arm");
{% endif %} {% endif -%}
// First argument must provide the user script path // First argument must provide the user script path
if (user_script === undefined) { if (user_script === undefined) {
...@@ -169,7 +173,11 @@ import { err, exit, open, out } from "std"; ...@@ -169,7 +173,11 @@ import { err, exit, open, out } from "std";
worker.postMessage({ worker.postMessage({
type: "load", type: "load",
path: user_script {% if not isADrone -%}
operator_path: operator_script,
map_path: map_json,
{% endif -%}
user_path: user_script
}); });
} }
......
...@@ -94,17 +94,19 @@ import { evalScript, fdopen, loadFile, open } from "std"; ...@@ -94,17 +94,19 @@ import { evalScript, fdopen, loadFile, open } from "std";
setTargetCoordinates: setTargetCoordinates, setTargetCoordinates: setTargetCoordinates,
takeOff: takeOff, takeOff: takeOff,
{% endif -%} {% endif -%}
sendMsg: function (msg, id) { sendMsg: sendMsg
if (id === undefined) { id = -1; }
setMessage(JSON.stringify({
content: msg,
timestamp: Date.now(),
dest_id: id
}));
}
}; };
conf_file.close(); conf_file.close();
function sendMsg(msg, id) {
if (id === undefined) { id = -1; }
setMessage(JSON.stringify({
content: msg,
timestamp: Date.now(),
dest_id: id
}));
}
function exitWorker(exit_code) { function exitWorker(exit_code) {
if (user_me.hasOwnProperty("onWebSocketMessage")) { if (user_me.hasOwnProperty("onWebSocketMessage")) {
stopGwsocket(); stopGwsocket();
...@@ -192,12 +194,17 @@ import { evalScript, fdopen, loadFile, open } from "std"; ...@@ -192,12 +194,17 @@ import { evalScript, fdopen, loadFile, open } from "std";
waitpid(gwsocket_pid, WNOHANG); waitpid(gwsocket_pid, WNOHANG);
} }
function loadUserScript(path) { function getFileContent(path) {
var script_content = loadFile(path); var script_content = loadFile(path);
if (script_content === null) { if (script_content === null) {
console.log("Failed to load user script " + path); console.log("Failed to load script", path);
exitWorker(1); exitWorker(1);
} }
return script_content;
}
function loadUserScript(path) {
var script_content = getFileContent(path);
try { try {
evalScript( evalScript(
"function execUserScript(from, me) {" + script_content + "};" "function execUserScript(from, me) {" + script_content + "};"
...@@ -218,6 +225,22 @@ import { evalScript, fdopen, loadFile, open } from "std"; ...@@ -218,6 +225,22 @@ import { evalScript, fdopen, loadFile, open } from "std";
} }
} }
function loadOperatorScript(operator_path, map_path) {
var script_content = getFileContent(operator_path),
operator = {
getMapJSON: function () { return JSON.parse(getFileContent(map_path)); },
sendMsg: sendMsg
};
try {
evalScript(
"function execOperatorScript(operator) {" + script_content + "};"
);
user_me.execOperatorScript = execOperatorScript.bind(null, operator);
} catch (e) {
console.log("Failed to evaluate operator script", e);
}
}
function handleMainMessage(evt) { function handleMainMessage(evt) {
var type = evt.data.type, message, parsed_message, peer_id, log; var type = evt.data.type, message, parsed_message, peer_id, log;
...@@ -238,7 +261,10 @@ import { evalScript, fdopen, loadFile, open } from "std"; ...@@ -238,7 +261,10 @@ import { evalScript, fdopen, loadFile, open } from "std";
break; break;
case "load": case "load":
loadUserScript(evt.data.path); loadUserScript(evt.data.user_path);
if (evt.data.hasOwnProperty("operator_path") && evt.data.hasOwnProperty("map_path")) {
loadOperatorScript(evt.data.operator_path, evt.data.map_path);
}
parent.postMessage({type: "loaded"}); parent.postMessage({type: "loaded"});
break; break;
......
...@@ -50,11 +50,23 @@ ...@@ -50,11 +50,23 @@
"type": "string", "type": "string",
"default": "ff15::1111" "default": "ff15::1111"
}, },
"operatorScript": {
"title": "Script's URL to prepare the flight",
"description": "URL of the operator script. The operator script is used to send a first message to the swarm.\nThis can be useful to determine some path planning depending on map informations.\nThis URL must be publicly accesible so that the drone can fetch the script.",
"type": "string",
"default": "https://lab.nexedi.com/nexedi/flight-scripts/-/raw/operator_script/operator.js"
},
"mapJson": {
"title": "Map's URL",
"description": "URL of the terrain's map. It is used as a data source by the operator script.",
"type": "string",
"default": "https://lab.nexedi.com/nexedi/flight-scripts/-/raw/operator_script/map.json"
},
"flightScript": { "flightScript": {
"title": "Script's URL of the flight", "title": "Script's URL of the flight",
"description": "URL of the script which will be executed for the flight. This URL must be publicly accesible so that the drone can fetch the script.", "description": "URL of the script which will be executed for the flight. This URL must be publicly accesible so that the drone can fetch the script.",
"type": "string", "type": "string",
"default": "https://lab.nexedi.com/nexedi/flight-scripts/-/raw/v2.0/default.js" "default": "https://lab.nexedi.com/nexedi/flight-scripts/-/raw/operator_script/default.js"
}, },
"loopPeriod": { "loopPeriod": {
"title": "Loop execution period", "title": "Loop execution period",
......
...@@ -35,7 +35,9 @@ config-netIf = {{ parameter_dict['droneNetIf'] }} ...@@ -35,7 +35,9 @@ config-netIf = {{ parameter_dict['droneNetIf'] }}
{% else -%} {% else -%}
{% do subscriber_id_list.append(id) %} {% do subscriber_id_list.append(id) %}
config-isADrone = {{ dumps(False) }} config-isADrone = {{ dumps(False) }}
config-flightScript = https://lab.nexedi.com/nexedi/flight-scripts/-/raw/v2.0/subscribe.js config-operatorScript = {{ parameter_dict['operatorScript'] }}
config-mapJson = {{ parameter_dict['mapJson'] }}
config-flightScript = https://lab.nexedi.com/nexedi/flight-scripts/-/raw/operator_script/subscribe.js
config-netIf = {{ parameter_dict['subscriberNetIf'] }} config-netIf = {{ parameter_dict['subscriberNetIf'] }}
{% endif -%} {% endif -%}
config-multicastIp = {{ parameter_dict['multicastIp'] }} config-multicastIp = {{ parameter_dict['multicastIp'] }}
......
...@@ -20,6 +20,21 @@ inline = ...@@ -20,6 +20,21 @@ inline =
{% do parameter_dict.__setitem__('websocketPort', websocket_port) -%} {% do parameter_dict.__setitem__('websocketPort', websocket_port) -%}
{{ json_module.dumps(parameter_dict) }} {{ json_module.dumps(parameter_dict) }}
[operator]
recipe = slapos.recipe.build:download
url = $${slap-configuration:configuration.operatorScript}
destination = $${directory:etc}/operator.js
offline = false
[map]
recipe = slapos.recipe.build:download
url = $${slap-configuration:configuration.mapJson}
destination = $${directory:etc}/map.json
offline = false
[qjs-launcher]
command-line += $${operator:target} $${map:target}
[script-js] [script-js]
recipe = slapos.recipe.template:jinja2 recipe = slapos.recipe.template:jinja2
template = ${script-js:target} template = ${script-js:target}
......
...@@ -37,7 +37,9 @@ default-parameters = ...@@ -37,7 +37,9 @@ default-parameters =
"debug": false, "debug": false,
"droneGuidList": [], "droneGuidList": [],
"droneNetIf": "eth0", "droneNetIf": "eth0",
"flightScript": "https://lab.nexedi.com/nexedi/flight-scripts/-/raw/v2.0/default.js", "operatorScript": "https://lab.nexedi.com/nexedi/flight-scripts/-/raw/operator_script/operator.js",
"mapJson": "https://lab.nexedi.com/nexedi/flight-scripts/-/raw/operator_script/map.json",
"flightScript": "https://lab.nexedi.com/nexedi/flight-scripts/-/raw/operator_script/default.js",
"loopPeriod": 200, "loopPeriod": 200,
"multicastIp": "ff15::1111", "multicastIp": "ff15::1111",
"subscriberGuidList":[], "subscriberGuidList":[],
......
...@@ -282,7 +282,9 @@ class SubscriberTestCase(SlapOSInstanceTestCase): ...@@ -282,7 +282,9 @@ class SubscriberTestCase(SlapOSInstanceTestCase):
'debug': False, 'debug': False,
'loopPeriod': LOOP_PERIOD, 'loopPeriod': LOOP_PERIOD,
'isADrone': False, 'isADrone': False,
'flightScript': 'https://lab.nexedi.com/nexedi/flight-scripts/-/raw/v2.0/subscribe.js', 'operatorScript': 'https://lab.nexedi.com/nexedi/flight-scripts/-/raw/operator_script/operator.js',
'mapJson': 'https://lab.nexedi.com/nexedi/flight-scripts/-/raw/operator_script/map.json',
'flightScript': 'https://lab.nexedi.com/nexedi/flight-scripts/-/raw/operator_script/subscribe.js',
'netIf': OPC_UA_NET_IF, 'netIf': OPC_UA_NET_IF,
'multicastIp': MCAST_GRP 'multicastIp': MCAST_GRP
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment