//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 = `

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);