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

component/qjs-wrapper: v↑ qjs-wrapper (2.1 -> 2.2)

parent 18be4815
......@@ -9,7 +9,7 @@ parts = qjs-wrapper
[qjs-wrapper-source]
recipe = slapos.recipe.build:gitclone
repository = https://lab.nexedi.com/nexedi/qjs-wrapper.git
revision = v2.1
revision = v2.2
git-executable = ${git:location}/bin/git
[qjs-wrapper]
......
......@@ -14,7 +14,6 @@
* autopilotIp: IPv4 address to identify the autopilot from the companion board
* droneGuidList: List of computer id on which flight script must be deployed
* droneNetIf: Drone network interface used for multicast traffic
* isASimulation: Must be set to 'true' to automatically take off during simulation
* debug: Must be set to 'true' to send drone logs through OPC-UA
* multicastIp: IPv6 of the multicast group of the swarm
* flightScript: URL of user's script to execute to fly drone swarm
......
......@@ -22,11 +22,11 @@ md5sum = 01425a1c77e79788e1948398b9136724
[instance-profile]
filename = instance.cfg.in
md5sum = 4733c63573e6812c124b356dc146ffcc
md5sum = b9f2301782bee12f9ff257a8f0a192f5
[instance-root]
filename = instance-root.cfg.jinja2
md5sum = 316f77c655540226f22dc7a6322624f1
md5sum = ed319ac31addb65842e38d5b20d2d8e7
[instance-subscriber]
filename = instance-subscriber.cfg.in
......@@ -34,7 +34,7 @@ md5sum = 8559dc8c95e9232060be6db3e0af4379
[main]
_update_hash_filename_ = drone-scripts/main.js.jinja2
md5sum = 60146505ec8ea50d881d033f63b6725c
md5sum = 85135842e42a1f48a474467508723c0c
[pubsub]
_update_hash_filename_ = drone-scripts/pubsub.js.jinja2
......@@ -42,8 +42,8 @@ md5sum = 34a02101a607e60f4e422375beaf7fc2
[script-js]
_update_hash_filename_ = web-gui/script.js.jinja2
md5sum = efd986b3685e50f73c17c9352804bae0
md5sum = c3858b5ec7373a0932fcda911a8177b5
[worker]
_update_hash_filename_ = drone-scripts/worker.js.jinja2
md5sum = 5fc7f9738d8230aeea9a9d25ea30f3f0
md5sum = f7f94c4e323a84796977f139b8ab4b9b
/*jslint nomen: true, indent: 2, maxerr: 3, maxlen: 80 */
{% if isADrone -%}
/*global arm, console, close, dup2, exit, open, scriptArgs, setTimeout, start,
stop, stopPubsub, takeOffAndWait, Worker, SIGINT, SIGTERM*/
/*global arm, console, dup2, err, exec, exit, kill, open, out, pipe, read,
scriptArgs, setLog, setReadHandler, setTimeout, signal, start, stop,
stopPubsub, waitpid, ArrayBuffer, Uint8Array, Worker, SIGINT, SIGTERM,
WNOHANG*/
{% else -%}
/*global console, close, dup2, exit, open, scriptArgs, setTimeout, stopPubsub,
Worker, SIGINT, SIGTERM*/
/*global console, dup2, err, exec, exit, kill, open, out, pipe, read,
scriptArgs, setReadHandler, setTimeout, signal, stopPubsub, waitpid,
ArrayBuffer, Uint8Array, Worker, SIGINT, SIGTERM, WNOHANG*/
{% endif -%}
import {
{% if isADrone -%}
arm,
setLog,
start,
stop,
{% endif -%}
stopPubsub,
{% if isADrone -%}
takeOffAndWait
{% endif -%}
} from "{{ qjs_wrapper }}";
import {
Worker,
SIGINT,
SIGTERM,
WNOHANG,
dup2,
exec,
kill,
pipe,
read,
setReadHandler,
setTimeout,
signal
signal,
waitpid
} from "os";
import { err, exit, open, out } from "std";
{% if isADrone -%}
(function (arm, console, dup2, err, exit, open, out, scriptArgs, setTimeout,
start, stop, stopPubsub, takeOffAndWait, Worker, SIGINT, SIGTERM) {
{% else -%}
(function (console, dup2, err, exit, open, out, scriptArgs, setTimeout,
stopPubsub, Worker, SIGINT, SIGTERM) {
{% endif -%}
(function (arm, console, dup2, err, exec, exit, kill, open, out, pipe, read,
scriptArgs, setLog, setReadHandler, setTimeout, signal, start, stop,
stopPubsub, waitpid, Worker, SIGINT, SIGTERM, WNOHANG) {
{% else %}
(function (console, dup2, err, exec, exit, kill, open, out, pipe, read,
scriptArgs, setReadHandler, setTimeout, signal, stopPubsub, waitpid,
Worker, SIGINT, SIGTERM, WNOHANG) {
{% endif %}
"use strict";
var CONF_PATH = "{{ configuration }}",
......@@ -42,11 +52,13 @@ import { err, exit, open, out } from "std";
AUTOPILOT_CONNECTION_TIMEOUT = 5,
MAVSDK_LOG_FILE_PATH =
"{{ log_dir }}/mavsdk_" + new Date().toISOString() + ".log",
MAX_STR_SIZE = 1024,
QUICKJS_LOG_FILE_PATH =
"{{ log_dir }}/quickjs_" + new Date().toISOString() + ".log",
QUICKJS_LOG_FILE =
open(QUICKJS_LOG_FILE_PATH, "w"),
QUICKJS_LOG_FILE = open(QUICKJS_LOG_FILE_PATH, "w"),
log_r_pipe,
pubsubWorker,
tail_pid,
worker,
user_script = scriptArgs[1],
LOOP_EXECUTION_PERIOD = configuration.loopPeriod,
......@@ -58,6 +70,23 @@ import { err, exit, open, out } from "std";
// redirect stdout and stderr
dup2(QUICKJS_LOG_FILE.fileno(), out.fileno());
dup2(QUICKJS_LOG_FILE.fileno(), err.fileno());
if (configuration.debug && configuration.isADrone) {
log_r_pipe = pipe();
tail_pid = exec([
"tail",
"-f",
QUICKJS_LOG_FILE_PATH
], {
block: false,
stdout: log_r_pipe[1]
});
setReadHandler(log_r_pipe[0], function () {
var data = new ArrayBuffer(MAX_STR_SIZE);
if (read(log_r_pipe[0], data, 0, MAX_STR_SIZE) > 0) {
setLog(String.fromCharCode.apply(null, new Uint8Array(data)));
}
});
}
// Use a Worker to ensure the user script
// does not block the main script
......@@ -70,10 +99,15 @@ import { err, exit, open, out } from "std";
function quit(exit_code) {
worker.onmessage = null;
stopPubsub();
{% if isADrone -%}
{% if isADrone %}
stop();
{% endif -%}
{% endif %}
QUICKJS_LOG_FILE.close();
if (log_r_pipe) {
setReadHandler(log_r_pipe[0], null);
kill(tail_pid, SIGTERM);
waitpid(tail_pid, WNOHANG);
}
exit(exit_code);
}
......@@ -94,7 +128,7 @@ import { err, exit, open, out } from "std";
}
}
{% if isADrone -%}
{% if isADrone %}
function connect() {
var address = configuration.autopilotIp + ":" + configuration.autopilotPort;
console.log("Will connect to", address);
......@@ -103,9 +137,7 @@ import { err, exit, open, out } from "std";
configuration.autopilotIp,
configuration.autopilotPort,
MAVSDK_LOG_FILE_PATH,
QUICKJS_LOG_FILE_PATH,
AUTOPILOT_CONNECTION_TIMEOUT,
configuration.debug
AUTOPILOT_CONNECTION_TIMEOUT
),
"Failed to connect to " + address
);
......@@ -113,7 +145,7 @@ import { err, exit, open, out } from "std";
console.log("Connecting to aupilot\n");
connect();
{% endif -%}
{% endif %}
pubsubWorker = new Worker("{{ pubsub_script }}");
pubsubWorker.onmessage = function (e) {
......@@ -124,19 +156,10 @@ import { err, exit, open, out } from "std";
worker.postMessage({type: "initPubsub"});
{% if isADrone -%}
function takeOff() {
exitOnFail(arm(), "Failed to arm");
takeOffAndWait();
}
{% endif -%}
function load() {
{% if isADrone -%}
if (configuration.isASimulation) {
takeOff();
}
{% endif -%}
{% if isADrone %}
exitOnFail(arm(), "Failed to arm");
{% endif %}
// First argument must provide the user script path
if (user_script === undefined) {
......@@ -192,9 +215,9 @@ import { err, exit, open, out } from "std";
// Start the update loop
loop();
} else if (type === 'updated') {
can_update = true;
err.flush();
out.flush();
can_update = true;
} else if (type === 'exited') {
worker.onmessage = null;
quit(e.data.exit);
......@@ -204,9 +227,11 @@ import { err, exit, open, out } from "std";
}
};
{% if isADrone -%}
}(arm, console, dup2, err, exit, open, out, scriptArgs, setTimeout, start, stop,
stopPubsub, takeOffAndWait, Worker, SIGINT, SIGTERM));
}(arm, console, dup2, err, exec, exit, kill, open, out, pipe, read, scriptArgs,
setLog, setReadHandler, setTimeout, signal, start, stop, stopPubsub, waitpid,
Worker, SIGINT, SIGTERM, WNOHANG));
{% else -%}
}(console, dup2, err, exit, open, out, scriptArgs, setTimeout, stopPubsub,
Worker, SIGINT, SIGTERM));
}(console, dup2, err, exec, exit, kill, open, out, pipe, read, scriptArgs,
setReadHandler, setTimeout, signal, stopPubsub, waitpid, Worker, SIGINT,
SIGTERM, WNOHANG));
{% endif -%}
/*jslint nomen: true, indent: 2, maxerr: 3, maxlen: 80 */
/*global console, getAltitude, getAltitudeRel, getInitialAltitude, gpsIsOk,
getLatitude, getLongitude, getYaw, execUserScript, initPubsub, loiter,
setAirSpeed, setMessage, setTargetCoordinates, std, triggerParachute,
updateLogAndProjection, Drone, Worker*/
{% if isADrone -%}
/*global console, execUserScript, getAltitude, getClimbRate, getInitialAltitude,
gpsIsOk, getPosition, getSpeed, getYaw, initPubsub, isLanding, isReadyToFly,
land, loiter, setMessage, setTargetCoordinates, std, takeOff,
updateLogAndProjection, ArrayBuffer, Drone, Uint8Array, Worker*/
{% else -%}
/*global console, execUserScript, initPubsub, setMessage, setTargetCoordinates,
std, updateLogAndProjection, Drone, Worker*/
{% endif -%}
import {
Drone,
{% if isADrone -%}
triggerParachute,
getAirspeed,
getAltitude,
getClimbRate,
getInitialAltitude,
gpsIsOk,
getPosition,
getSpeed,
getYaw,
{% endif -%}
initPubsub,
{% if isADrone -%}
isLanding,
isReadyToFly,
land,
loiter,
setAirSpeed,
{% endif -%}
setMessage,
{% if isADrone -%}
setTargetCoordinates,
takeOff,
updateLogAndProjection
{% endif -%}
} from "{{ qjs_wrapper }}";
......@@ -44,8 +50,8 @@ import { evalScript, fdopen, loadFile, open } from "std";
(function (Drone, SIGTERM, WNOHANG, Worker, close, console, evalScript, exec,
fdopen, getAltitude, getInitialAltitude, gpsIsOk, getPosition,
getYaw, initPubsub, isLanding, kill, loadFile, loiter, open,
pipe, setAirSpeed, setMessage, setReadHandler, setTargetCoordinates,
triggerParachute, updateLogAndProjection, waitpid) {
pipe, setMessage, setReadHandler, setTargetCoordinates,
land, updateLogAndProjection, waitpid) {
{% else -%}
(function (Drone, SIGTERM, WNOHANG, Worker, close, console, evalScript, exec,
fdopen, initPubsub, kill, loadFile, open, pipe, setMessage,
......@@ -63,32 +69,30 @@ import { evalScript, fdopen, loadFile, open } from "std";
gwsocket_r_pipe_fd,
gwsocket_w_pipe_fd,
handleWebSocketMessage,
last_message_timestamp = 0,
last_log_timestamp = 0,
last_message_timestamp_list = [],
parent = Worker.parent,
peer_dict = {},
user_me = {
//required to fly
{% if isADrone -%}
triggerParachute: triggerParachute,
{% endif -%}
exit: exitWorker,
getDroneDict: function () { return drone_dict; },
{% if isADrone -%}
getAltitudeAbs: getAltitude,
getClimbRate: getClimbRate,
getCurrentPosition: getPosition,
getInitialAltitude: getInitialAltitude,
gpsIsOk: gpsIsOk,
getSpeed: getSpeed,
getYaw: getYaw,
getSpeed: getAirspeed,
getClimbRate: getClimbRate,
{% endif -%}
id: configuration.id,
{% if isADrone -%}
isLanding: isLanding,
isReadyToFly: isReadyToFly,
land: land,
loiter: loiter,
setAirSpeed: setAirSpeed,
setTargetCoordinates: setTargetCoordinates,
takeOff: takeOff,
{% endif -%}
sendMsg: function (msg, id) {
if (id === undefined) { id = -1; }
......@@ -172,11 +176,11 @@ import { evalScript, fdopen, loadFile, open } from "std";
user_me.writeWebsocketMessage = function (message) {
var buf = new ArrayBuffer(message.length);
var bufView = new Uint8Array(buf);
for (var i=0; i<message.length; i++) {
for (var i = 0; i < message.length; i++) {
bufView[i] = message.charCodeAt(i);
}
writeMessage(gwsocket_w_pipe_fd, {client: clientId, type: 1, data: buf});
}
};
setReadHandler(gwsocket_r_pipe[0], handleWebSocketMessage);
}
......@@ -215,18 +219,20 @@ import { evalScript, fdopen, loadFile, open } from "std";
}
function handleMainMessage(evt) {
var type = evt.data.type, message, peer_id, log;
var type = evt.data.type, message, parsed_message, peer_id, log;
switch (type) {
case "initPubsub":
initPubsub(configuration.numberOfDrones, configuration.numberOfSubscribers);
initPubsub(configuration.numberOfDrones,
configuration.numberOfSubscribers);
for (peer_id = 0; peer_id < configuration.numberOfDrones + configuration.numberOfSubscribers; peer_id++) {
peer_dict[peer_id] = new Drone(peer_id);
peer_dict[peer_id].init(peer_id);
if (peer_id < configuration.numberOfDrones) {
drone_dict[peer_id] = peer_dict[peer_id];
}
last_message_timestamp_list[peer_id] = 0;
}
parent.postMessage({type: "initialized"});
break;
......@@ -239,13 +245,17 @@ import { evalScript, fdopen, loadFile, open } from "std";
case "update":
Object.entries(peer_dict).forEach(function ([id, peer]) {
message = peer.message;
if (user_me.id !== Number(id) && message.length > 0) {
message = JSON.parse(message);
if (message.timestamp != last_message_timestamp &&
user_me.hasOwnProperty("onGetMsg") &&
[-1, user_me.id].includes(message.dest_id)) {
last_message_timestamp = message.timestamp;
user_me.onGetMsg(message.content);
if (message.length > 0) {
parsed_message = JSON.parse(message);
while (parsed_message.timestamp !== last_message_timestamp_list[id]) {
if (user_me.hasOwnProperty("onGetMsg")
&& [-1, user_me.id].includes(parsed_message.dest_id)) {
console.log("running on getMsg");
user_me.onGetMsg(parsed_message.content);
}
last_message_timestamp_list[id] = parsed_message.timestamp;
parsed_message = JSON.parse(peer.message);
}
}
});
......@@ -259,7 +269,7 @@ import { evalScript, fdopen, loadFile, open } from "std";
{% if isADrone -%}
updateLogAndProjection();
{% endif -%}
last_log_timestamp = evt.data.timestamp;
last_log_timestamp = evt.data.timestamp;
}
parent.postMessage({type: "updated"});
......@@ -271,7 +281,7 @@ import { evalScript, fdopen, loadFile, open } from "std";
default:
throw new Error("Unsupported message type", type);
};
}
}
parent.onmessage = function (evt) {
......@@ -287,8 +297,8 @@ import { evalScript, fdopen, loadFile, open } from "std";
{% if isADrone -%}
}(Drone, SIGTERM, WNOHANG, Worker, close, console, evalScript, exec,
fdopen, getAltitude, getInitialAltitude, gpsIsOk, getPosition, getYaw,
initPubsub, isLanding, kill, loadFile, loiter, open, pipe, setAirSpeed,
setMessage, setReadHandler, setTargetCoordinates, triggerParachute,
initPubsub, isLanding, kill, loadFile, loiter, open, pipe, setMessage,
setReadHandler, setTargetCoordinates, land,
updateLogAndProjection, waitpid));
{% else -%}
}(Drone, SIGTERM, WNOHANG, Worker, close, console, evalScript, exec,
......
......@@ -38,12 +38,6 @@
"type": "string",
"default": "eth0"
},
"isASimulation": {
"title": "Set the flight as a simulation",
"description": "The option used to determine if the flight is real or if it is a simulation. This affects the context of the flight (e.g. if the take off is manual or automatic).",
"type": "boolean",
"default": false
},
"debug": {
"title": "Set debug mode",
"description": "When debug mode is enabled, drone are publishing the script logs through OPC-UA.",
......
......@@ -25,7 +25,6 @@ config-autopilotPort = {{ dumps(parameter_dict['autopilotPort']) }}
config-numberOfDrones = {{ dumps(len(parameter_dict['droneGuidList'])) }}
config-numberOfSubscribers = {{ dumps(len(parameter_dict['subscriberGuidList'])) }}
config-id = {{ dumps(id) }}
config-isASimulation = {{ dumps(parameter_dict['isASimulation']) }}
config-debug = {{ dumps(parameter_dict['debug']) }}
config-loopPeriod = {{ dumps(parameter_dict['loopPeriod']) }}
{% if id < len(parameter_dict['droneGuidList']) -%}
......
......@@ -38,7 +38,6 @@ default-parameters =
"droneGuidList": [],
"droneNetIf": "eth0",
"flightScript": "https://lab.nexedi.com/nexedi/flight-scripts/-/raw/v2.0/default.js",
"isASimulation": false,
"loopPeriod": 200,
"multicastIp": "ff15::1111",
"subscriberGuidList":[],
......
......@@ -17,8 +17,8 @@ pre-configure +=
[c-astral-wrapper]
recipe = slapos.recipe.cmmi
configure-command = true
url = https://lab.nexedi.com/nexedi/c-astral-wrapper/-/archive/v2.1/c-astral-wrapper-v2.1.tar.gz
md5sum = cca66724e1b7a61c1b9559fde95c420b
url = https://lab.nexedi.com/nexedi/c-astral-wrapper/-/archive/v2.2/c-astral-wrapper-v2.2.tar.gz
md5sum = 08cb41102f87475af0b6ec19e0c2965b
environment =
CPLUS_INCLUDE_PATH=${qjs-wrapper-source:location}/include:${mavsdk:location}/include:${mavsdk:location}/include/mavsdk
LDFLAGS=-L${mavsdk:location}/lib -Wl,-rpath=${mavsdk:location}/lib
......
......@@ -5,7 +5,7 @@ extends =
[sqdr-source]
recipe = slapos.recipe.build:gitclone
repository = https://lab.nexedi.com/slaposdrone/squadrone.git
revision = v2.1
revision = v2.2
git-executable = ${git:location}/bin/git
[sqdr-wrapper]
......
......@@ -38,16 +38,16 @@ from websocket import create_connection
from slapos.testing.testcase import installSoftwareUrlList, makeModuleSetUpAndTestCaseClass
'''
0. positionArray
0.1 latitude
0.2 longitude
0.3 absolute altitude
0.4 relative altitude
1. speedArray
1.1 yaw angle
1.2 air speed
1.3 climb rate
2. message
0. message
1. positionArray
1.1 latitude
1.2 longitude
1.3 absolute altitude
1.4 relative altitude
2. speedArray
2.1 yaw angle
2.2 air speed
2.3 climb rate
3. log
'''
MONITORED_ITEM_NB = 4
......@@ -231,6 +231,7 @@ class SubscriberTestCase(SlapOSInstanceTestCase):
def ua_dataSetMessage_encode(self):
data_set_message = self.ua_dataSetMessageHeader_encode()
data_set_message += struct.pack('H', MONITORED_ITEM_NB)
data_set_message += self.ua_string_encode(TEST_MESSAGE)
data_set_message += self.ua_array_encode(
POSITION_ARRAY_TYPE,
'q',
......@@ -241,7 +242,6 @@ class SubscriberTestCase(SlapOSInstanceTestCase):
'f',
SPEED_ARRAY_VALUES,
)
data_set_message += self.ua_string_encode(TEST_MESSAGE)
data_set_message += self.ua_string_encode(LOG)
return data_set_message
......@@ -280,7 +280,6 @@ class SubscriberTestCase(SlapOSInstanceTestCase):
'numberOfSubscribers': 1,
'id': 1,
'debug': False,
'isASimulation': False,
'loopPeriod': LOOP_PERIOD,
'isADrone': False,
'flightScript': 'https://lab.nexedi.com/nexedi/flight-scripts/-/raw/v2.0/subscribe.js',
......@@ -322,7 +321,6 @@ class SubscriberTestCase(SlapOSInstanceTestCase):
)
self.send_ua_networkMessage()
time.sleep(0.1)
self.assertEqual(conn.recv_frame().data, MESSAGE_CONTENT.replace(b'\\', b''))
self.assertEqual(
conn.recv_frame().data,
b''.join((
......@@ -333,3 +331,4 @@ class SubscriberTestCase(SlapOSInstanceTestCase):
b'"log":""}}}',
)),
)
self.assertEqual(conn.recv_frame().data, MESSAGE_CONTENT.replace(b'\\', b''))
......@@ -64,6 +64,7 @@
date,
flight_state_cell,
i,
log_textarea,
message,
new_div,
new_span,
......@@ -81,7 +82,9 @@
document.getElementById(CLIMB_RATE_BASE_ID + id).innerHTML = drone["climbRate"];
document.getElementById(TIMESTAMP_BASE_ID + id).innerHTML = new Date(drone["timestamp"]).toLocaleTimeString('fr-FR');
{% if debug -%}
document.getElementById(LOG_BASE_ID + id).value += drone["log"];
log_textarea = document.getElementById(LOG_BASE_ID + id);
log_textarea.scrollTop = log_textarea.scrollHeight;
log_textarea.value += drone["log"];
{% endif -%}
});
} else if (message.hasOwnProperty("state") && message.hasOwnProperty("id")) {
......
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