Files
24H_du_code_2025/script.js
T
2025-03-23 08:28:48 +01:00

590 lines
15 KiB
JavaScript

const ROBOT_URL = "192.168.185.166";
const CURL_URL = "localhost:4444";
const INFOS_POSITION = 0x01;
const INFOS_LED = 0x02;
const INFOS_MOTORS = 0x04;
const INFOS_WHEELS = 0x08;
const INFOS_SPEED = 0x10;
const INFOS_RANGEFINDER = 0x20;
const DEBUG = true;
const MUR_THRESHOLD = 80;
const MASTER_SLEEP_TIME = 500;
let socketMoteur = undefined;
let socketInfos = undefined;
let moteur = undefined;
let led = undefined;
let position = undefined;
let turtle = undefined;
const sleep = async (time) => {
await new Promise(r => setTimeout(r, time));
}
const connectSockets = () => {
if ((socketMoteur != undefined) || (socketInfos != undefined)) {
disconnectSocket();
}
socketMoteur = new WebSocket("ws://" + ROBOT_URL + "/motors.ws");
socketInfos = new WebSocket("ws://" + ROBOT_URL + "/infos.ws");
socketInfos.binaryType = "arraybuffer";
socketMoteur.onclose = function(event) {
if (event.wasClean) {
console.log(`[socketMoteur] Connection closed cleanly, code=${event.code} reason=${event.reason}`);
} else {
console.log('[socketMoteur] Connection died');
}
};
socketMoteur.onopen = function(e) {
console.log("[socketMoteur] Connection established");
};
socketInfos.onopen = function(e) {
console.log("[socketInfos] Connection established");
position.reset();
led.green();
new Promise(r => setTimeout(r, 2000)).then(() => {
led.off();
});
socketInfos.send(new Uint8Array([0x32, 0x20]));
};
socketInfos.onclose = function(event) {
if (event.wasClean) {
console.log(`[socketInfos] Connection closed cleanly, code=${event.code} reason=${event.reason}`);
} else {
console.log('[socketInfos] Connection died');
}
};
socketMoteur.onerror = function(error) {
console.log(`[socketMoteur] ERROR : ${error}`);
};
socketInfos.onerror = function(error) {
console.log(`[socketInfos] ERROR : ${error}`);
};
}
const disconnectSocket = () => {
if (socketMoteur != undefined) {
socketMoteur.close();
}
if (socketInfos != undefined) {
socketInfos.close();
}
socketMoteur = undefined;
socketInfos = undefined;
}
class Moteur {
motors_buf = new Float32Array(2);
send(d, g, v) {
if (socketMoteur == undefined) {
return;
}
this.motors_buf[0] = d * v;
this.motors_buf[1] = g * v;
socketMoteur.send(this.motors_buf);
}
avancer(v) {
this.send(1, 1, v);
}
reculer(v) {
this.send(-1, -1, v);
}
droite(v) {
this.send(1, -1, v);
}
gauche(v) {
this.send(-1, 1, v);
}
stop() {
this.send(0, 0, 0);
}
}
class Led {
http = undefined;
async send(color){
await fetch('http://' + ROBOT_URL + '/led/set_color', {
method: 'post',
headers: { "Content-Type": "application/x-www-form-urlencoded" },
mode: "no-cors",
body: 'ledcolor=' + color
});
}
red(){
this.send("#ff0000");
}
green(){
this.send("#00ff00");
}
blue(){
this.send("#0000ff");
}
off(){
this.send("#000000");
}
}
class Position {
posX = 0;
posY = 0;
Rot = 0;
capteur_distance = 0;
distance_mur_moyenne = 0;
constructor(socket) {
socket.addEventListener("message", e => {
const view = new DataView(e.data);
let pos = 0;
const mask = view.getUint8(pos);
pos++;
if (mask & INFOS_POSITION) {
this.posX = view.getFloat32(pos + 0);
this.posY = view.getFloat32(pos + 4);
this.Rot = view.getFloat32(pos + 8);
pos += 3*4;
if (DEBUG) {
console.log(
{"posX": this.posX, "posY": this.posY, "Rot": this.Rot}
);
}
}
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;
}
if (mask & INFOS_MOTORS) {
const ml = view.getFloat32(pos + 0);
const mr = view.getFloat32(pos + 4);
pos += 2*4;
}
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;
}
if (mask & INFOS_SPEED) {
const w = view.getFloat32(pos + 0);
const v = view.getFloat32(pos + 4);
pos += 4*2;
}
if (mask & INFOS_RANGEFINDER) {
this.capteur_distance = view.getUint16(pos + 0);
this.distance_mur_moyenne = (this.distance_mur_moyenne*2 + this.capteur_distance)/3;
pos += 2;
if (DEBUG) {
console.log("Capteur distance : " + this.capteur_distance);
console.log("Distance moyenne : " + this.distance_mur_moyenne);
}
}
});
}
async reset() {
await fetch("http://" + ROBOT_URL + "/position/reset", {
method: 'get',
headers: { "Content-Type": "application/x-www-form-urlencoded" },
mode: "no-cors"
});
}
getCapteurDistance() {
return this.capteur_distance;
}
isMurDevant() {
return this.distance_mur_moyenne < MUR_THRESHOLD;
}
}
class Turtle {
async avancer(distance) {
const data = {
'type': 'dist',
'dist': distance,
}
await fetch("http://" + CURL_URL, {
method: "POST",
mode: "no-cors",
body: JSON.stringify(data),
});
await sleep(MASTER_SLEEP_TIME);
}
async tourner(angle) {
const data = {
'type': 'angle',
'angle': angle,
}
await fetch("http://" + CURL_URL, {
method: "POST",
mode: "no-cors",
body: JSON.stringify(data),
});
await sleep(MASTER_SLEEP_TIME);
}
}
const Orientation = Object.freeze({
HAUT: 0,
DROITE: 1,
BAS: 2,
GAUCHE: 3,
});
class Noeud {
coordX;
coordY;
chemins;
constructor(coordX, coordY){
this.coordX = coordX;
this.coordY = coordY;
this.chemins = new Array();
}
addCheminPossible(chemin){
this.chemins.push(chemin);
}
getCheminsPossibles(){
return this.chemins;
}
toString(){
console.log("node : (" + this.getX + "; " + this.getY + ")");
}
toStringWithVoisins(){
console.log("node : (" + this.getX + "; " + this.getY + ")");
console.log("voisins connus : " + this.chemins.forEach((chemin) => chemin.toString()));
}
}
class ModeAutomatique {
cheminsPossibles = new Array(); // liste des listes de noeuds
noeudsVisites = new Array();
// valeurs pour la première position
posX=0;
posY=0;
rotation = Orientation.HAUT;
lastRotation = this.rotation;
isFirstNode = true;
async run(){
led.blue();
let iter = 0;
while(!this.isObjectifTrouve()){
if (iter >= 50)
break;
console.log("Iteration " + iter++);
console.log(this.cheminsPossibles);
let currentNode = this.getCaseAnalysee();
currentNode = await this.analyseEnvironnement(currentNode); // met à jour ses connaissances (à dessiner au fur et à mesure ?)
await this.deplacer(currentNode); // récupère un chemin non encore parcourus en fonction de ses connaissances
}
led.off();
}
isObjectifTrouve(){
return false;
}
//////////////////////////////////////// ANALYSE ENVIRONNEMENT
async isObstacleDevant () {
//await turtle.avancer(20);
await sleep(2000);
const mur = position.isMurDevant();
//await turtle.avancer(-20);
return mur;
}
async analyseEnvironnement(currentNode){
// SI case déjà visitée, alors on connait ses obstacles
if (currentNode == null) {
console.log(">>>>>>>>> iteration ")
currentNode = new Noeud(0, 0);
// pour les 4 directions possibles
for (let i = 0; i < 4; i++) {
if (!await this.isObstacleDevant()){
this.addCheminPossible(currentNode);
console.log("chemins possibles : ");
console.log(this.cheminsPossibles);
}
await this.tourne(); // changement de l'angle de 90 degrés
if (i == 1 && !this.isFirstNode) {
this.tourne(); // On scan pas notre pt d'entrée
this.isFirstNode = false;
i++;
}
}
this.noeudsVisites.push(currentNode);
console.log("noeuds visités : " + this.noeudsVisites)
}
return currentNode;
}
async tourne(){
await turtle.tourner(90);
this.rotation--;
if (this.rotation < 0) {
this.rotation = 3;
}
}
addCheminPossible(){
const chemin = new Array();
console.log("rotation : " + this.rotation);
if(this.rotation == 0){
console.log("rotation 0 ");
chemin.push(new Noeud(this.posX, this.posY+1));
} else if(this.rotation == 2){
console.log("rotation 2 ");
chemin.push(new Noeud(this.posX, this.posY-1));
}else if(this.rotation == 3){
console.log("rotation 3 ");
chemin.push(new Noeud(this.posX-1, this.posY));
}else if(this.rotation == 1){
console.log("rotation 1");
chemin.push(new Noeud(this.posX+1, this.posY));
}
console.log(chemin);
this.cheminsPossibles.push(chemin);
console.log(this.cheminsPossibles);
}
//////////////////////////////////////// MOVE
async deplacer(currentNode){
// on choisi le prochain noeud
const nextNode = this.getNextNode();
if (nextNode != null) {
console.log("next node : ");
nextNode.toString();
}
// on met à jour la liste des chemins en ajoutant le noeud courant dans les chemins non choisis
this.updatePaths(nextNode);
// on met à jour les coordonnées
this.updateCoordonnees(currentNode, nextNode);
// move
await this.move();
}
// parcours en profondeur : il s'agit de LIFOs
getNextNode(){
if (this.cheminsPossibles.length > 0) {
// on récupère la dernière liste
const cheminChoisi = this.cheminsPossibles[this.cheminsPossibles.length - 1];
// on récupère le dernier élément de cette liste
const nodeChoisi = cheminChoisi[cheminChoisi.length - 1];
console.log("noeud choisi :");
nodeChoisi.toString();
// on créé un nouveau noeud
return new Noeud(nodeChoisi.posX, nodeChoisi.posY);
}
return null;
}
updatePaths(nextNode){
// on ajoute le noeud visité à toutes les listes sauf la dernière
for (let i=0; i < this.cheminsPossibles.length -1; i++){
const cheminPossible = this.cheminsPossibles[i];
if (this.getCaseAnalyseeWithCoord(nextNode.getX, nextNode.getY == null)){
// on ajoute le noeud en cours à la liste des noeuds visités
cheminPossible.push(nextNode);
} else {
// on vire le noeud visité de toutes les listes s'il a déjà été visité avant (on fait demi tour)
cheminPossible.pop();
}
}
// on supprime les listes vides
this.cheminsPossibles = this.cheminsPossibles.filter(function (el) {
return el.length > 0;
});
console.log("nombre de chemins restants : " + this.cheminsPossibles.length)
console.log("chemins possibles : " + this.cheminsPossibles)
}
updateCoordonnees(currentNode, nextNode) {
console.log("coordonnees : (" + this.posX + "; " + this.posY + ")");
this.lastRotation = this.rotation;
if (currentNode.getX > nextNode.getX){
this.rotation = 3
this.posY--;
} else if (currentNode.getX < nextNode.getX){
this.rotation = 1
this.pos++;
} else if (currentNode.getY > nextNode.getY){
this.rotation = 2
this.posX--;
} else {
this.rotation = 0
this.posX++;
}
console.log("new coordonnees : (" + this.posX + "; " + this.posY + ")");
}
async move(){
// déplacer robot (les coordonnées sont déjà à jour)
let aRotater = this.rotation - this.lastRotation;
if (aRotater > 2) {
aRotater -= 2;
}
if (aRotater < 2) {
aRotater += 2;
}
if (aRotater > 0) {
aRotater = 4 - aRotater;
}
aRotater *= 90;
turtle.tourner(aRotater);
await turtle.avancer(190);
}
/////////////////////////////////////// GETTERS
getCaseAnalysee(){
let index = 0;
console.log(this.noeudsVisites)
this.noeudsVisites.forEach((noeud) => {
if (noeud == null) {
console.error(this.noeudsVisites);
console.error("INDEX NULL : " + (index++));
return;
}
if ((noeud.posX == this.posX) && (noeud.posY == this.posY)){
return noeud;
}
});
return null;
}
getCaseAnalyseeWithCoord(x, y){
let index = 0;
console.log(this.noeudsVisites)
this.noeudsVisites.forEach((noeud) => {
if (noeud == null) {
console.error(this.noeudsVisites);
console.error("INDEX NULL : " + (index++));
return;
}
if ((noeud.posX == x) && (noeud.posY == y)){
return noeud;
}
});
return null;
}
}
let doRun = false;
async function isMurDevant() {
turtle.avancer(20);
await sleep(2000);
const mur = position.isMurDevant();
turtle.avancer(-20);
return mur;
}
async function algo() {
doRun = true;
while(doRun) {
await turtle.avancer(1500);
await turtle.avancer(-20);
await turtle.tourner(30);
/*
let mur = isMurDevant();
if (!mur) {
await turtle.avancer(75);
}
await turtle.tourner(90);
mur = isMurDevant();
if (mur) {
await turtle.tourner(-90);
await turtle.tourner(-90);
await turtle.tourner(-90);
}
*/
}
}
async function run() {
connectSockets();
moteur = new Moteur();
led = new Led();
position = new Position(socketInfos);
turtle = new Turtle();
led.red();
algo();
/*
led.red();
await sleep(400);
led.green();
await sleep(400);
led.blue();
await sleep(400);
*/
}