diff --git a/ui/bootstrap-5.3.3-dist/css/bootstrap.css b/ui/bootstrap-5.3.3-dist/css/bootstrap.css
index 0cc4521..5505a00 100644
--- a/ui/bootstrap-5.3.3-dist/css/bootstrap.css
+++ b/ui/bootstrap-5.3.3-dist/css/bootstrap.css
@@ -12118,4 +12118,27 @@ input:checked + .slider:before {
.slider.round:before {
border-radius: 50%;
-}
\ No newline at end of file
+}
+
+#map {
+ max-width: 100%;
+ max-height: 100%;
+}
+
+.pico canvas {
+ display: inline-block;
+}
+
+.pico :where(audio, canvas, iframe, img, svg, video) {
+ vertical-align: middle;
+}
+
+*, ::after, ::before {
+ box-sizing: border-box;
+ background-repeat: no-repeat;
+}
+
+canvas {
+ overflow-clip-margin: content-box;
+ overflow: clip;
+}
\ No newline at end of file
diff --git a/ui/bootstrap-5.3.3-dist/js/bootstrap.js b/ui/bootstrap-5.3.3-dist/js/bootstrap.js
index e764ef9..b87a22e 100644
--- a/ui/bootstrap-5.3.3-dist/js/bootstrap.js
+++ b/ui/bootstrap-5.3.3-dist/js/bootstrap.js
@@ -4472,26 +4472,39 @@
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
+
+
+const motors_buf = new Float32Array(2);
+const motors_ml = document.getElementById("motors_ml")
+const motors_mr = document.getElementById("motors_mr")
+const motors_stop = document.getElementById("motors_stop")
+const motors_joystick = document.getElementById('motors_joystick');
+const motors_canvas = document.getElementById('motors_canvas');
+
+let motors_ws = null;
+
+const send_motors = (vl, vr) => {
+ /*if (motors_ws && motors_ws.readyState == WebSocket.OPEN) {
+ const lim = v => Math.max(-1, Math.min(v, 1));
+ motors_buf[0] = lim(vl);
+ motors_buf[1] = lim(vr);
+ motors_ml.value = vl * 100;
+ motors_mr.value = vr * 100;
+ motors_ws.send(motors_buf);
+ } else if (!motors_ws || motors_ws.readyState != WebSocket.CONNECTING) {
+ motors_ws = new WebSocket('ws://' + document.location.host + '/motors.ws');
+ motors_ws.addEventListener("open", () => send_motors(vl, vr));
+ }*/
+};
+
+const update_motors = () => {
+ const vl = motors_ml.value / 100;
+ const vr = motors_mr.value / 100;
+ send_motors(vl, vr);
+}
+
- const index_umd = {
- Alert,
- Button,
- Carousel,
- Collapse,
- Dropdown,
- Modal,
- Offcanvas,
- Popover,
- ScrollSpy,
- Tab,
- Toast,
- Tooltip
- };
- document.getElementById("colorLed").onchange=function(){
- // do whatever you want with value
- console.log(this.value);
- };
const btnsz = 40;
let positionX = 0, positionY = 0;
@@ -4503,20 +4516,19 @@
const h = motors_joystick.height;
ctx.clearRect(0, 0, w, h);
- ctx.fillStyle = "#00aa0040";
+ ctx.fillStyle = "#00aaff40";
ctx.beginPath();
ctx.arc(w/2, h/2, w/2-btnsz, 0, 2 * Math.PI);
ctx.fill();
ctx.lineWidth = 2;
- ctx.strokeStyle = "#009900";
- ctx.fillStyle = "#00aa00";
+ ctx.strokeStyle = "#0099ee";
+ ctx.fillStyle = "#00aaff";
ctx.beginPath();
ctx.arc(w/2 + positionX, h/2 + positionY, btnsz, 0, 2 * Math.PI);
ctx.fill();
ctx.stroke();
}
-
const updatePosition = e => {
const clientX = e.clientX || e.touches[0].clientX;
const clientY = e.clientY || e.touches[0].clientY;
@@ -4531,11 +4543,9 @@
}
positionX = vx * kw;
positionY = vy * kw;
- console.log("PositionX : "+ Math.round(positionX))
- console.log("PositionY : "+ Math.round(positionY))
vy *= -1;
if (performance.now() - lastsend > 250) {
- //send_motors(vy+vx, vy-vx);
+ send_motors(vy+vx, vy-vx);
lastsend = performance.now();
}
joystick_draw();
@@ -4552,7 +4562,7 @@
};
const onup = () => {
if (clicked) {
- //send_motors(0, 0);
+ send_motors(0, 0);
motors_joystick.style.cursor = ''
clicked = false;
positionX = positionY = 0;
@@ -4568,15 +4578,70 @@
motors_joystick.addEventListener('touchmove', onmove);
motors_joystick.addEventListener('touchend', onup);
motors_joystick.addEventListener('touchcancel', onup);
+
+ //const text_color = getComputedStyle(motors_canvas).color;
+ document.body.addEventListener('h:infos:motors', e => {
+ const ctx = motors_canvas.getContext("2d");
+ const w = motors_canvas.width;
+ const h = motors_canvas.height;
+ const m = 5;
+ const ml = e.detail[0];
+ const mr = e.detail[1];
+ ctx.clearRect(0, 0, w, h);
+ ctx.lineWidth = 3;
+ ctx.strokeStyle = "#808080"
+ ctx.fillStyle = "#008000"
+
+ ctx.beginPath();
+ ctx.rect(m, m, w-2*m, (h/2-m));
+ ctx.stroke();
+
+ ctx.beginPath();
+ ctx.rect(m, (h/2-m)+m, w-2*m, (h/2-m));
+ ctx.stroke();
+
+ ctx.beginPath();
+ if (ml > 0)
+ ctx.rect(w/2, m+1, ml*(w/2-m), (h/2-m)-2);
+ else
+ ctx.rect(w/2+ml*(w/2-m), m+1, -ml*(w/2-m), (h/2-m)-2);
+ ctx.fill();
+
+ ctx.beginPath();
+ if (ml > 0)
+ ctx.rect(w/2, (h/2-m)+m+1, mr*(w/2-m), (h/2-m)-2);
+ else
+ ctx.rect(w/2+mr*(w/2-m), (h/2-m)+m+1, -mr*(w/2-m), (h/2-m)-2);
+ ctx.fill();
+ });
+
+ /*document.body.addEventListener('h:section:statechanged', e => {
+ infos_mask(INFOS_MOTORS, e.detail['on']);
+ });
+ infos_mask(INFOS_MOTORS, true);*/
- function onclick(){
- send_motors(vy+vx, vy-vx);
-
- }
+ document.getElementById("colorLed").value = "#ffffff"
+ document.getElementById("colorLed").onchange=function(){
+ // do whatever you want with value
+ console.log(this.value);
+ };
+
+ const index_umd = {
+ Alert,
+ Button,
+ Carousel,
+ Collapse,
+ Dropdown,
+ Modal,
+ Offcanvas,
+ Popover,
+ ScrollSpy,
+ Tab,
+ Toast,
+ Tooltip
+ };
return index_umd;
-
-
}));
//# sourceMappingURL=bootstrap.js.map
diff --git a/ui/bootstrap-5.3.3-dist/js/infos.js b/ui/bootstrap-5.3.3-dist/js/infos.js
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/ui/bootstrap-5.3.3-dist/js/infos.js
@@ -0,0 +1 @@
+
diff --git a/ui/bootstrap-5.3.3-dist/js/main.js b/ui/bootstrap-5.3.3-dist/js/main.js
new file mode 100644
index 0000000..961963f
--- /dev/null
+++ b/ui/bootstrap-5.3.3-dist/js/main.js
@@ -0,0 +1,482 @@
+//Infos
+
+const INFOS_POSITION = 0x01;
+const INFOS_LED = 0x02;
+const INFOS_MOTORS = 0x04;
+const INFOS_WHEELS = 0x08;
+const INFOS_SPEED = 0x10;
+const INFOS_RANGEFINDER = 0x20;
+
+let infos_ws = null;
+let period_ms = 500;
+let mask = 0;
+
+function on_msg(e) {
+ const view = new DataView(e.data);
+ let pos = 0;
+
+ const mask = view.getUint8(pos);
+ pos++;
+
+ if (mask & INFOS_POSITION) {
+ const position_x = view.getFloat32(pos + 0);
+ const position_y = view.getFloat32(pos + 4);
+ const position_a = view.getFloat32(pos + 8);
+ pos += 3*4;
+ document.body.dispatchEvent(new CustomEvent('h:infos:position', {
+ 'detail': [position_x, position_y, position_a]
+ }));
+ }
+
+ if (mask & INFOS_LED) {
+ const led_r = view.getUint8(pos + 0);
+ const led_g = view.getUint8(pos + 1);
+ const led_b = view.getUint8(pos + 2);
+ pos += 3;
+
+ document.body.dispatchEvent(new CustomEvent('h:infos:led', {
+ 'detail': [led_r, led_g, led_b]
+ }));
+ }
+
+ if (mask & INFOS_MOTORS) {
+ const ml = view.getFloat32(pos + 0);
+ const mr = view.getFloat32(pos + 4);
+ pos += 2*4;
+
+ document.body.dispatchEvent(new CustomEvent('h:infos:motors', {
+ 'detail': [ml, mr]
+ }));
+ }
+
+ if (mask & INFOS_WHEELS) {
+ const wl = view.getInt16(pos + 0);
+ const wr = view.getInt16(pos + 2);
+ const tl = view.getInt16(pos + 4);
+ const tr = view.getInt16(pos + 6);
+ pos += 4*2;
+
+ document.body.dispatchEvent(new CustomEvent('h:infos:wheels', {
+ 'detail': [wl, wr, tl, tr]
+ }));
+ }
+
+ if (mask & INFOS_SPEED) {
+ const w = view.getFloat32(pos + 0);
+ const v = view.getFloat32(pos + 4);
+ pos += 4*2;
+
+ document.body.dispatchEvent(new CustomEvent('h:infos:speed', {
+ 'detail': [w, v]
+ }));
+ }
+
+ if (mask & INFOS_RANGEFINDER) {
+ const d = view.getUint16(pos + 0);
+ pos += 2;
+
+ console.log(d);
+
+ document.body.dispatchEvent(new CustomEvent('h:infos:rangefinder', {
+ 'detail': [d]
+ }));
+ }
+}
+
+function send_mask() {
+ /*if (mask == 0) {
+ if (infos_ws) {
+ infos_ws.close();
+ infos_ws = null;
+ }
+ } else {
+ if (infos_ws) {
+ if (infos_ws.readyState == WebSocket.OPEN) {
+ const buf = new Uint8Array(2);
+ buf[0] = period_ms / 10;
+ buf[1] = mask;
+ infos_ws.send(buf);
+ }
+ } else {
+ infos_ws = new WebSocket('ws://' + document.location.host + '/infos.ws');
+ infos_ws.binaryType = "arraybuffer";
+ infos_ws.addEventListener("open", send_mask);
+ infos_ws.addEventListener("message", on_msg);
+ }
+ }*/
+}
+
+function infos_mask(v, on) {
+ if (on)
+ mask |= v;
+ else
+ mask &= ~v;
+ send_mask();
+}
+
+
+
+
+//--------------------------------
+
+//main
+document.body.innerHTML = `
+
+
+ Sections…
+
+
+ MinHAUMtaure
+
+`;
+
+const sections_opened = new Set();
+if (sessionStorage.getItem('sections_opened')) {
+ for (const s of sessionStorage.getItem('sections_opened').split('\n'))
+ sections_opened.add(s);
+}
+
+const sections = [
+ { 'id': 'led', 'title': 'Led' },
+ { 'id': 'motors', 'title': 'Moteurs' },
+ { 'id': 'wheels', 'title': 'Retour roues' },
+ { 'id': 'position', 'title': 'Position' },
+ { 'id': 'turtle', 'title': 'Turtle' },
+ { 'id': 'rangefinder', 'title': 'Télémètre' },
+ { 'id': 'config', 'title': 'Config' },
+ { 'id': 'wlan', 'title': 'Wifi' },
+];
+const ul_header_menu = document.getElementById('ul_header_menu');
+for (const d of sections) {
+ const s = document.createElement('section');
+ const id = d['id'];
+ const title = d['title'];
+ s.id = 'section_' + id;
+ document.body.append(s);
+
+ const li = document.createElement('li');
+ ul_header_menu.append(li);
+ const label = document.createElement('label');
+ li.append(label);type="module"
+ const checkbox = document.createElement('input');
+ checkbox.type = 'checkbox';
+ checkbox.checked = false;
+ label.append(checkbox, title);
+
+ const h2 = document.createElement('h2');
+ const b_spans = document.createElement('b');
+ const span_fs = document.createElement('span');
+ span_fs.innerText = "⛶";
+ const span_close = document.createElement('span');
+ span_close.innerText = "×";
+ b_spans.append(span_fs, span_close);
+ h2.append(title, b_spans);
+ s.insertBefore(h2, s.firstChild);
+ s.classList.add('pico');
+ span_close.addEventListener('click', () => {
+ if (document.fullscreenElement != null)
+ document.exitFullscreen();
+ toggle(false);
+ });
+ span_fs.addEventListener('click', () => {
+ if (document.fullscreenElement == null)
+ s.requestFullscreen();
+ else
+ document.exitFullscreen();
+ });
+
+ const toggle = on => {
+ s.classList.toggle('opened', on);
+ checkbox.checked = on;
+ if (on)
+ sections_opened.add(id);
+ else
+ sections_opened.delete(id);
+ sessionStorage.setItem('sections_opened', Array.from(sections_opened).join('\n'));
+ if (on)
+ import('./' + id + '.js');
+ document.body.dispatchEvent(new CustomEvent('h:section:statechanged', {
+ 'detail': {
+ 'id': id,
+ 'on': on,
+ 'section': s
+ }
+ }));
+ }
+ checkbox.addEventListener('click', e => toggle(e.target.checked));
+ if (sections_opened.has(id)) toggle(true);
+}
+
+async function form_post(form) {
+ const data = new URLSearchParams();
+ for (const pair of new FormData(form)) {
+ data.append(pair[0], pair[1]);
+ }
+ return await fetch(form.action, {
+ method: 'post',
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
+ body: data
+ });
+}
+
+//--------------------------------
+
+//motors
+
+const section = document.getElementById('section_motors');
+section.insertAdjacentHTML('beforeend', `
+
+`);
+
+const motors_buf = new Float32Array(2);
+const motors_ml = document.getElementById("motors_ml")
+const motors_mr = document.getElementById("motors_mr")
+const motors_stop = document.getElementById("motors_stop")
+const motors_joystick = document.getElementById('motors_joystick');
+const motors_canvas = document.getElementById('motors_canvas');
+
+let motors_ws = null;
+
+const send_motors = (vl, vr) => {
+ /*if (motors_ws && motors_ws.readyState == WebSocket.OPEN) {
+ const lim = v => Math.max(-1, Math.min(v, 1));
+ motors_buf[0] = lim(vl);
+ motors_buf[1] = lim(vr);
+ motors_ml.value = vl * 100;
+ motors_mr.value = vr * 100;
+ motors_ws.send(motors_buf);
+ } else if (!motors_ws || motors_ws.readyState != WebSocket.CONNECTING) {
+ motors_ws = new WebSocket('ws://' + document.location.host + '/motors.ws');
+ motors_ws.addEventListener("open", () => send_motors(vl, vr));
+ }*/
+};
+
+const update_motors = () => {
+ const vl = motors_ml.value / 100;
+ const vr = motors_mr.value / 100;
+ send_motors(vl, vr);
+}
+
+motors_ml.addEventListener('change', update_motors);
+motors_mr.addEventListener('change', update_motors);
+motors_stop.addEventListener('click', e => {
+ e.preventDefault();
+ motors_ml.value = 0;
+ motors_mr.value = 0;
+ update_motors();
+});
+
+const btnsz = 40;
+let positionX = 0, positionY = 0;
+let clicked = false;
+let lastsend = performance.now();
+const joystick_draw = () => {
+ const ctx = motors_joystick.getContext("2d");
+ const w = motors_joystick.width;
+ const h = motors_joystick.height;
+ ctx.clearRect(0, 0, w, h);
+
+ ctx.fillStyle = "#00aaff40";
+ ctx.beginPath();
+ ctx.arc(w/2, h/2, w/2-btnsz, 0, 2 * Math.PI);
+ ctx.fill();
+
+ ctx.lineWidth = 2;
+ ctx.strokeStyle = "#0099ee";
+ ctx.fillStyle = "#00aaff";
+ ctx.beginPath();
+ ctx.arc(w/2 + positionX, h/2 + positionY, btnsz, 0, 2 * Math.PI);
+ ctx.fill();
+ ctx.stroke();
+}
+const updatePosition = e => {
+ const clientX = e.clientX || e.touches[0].clientX;
+ const clientY = e.clientY || e.touches[0].clientY;
+ const r = motors_joystick.getBoundingClientRect();
+ const kw = r.width/2 - btnsz;
+ let vx = Math.max(-1, Math.min((clientX - r.left - r.width/2) / kw, 1));
+ let vy = Math.max(-1, Math.min((clientY - r.top - r.height/2) / kw, 1));
+ if (vx*vx + vy*vy > 1) {
+ const a = Math.atan2(vy, vx);
+ vx = Math.cos(a);
+ vy = Math.sin(a);
+ }
+ positionX = vx * kw;
+ positionY = vy * kw;
+ vy *= -1;
+ if (performance.now() - lastsend > 250) {
+ send_motors(vy+vx, vy-vx);
+ lastsend = performance.now();
+ }
+ joystick_draw();
+}
+const ondown = e => {
+ clicked = true;
+ motors_joystick.style.cursor = 'none'
+ updatePosition(e);
+};
+const onmove = e => {
+ if (clicked) {
+ updatePosition(e);
+ }
+};
+const onup = () => {
+ if (clicked) {
+ send_motors(0, 0);
+ motors_joystick.style.cursor = ''
+ clicked = false;
+ positionX = positionY = 0;
+ joystick_draw();
+ }
+};
+joystick_draw();
+motors_joystick.addEventListener('mousedown', ondown);
+motors_joystick.addEventListener('mousemove', onmove);
+motors_joystick.addEventListener('mouseup', onup);
+motors_joystick.addEventListener('mouseleave', onup);
+motors_joystick.addEventListener('touchstart', ondown);
+motors_joystick.addEventListener('touchmove', onmove);
+motors_joystick.addEventListener('touchend', onup);
+motors_joystick.addEventListener('touchcancel', onup);
+
+const text_color = getComputedStyle(motors_canvas).color;
+document.body.addEventListener('h:infos:motors', e => {
+ const ctx = motors_canvas.getContext("2d");
+ const w = motors_canvas.width;
+ const h = motors_canvas.height;
+ const m = 5;
+ const ml = e.detail[0];
+ const mr = e.detail[1];
+ ctx.clearRect(0, 0, w, h);
+ ctx.lineWidth = 3;
+ ctx.strokeStyle = "#808080"
+ ctx.fillStyle = "#008000"
+
+ ctx.beginPath();
+ ctx.rect(m, m, w-2*m, (h/2-m));
+ ctx.stroke();
+
+ ctx.beginPath();
+ ctx.rect(m, (h/2-m)+m, w-2*m, (h/2-m));
+ ctx.stroke();
+
+ ctx.beginPath();
+ if (ml > 0)
+ ctx.rect(w/2, m+1, ml*(w/2-m), (h/2-m)-2);
+ else
+ ctx.rect(w/2+ml*(w/2-m), m+1, -ml*(w/2-m), (h/2-m)-2);
+ ctx.fill();
+
+ ctx.beginPath();
+ if (ml > 0)
+ ctx.rect(w/2, (h/2-m)+m+1, mr*(w/2-m), (h/2-m)-2);
+ else
+ ctx.rect(w/2+mr*(w/2-m), (h/2-m)+m+1, -mr*(w/2-m), (h/2-m)-2);
+ ctx.fill();
+});
+
+document.body.addEventListener('h:section:statechanged', e => {
+ infos_mask(INFOS_MOTORS, e.detail['on']);
+});
+infos_mask(INFOS_MOTORS, true);
+
+ //--------------------------------------------------
+
+ //position
+
+ const section2 = document.getElementById('section_position');
+section2.insertAdjacentHTML('beforeend', `
+
+
+
+
+
+`);
+
+const position_reset = document.getElementById('position_reset');
+position_reset.addEventListener('click', async () => {
+ position_reset.disabled = true;
+ await fetch('/position/reset')
+ position_reset.disabled = false;
+});
+
+const map_canvas = document.getElementById('map');
+const map_scale_range = document.getElementById('map_scale');
+const pos_hist = [];
+const text_color2 = getComputedStyle(map_canvas).color;
+const map_draw = (x, y, a) => {
+ const ctx = map_canvas.getContext("2d");
+ const w = map_canvas.width;
+ const h = map_canvas.height;
+ const map_scale = Number(map_scale_range.value);
+ ctx.clearRect(0, 0, w, h);
+
+ pos_hist.unshift([x, y]);
+ if (pos_hist.length > 10) pos_hist.pop();
+
+ ctx.font = '30px sans-serif';
+ ctx.fillStyle = text_color2;
+ ctx.textAlign = 'right';
+ ctx.fillText(Math.round(x*1000) + ' mm ', w-5, 30);
+ ctx.fillText(Math.round(y*1000) + ' mm ', w-5, 60);
+ ctx.fillText(Math.round(a*180/Math.PI) + ' deg ', w-5, 90);
+
+ ctx.save();
+ ctx.translate(w/2, h/2);
+ ctx.scale(map_scale/1000, map_scale/1000);
+ ctx.lineWidth = Math.round(2*1000/map_scale);
+
+ ctx.beginPath();
+ for (let i = -10; i <= 11; i++) {
+ ctx.moveTo(-95+190*i, -95-2090);
+ ctx.lineTo(-95+190*i, -95+2090+190);
+ ctx.moveTo(-95-2090, -95+190*i);
+ ctx.lineTo(-95+2090+190, -95+190*i);
+ }
+ ctx.strokeStyle = 'rgba(180, 180, 180, 50%)';
+ ctx.stroke();
+
+ const hl = pos_hist.length;
+ for (let i = 1; i < hl; i++) {
+ ctx.beginPath();
+ ctx.moveTo(1000*pos_hist[i-1][0], -1000*pos_hist[i-1][1]);
+ ctx.lineTo(1000*pos_hist[i][0], -1000*pos_hist[i][1]);
+ const v = Math.round((hl-i)/(hl-1)*100);
+ ctx.strokeStyle = 'rgba(180, 200, 0, ' + v + '%)';
+ ctx.stroke();
+ }
+
+ ctx.translate(x*1000, -y*1000);
+ ctx.rotate(-a);
+ ctx.beginPath();
+ ctx.moveTo(40, 0);
+ ctx.lineTo(35, 77/4);
+ ctx.lineTo(24, 77/2);
+ ctx.lineTo(-68, 77/2);
+ ctx.lineTo(-68, -77/2);
+ ctx.lineTo(24, -77/2);
+ ctx.lineTo(35, -77/4);
+ ctx.lineTo(40, 0);
+ ctx.strokeStyle = '#008000'
+ ctx.stroke();
+
+ ctx.restore();
+};
+
+document.body.addEventListener('h:infos:position', e => {
+ map_draw(...e.detail);
+});
+
+document.body.addEventListener('h:section:statechanged', e => {
+ infos_mask(INFOS_POSITION, e.detail['on']);
+});
+infos_mask(INFOS_POSITION, true);
+
diff --git a/ui/bootstrap-5.3.3-dist/js/motors.js b/ui/bootstrap-5.3.3-dist/js/motors.js
new file mode 100644
index 0000000..ab941e7
--- /dev/null
+++ b/ui/bootstrap-5.3.3-dist/js/motors.js
@@ -0,0 +1,166 @@
+import { infos_mask, INFOS_MOTORS } from "./infos.js";
+
+const section = document.getElementById('section_motors');
+section.insertAdjacentHTML('beforeend', `
+
+`);
+
+const motors_buf = new Float32Array(2);
+const motors_ml = document.getElementById("motors_ml")
+const motors_mr = document.getElementById("motors_mr")
+const motors_stop = document.getElementById("motors_stop")
+const motors_joystick = document.getElementById('motors_joystick');
+const motors_canvas = document.getElementById('motors_canvas');
+
+let motors_ws = null;
+
+const send_motors = (vl, vr) => {
+ if (motors_ws && motors_ws.readyState == WebSocket.OPEN) {
+ const lim = v => Math.max(-1, Math.min(v, 1));
+ motors_buf[0] = lim(vl);
+ motors_buf[1] = lim(vr);
+ motors_ml.value = vl * 100;
+ motors_mr.value = vr * 100;
+ motors_ws.send(motors_buf);
+ } else if (!motors_ws || motors_ws.readyState != WebSocket.CONNECTING) {
+ motors_ws = new WebSocket('ws://' + document.location.host + '/motors.ws');
+ motors_ws.addEventListener("open", () => send_motors(vl, vr));
+ }
+};
+
+const update_motors = () => {
+ const vl = motors_ml.value / 100;
+ const vr = motors_mr.value / 100;
+ send_motors(vl, vr);
+}
+
+motors_ml.addEventListener('change', update_motors);
+motors_mr.addEventListener('change', update_motors);
+motors_stop.addEventListener('click', e => {
+ e.preventDefault();
+ motors_ml.value = 0;
+ motors_mr.value = 0;
+ update_motors();
+});
+
+const btnsz = 40;
+let positionX = 0, positionY = 0;
+let clicked = false;
+let lastsend = performance.now();
+const joystick_draw = () => {
+ const ctx = motors_joystick.getContext("2d");
+ const w = motors_joystick.width;
+ const h = motors_joystick.height;
+ ctx.clearRect(0, 0, w, h);
+
+ ctx.fillStyle = "#00aaff40";
+ ctx.beginPath();
+ ctx.arc(w/2, h/2, w/2-btnsz, 0, 2 * Math.PI);
+ ctx.fill();
+
+ ctx.lineWidth = 2;
+ ctx.strokeStyle = "#0099ee";
+ ctx.fillStyle = "#00aaff";
+ ctx.beginPath();
+ ctx.arc(w/2 + positionX, h/2 + positionY, btnsz, 0, 2 * Math.PI);
+ ctx.fill();
+ ctx.stroke();
+}
+const updatePosition = e => {
+ const clientX = e.clientX || e.touches[0].clientX;
+ const clientY = e.clientY || e.touches[0].clientY;
+ const r = motors_joystick.getBoundingClientRect();
+ const kw = r.width/2 - btnsz;
+ let vx = Math.max(-1, Math.min((clientX - r.left - r.width/2) / kw, 1));
+ let vy = Math.max(-1, Math.min((clientY - r.top - r.height/2) / kw, 1));
+ if (vx*vx + vy*vy > 1) {
+ const a = Math.atan2(vy, vx);
+ vx = Math.cos(a);
+ vy = Math.sin(a);
+ }
+ positionX = vx * kw;
+ positionY = vy * kw;
+ vy *= -1;
+ if (performance.now() - lastsend > 250) {
+ send_motors(vy+vx, vy-vx);
+ lastsend = performance.now();
+ }
+ joystick_draw();
+}
+const ondown = e => {
+ clicked = true;
+ motors_joystick.style.cursor = 'none'
+ updatePosition(e);
+};
+const onmove = e => {
+ if (clicked) {
+ updatePosition(e);
+ }
+};
+const onup = () => {
+ if (clicked) {
+ send_motors(0, 0);
+ motors_joystick.style.cursor = ''
+ clicked = false;
+ positionX = positionY = 0;
+ joystick_draw();
+ }
+};
+joystick_draw();
+motors_joystick.addEventListener('mousedown', ondown);
+motors_joystick.addEventListener('mousemove', onmove);
+motors_joystick.addEventListener('mouseup', onup);
+motors_joystick.addEventListener('mouseleave', onup);
+motors_joystick.addEventListener('touchstart', ondown);
+motors_joystick.addEventListener('touchmove', onmove);
+motors_joystick.addEventListener('touchend', onup);
+motors_joystick.addEventListener('touchcancel', onup);
+
+const text_color = getComputedStyle(motors_canvas).color;
+document.body.addEventListener('h:infos:motors', e => {
+ const ctx = motors_canvas.getContext("2d");
+ const w = motors_canvas.width;
+ const h = motors_canvas.height;
+ const m = 5;
+ const ml = e.detail[0];
+ const mr = e.detail[1];
+ ctx.clearRect(0, 0, w, h);
+ ctx.lineWidth = 3;
+ ctx.strokeStyle = "#808080"
+ ctx.fillStyle = "#008000"
+
+ ctx.beginPath();
+ ctx.rect(m, m, w-2*m, (h/2-m));
+ ctx.stroke();
+
+ ctx.beginPath();
+ ctx.rect(m, (h/2-m)+m, w-2*m, (h/2-m));
+ ctx.stroke();
+
+ ctx.beginPath();
+ if (ml > 0)
+ ctx.rect(w/2, m+1, ml*(w/2-m), (h/2-m)-2);
+ else
+ ctx.rect(w/2+ml*(w/2-m), m+1, -ml*(w/2-m), (h/2-m)-2);
+ ctx.fill();
+
+ ctx.beginPath();
+ if (ml > 0)
+ ctx.rect(w/2, (h/2-m)+m+1, mr*(w/2-m), (h/2-m)-2);
+ else
+ ctx.rect(w/2+mr*(w/2-m), (h/2-m)+m+1, -mr*(w/2-m), (h/2-m)-2);
+ ctx.fill();
+});
+
+document.body.addEventListener('h:section:statechanged', e => {
+ infos_mask(INFOS_MOTORS, e.detail['on']);
+});
+infos_mask(INFOS_MOTORS, true);
diff --git a/ui/bootstrap-5.3.3-dist/js/position.js b/ui/bootstrap-5.3.3-dist/js/position.js
new file mode 100644
index 0000000..30748fb
--- /dev/null
+++ b/ui/bootstrap-5.3.3-dist/js/position.js
@@ -0,0 +1,89 @@
+import { infos_mask, INFOS_POSITION } from "./infos.js";
+
+const section = document.getElementById('section_position');
+section.insertAdjacentHTML('beforeend', `
+
+
+
+
+
+`);
+
+const position_reset = document.getElementById('position_reset');
+position_reset.addEventListener('click', async () => {
+ position_reset.disabled = true;
+ await fetch('/position/reset')
+ position_reset.disabled = false;
+});
+
+const map_canvas = document.getElementById('map');
+const map_scale_range = document.getElementById('map_scale');
+const pos_hist = [];
+const text_color = getComputedStyle(map_canvas).color;
+const map_draw = (x, y, a) => {
+ const ctx = map_canvas.getContext("2d");
+ const w = map_canvas.width;
+ const h = map_canvas.height;
+ const map_scale = Number(map_scale_range.value);
+ ctx.clearRect(0, 0, w, h);
+
+ pos_hist.unshift([x, y]);
+ if (pos_hist.length > 10) pos_hist.pop();
+
+ ctx.font = '30px sans-serif';
+ ctx.fillStyle = text_color;
+ ctx.textAlign = 'right';
+ ctx.fillText(Math.round(x*1000) + ' mm ', w-5, 30);
+ ctx.fillText(Math.round(y*1000) + ' mm ', w-5, 60);
+ ctx.fillText(Math.round(a*180/Math.PI) + ' deg ', w-5, 90);
+
+ ctx.save();
+ ctx.translate(w/2, h/2);
+ ctx.scale(map_scale/1000, map_scale/1000);
+ ctx.lineWidth = Math.round(2*1000/map_scale);
+
+ ctx.beginPath();
+ for (let i = -10; i <= 11; i++) {
+ ctx.moveTo(-95+190*i, -95-2090);
+ ctx.lineTo(-95+190*i, -95+2090+190);
+ ctx.moveTo(-95-2090, -95+190*i);
+ ctx.lineTo(-95+2090+190, -95+190*i);
+ }
+ ctx.strokeStyle = 'rgba(180, 180, 180, 50%)';
+ ctx.stroke();
+
+ const hl = pos_hist.length;
+ for (let i = 1; i < hl; i++) {
+ ctx.beginPath();
+ ctx.moveTo(1000*pos_hist[i-1][0], -1000*pos_hist[i-1][1]);
+ ctx.lineTo(1000*pos_hist[i][0], -1000*pos_hist[i][1]);
+ const v = Math.round((hl-i)/(hl-1)*100);
+ ctx.strokeStyle = 'rgba(180, 200, 0, ' + v + '%)';
+ ctx.stroke();
+ }
+
+ ctx.translate(x*1000, -y*1000);
+ ctx.rotate(-a);
+ ctx.beginPath();
+ ctx.moveTo(40, 0);
+ ctx.lineTo(35, 77/4);
+ ctx.lineTo(24, 77/2);
+ ctx.lineTo(-68, 77/2);
+ ctx.lineTo(-68, -77/2);
+ ctx.lineTo(24, -77/2);
+ ctx.lineTo(35, -77/4);
+ ctx.lineTo(40, 0);
+ ctx.strokeStyle = '#008000'
+ ctx.stroke();
+
+ ctx.restore();
+};
+
+document.body.addEventListener('h:infos:position', e => {
+ map_draw(...e.detail);
+});
+
+document.body.addEventListener('h:section:statechanged', e => {
+ infos_mask(INFOS_POSITION, e.detail['on']);
+});
+infos_mask(INFOS_POSITION, true);
diff --git a/ui/images/labyrinth-1080p-wallpaper-1920x1080.jpg b/ui/images/labyrinth-1080p-wallpaper-1920x1080.jpg
new file mode 100644
index 0000000..faa84f2
Binary files /dev/null and b/ui/images/labyrinth-1080p-wallpaper-1920x1080.jpg differ
diff --git a/ui/index.html b/ui/index.html
index b341c59..98358d6 100644
--- a/ui/index.html
+++ b/ui/index.html
@@ -6,27 +6,30 @@
MinHAUMTaure
-
+
-
+
Bienvenue sur MinHAUMTaure
-
-
-
-
-
Directions
-
+
+
+
+
Fonctionnalités
+
+
+
+
Directions
+
-
Fonctionnalités
+
Changement de couleur LED
Sélection de la couleur
-
Automatique
+
Automatique
@@ -34,9 +37,13 @@
-
-
Repère Spatial
-
+
+
+
Repère Spatial
+
+
+
+