שיעור 8 - חיישן מרחק ותכנות פליירו

ברוכים הבאים לשיעור השמיני!

השיעור מורכב מסדרה של סרטונים, צפו בהם לפי הסדר.

תהנו 🙂

חלק 1: הקדמה

חלק 2: איך עובד חיישן מרחק?

חלק 3: תכנות חיישן מרחק

/*
* Ultrasonic Sensor HC-SR04 and Arduino Tutorial
*
* by Dejan Nedelkovski,
* www.HowToMechatronics.com
*
*/
// defines pins numbers
const int trigPin = 9;
const int echoPin = 10;
// defines variables
long duration;
int distance;
void setup() {
pinMode(trigPin, OUTPUT); // Sets the trigPin as an Output
pinMode(echoPin, INPUT); // Sets the echoPin as an Input
Serial.begin(9600); // Starts the serial communication
}
void loop() {
// Clears the trigPin
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
// Sets the trigPin on HIGH state for 10 micro seconds
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
// Reads the echoPin, returns the sound wave travel time in microseconds
duration = pulseIn(echoPin, HIGH);
// Calculating the distance
distance= duration*0.034/2;
// Prints the distance on the Serial Monitor
Serial.print("Distance: ");
Serial.println(distance);
delay(100);
} 

חלק 4: מתכנתים את פליירו

חלק 5: מדליקים מנורה

void setup() {
  // put your setup code here, to run once:
  pinMode(2, OUTPUT);
}

void loop() {
  // put your main code here, to run repeatedly:
  digitalWrite(2, HIGH);
  delay(1000);
  digitalWrite(2, LOW);
  delay(1000);  
} 

חלק 6: התקנת ספריות

//Ver 1.6 - 10/9/2020

//Calibrate the servo, change servo_offset value until the wheels will be straight
//Higher number will turn the wheels left, lower number will turn them right 
int servo_offset = 0;
int servo_center = 90;

//=======================================

//Install the following libraries:

//FastLED
#include "FastLED.h"
//ESP32Servo
#include <ESP32Servo.h> 
//IRremoteESP8266
#include <IRremoteESP8266.h>
//Melody_player
#include <melody_player.h>
#include <melody_factory.h>

//=======================================

#include <WiFi.h>
#include <WebServer.h>


#include <Wire.h>
#include <SPI.h>
#include "RF24.h"
#include <Arduino.h>

#include <IRrecv.h>
#include <IRsend.h> 
#include <IRutils.h>

// Library to read and write from flash memory
#include <EEPROM.h>


//Ir recive pin
const uint16_t kRecvPin = 34;
//const uint16_t kRecvPin = 32; //v 1.6
//const uint16_t kRecvPin = 15; // IR REC2

IRrecv irrecv(kRecvPin);
decode_results results;

//Ir send pin
IRsend irsend(13);

extern RF24 radio;
const byte robot[6] = "00001";
const byte remote_control[6] = "00002";
const byte race_controller[6] = "00003";

Servo myservo; 
#define SERVO_PIN 32      // GPIO pin used to connect the servo control (digital out)
//#define SERVO_PIN 27      // v1.6

#define BUZZER_PIN 14

const int CE = 26, CSN = 25;
RF24 radio(CE, CSN); // CE, CSN

const char* PARAM_MESSAGE = "message";


void printDetail(uint8_t type, int value);


#define MOTOR_PIN_IN1 5
#define MOTOR_PIN_IN2 2
#define MOTOR_PIN_PWMB 4
#define PWMB_CHANNEL 5

#define PWM_FREQ 20000
#define SPEED_RESOLUTION 8



MelodyPlayer player(BUZZER_PIN,6);




//Global variables
int pos;    // variable to store the servo position
int speed;
int icon;
int sound;
int degrees;
int turn_speed;
int steering_value;
unsigned long current_time;
unsigned long last_lap_time;
unsigned long last_sensor_lap_reading;
float best_lap_time=99999;
float lap_time =99998;
int random_number=1;
int last_random_number=1;
int loop_counter=10;
char formatted_lap_time[6]="0.00";
char formatted_best_lap_time[6];

//Ultrasonic Setup 
int trigPin = 12;    // Trigger
int echoPin = 35;    // Echo
long compass_angle;
float heading;
float final_heading;
CRGB leds[16];

int total_laps_counter;

WebServer server(80);

long microsecondsToInches(long microseconds) {
   return microseconds / 74 / 2;
}

long microsecondsToCentimeters(long microseconds) {
   return microseconds / 29 / 2;
}

void drive_rc(bool forward, int speed) {
    speed = speed/3;
    //If somehow we got negative speed convert it to positive
    if (speed<0)
      speed = speed*-1;
    Serial.printf("motor front engine speed: %d\n", speed);
    if (forward) {
        digitalWrite(MOTOR_PIN_IN1, LOW);
        digitalWrite(MOTOR_PIN_IN2, HIGH);
    } else {
        digitalWrite(MOTOR_PIN_IN1, HIGH);
        digitalWrite(MOTOR_PIN_IN2, LOW);
    }
    ledcWrite(PWMB_CHANNEL, speed );
}

void steering_rc(int position) {
  
  //The input this function gets is 0-180, but our turning range is using just a part of the 180 degrees range of the servo
  //So we need to map it to just 60 degrees range (30 degrees to each side)
  //SERVO_CENTER is a variable we are getting from the remote
  position = map(position, 0, 180, servo_center - 20 + servo_offset, servo_center + 20 + servo_offset);
  Serial.printf("steering: %d\n", position);
  myservo.write(position);
  //server.sendHeader("Access-Control-Allow-Origin", "*");
  //server.send(200, "text/html", "a"); 
}

void stop() {
  digitalWrite(MOTOR_PIN_IN1, LOW);
  digitalWrite(MOTOR_PIN_IN2, LOW);
  //motor.drive(0);
  Serial.printf("Stop\n");
  //server.sendHeader("Access-Control-Allow-Origin", "*");
  //server.send(200, "text/html", "a"); 
}

void read_from_remote()
{
    int speed;
    int direction;
    int steering;
    char text[17] = "";
    //xSemaphoreTake(radio_baton, portMAX_DELAY);
    if (radio.available())
    {
        radio.read(&text, sizeof(text));

        if(text[0] == 'M')
        {
          //We got a movement request
          int speed_one = (int)text[1];
          int speed_two = (int)text[2];
          //Because of size issues we divided speed into two values, now are are combining them again
          int speed = (speed_one * 254) + speed_two;
          //The value we have now in speed is the value we sent (0-512), but the motor controller should get a value of (0-255)
          //We need to make the mapping
          speed = map(speed, 0, 512, 0, 255);

          //Serial.printf("speed:%d  | ", speed);
          int direction = (int)text[3];
          //Serial.printf("direction:%d  | ", direction);
          int steering = (int)text[4];
          Serial.printf("steering:%d  | ", steering);
          if (direction==1)
            drive_rc(true,  speed); //forward
          else  
            drive_rc(false, speed); //back
          int received_servo_center = (int)text[5];
          //Verify we got a correct value
          if ((received_servo_center>29)&&(received_servo_center<121))
            servo_center = received_servo_center;
          steering_rc(steering);


          
        }



    

    
    }

}
//void handle_NotFound(){
  //server.send(404, "text/plain", "Not found");
//}






String SendHTML(){
  String ptr = "<html><head><meta charset='utf-8'> \n\
<meta name='viewport' content='width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0'> \n\
<style> \
	body {overflow	: hidden;background-color: #BBB;} \n\
	#container \
	{width: 100%;height: 100%;overflow: hidden;padding:0;margin:0;-webkit-user-select:none;-moz-user-select:none;} \n\
	html, body { \n\
		position: absolute; \n\
		top: 0; \n\
		left: 0; \n\
		right: 0; \n\ 
		bottom: 0; \n\
		padding: 0; \n\
		margin: 0; \n\
    -webkit-touch-callout: none; \n\
    -webkit-text-size-adjust: none; \n\
    -webkit-user-select: none; \n\
	} \n\
	#left { \n\
		position: absolute; \n\
		left: 0; \n\
		top: 0; \n\
		height: 100%; \n\
		width: 50%; \n\
		background: rgba(0, 255, 0, 0.1); \n\
     text-align: center; \n\
	} \n\
	#right { \n\
		position: absolute; \n\
		right: 0; \n\
		top: 0; \n\
		height: 100%; \n\
		width: 50%; \n\
		background: rgba(0, 0, 255, 0.1); \n\
    text-align: center; \n\
	} \n\
  	#lap-titles {font-family: sans-serif; font-size: 30px; padding-left: 5px;} \n\
  	#svg-title-icon { position: relative; top: 7px;	} \n\
    #total-laps-container { padding-left: 5px;position: absolute;bottom: 10px;left: 10px;font-family: sans-serif;font-size: 20px; }  \n\
	</style> \n\
<title> PlayRobotics - Racing game </title></head><body> \n\
<div id='container'> \n\
</div> \n\
<div id='left'> \n\
  <! –– Icons made by http://www.freepik.com/ from https://www.flaticon.com/  ––> \n\
	<svg id='svg-title-icon'  height='40' viewBox='0 0 512.392 512.392' width='40' xmlns='http://www.w3.org/2000/svg'> \n\
		<path d='M511.005,279.646c-4.597-46.238-25.254-89.829-58.168-122.744    c-28.128-28.127-62.556-46.202-98.782-54.239V77.255c14.796-3.681,25.794-17.074,25.794-32.993c0-18.748-15.252-34-34-34h-72    c-18.748,0-34,15.252-34,34c0,15.918,10.998,29.311,25.793,32.993v25.479c-36.115,8.071-70.429,26.121-98.477,54.169    c-6.138,6.138-11.798,12.577-16.979,19.269c-0.251-0.019-0.502-0.038-0.758-0.038H78.167c-5.522,0-10,4.477-10,10s4.478,10,10,10    h58.412c-7.332,12.275-13.244,25.166-17.744,38.436H10c-5.522,0-10,4.477-10,10s4.478,10,10,10h103.184    c-2.882,12.651-4.536,25.526-4.963,38.437H64c-5.522,0-10,4.477-10,10s4.478,10,10,10h44.54    c0.844,12.944,2.925,25.82,6.244,38.437H50c-5.522,0-10,4.477-10,10s4.478,10,10,10h71.166    c9.81,25.951,25.141,50.274,45.999,71.132c32.946,32.946,76.582,53.608,122.868,58.181c6.606,0.652,13.217,0.975,19.819,0.975    c39.022,0,77.548-11.293,110.238-32.581c4.628-3.014,5.937-9.209,2.923-13.837s-9.209-5.937-13.837-2.923    c-71.557,46.597-167.39,36.522-227.869-23.957c-70.962-70.962-70.962-186.425,0-257.388c70.961-70.961,186.424-70.961,257.387,0    c60.399,60.4,70.529,156.151,24.086,227.673c-3.008,4.632-1.691,10.826,2.94,13.833c4.634,3.008,10.826,1.691,13.833-2.941    C504.367,371.396,515.537,325.241,511.005,279.646z M259.849,44.263c0-7.72,6.28-14,14-14h72c7.72,0,14,6.28,14,14s-6.28,14-14,14    h-1.794h-68.413h-1.793C266.129,58.263,259.849,51.982,259.849,44.263z M285.642,99.296V78.263h48.413v20.997    C317.979,97.348,301.715,97.36,285.642,99.296z'/> \n\
		<path d='M445.77,425.5c-2.64,0-5.21,1.07-7.069,2.93c-1.87,1.86-2.931,4.44-2.931,7.07    c0,2.63,1.061,5.21,2.931,7.07c1.859,1.87,4.43,2.93,7.069,2.93c2.63,0,5.2-1.06,7.07-2.93c1.86-1.86,2.93-4.44,2.93-7.07    c0-2.63-1.069-5.21-2.93-7.07C450.97,426.57,448.399,425.5,445.77,425.5z'/> \n\
		<path d='M310.001,144.609c-85.538,0-155.129,69.59-155.129,155.129s69.591,155.129,155.129,155.129    s155.129-69.59,155.129-155.129S395.539,144.609,310.001,144.609z M310.001,434.867c-74.511,0-135.129-60.619-135.129-135.129    s60.618-135.129,135.129-135.129S445.13,225.228,445.13,299.738S384.512,434.867,310.001,434.867z'/> \n\
		<path d='M373.257,222.34l-49.53,49.529c-4.142-2.048-8.801-3.205-13.726-3.205c-4.926,0-9.584,1.157-13.726,3.205    l-22.167-22.167c-3.906-3.905-10.236-3.905-14.143,0c-3.905,3.905-3.905,10.237,0,14.142l22.167,22.167    c-2.049,4.142-3.205,8.801-3.205,13.726c0,17.134,13.939,31.074,31.074,31.074s31.074-13.94,31.074-31.074    c0-4.925-1.157-9.584-3.205-13.726l48.076-48.076v0l1.453-1.453c3.905-3.905,3.905-10.237,0-14.142    S377.164,218.435,373.257,222.34z M310.001,310.812c-6.106,0-11.074-4.968-11.074-11.074s4.968-11.074,11.074-11.074    s11.074,4.968,11.074,11.074S316.107,310.812,310.001,310.812z'/> \n\
		<path d='M416.92,289.86h-9.265c-5.522,0-10,4.477-10,10s4.478,10,10,10h9.265c5.522,0,10-4.477,10-10    S422.442,289.86,416.92,289.86z'/> \n\
		<path d='M212.346,289.616h-9.264c-5.522,0-10,4.477-10,10s4.478,10,10,10h9.264c5.522,0,10-4.477,10-10    S217.868,289.616,212.346,289.616z'/> \n\
		<path d='M310.123,212.083c5.522,0,10-4.477,10-10v-9.264c0-5.523-4.478-10-10-10s-10,4.477-10,10v9.264    C300.123,207.606,304.601,212.083,310.123,212.083z'/> \n\
		<path d='M309.879,387.393c-5.522,0-10,4.477-10,10v9.264c0,5.523,4.478,10,10,10s10-4.477,10-10v-9.264    C319.879,391.87,315.401,387.393,309.879,387.393z'/> \n\
		<path d='M10,351.44c-2.63,0-5.21,1.07-7.07,2.93c-1.86,1.86-2.93,4.44-2.93,7.07c0,2.64,1.069,5.21,2.93,7.07    s4.44,2.93,7.07,2.93s5.21-1.07,7.069-2.93c1.86-1.86,2.931-4.44,2.931-7.07s-1.07-5.21-2.931-7.07    C15.21,352.51,12.63,351.44,10,351.44z'/> \n\
	</svg> \n\
	<span id='lap-titles'>Lap: <span id='lap_time'>0.00</span></span>\n\
  <span id='total-laps-container'>\n\
    <! –– Icons made by http://www.freepik.com/ from https://www.flaticon.com/  ––> \n\
    <svg id='svg-title-icon'  height='40' viewBox='0 0 512.392 512.392' width='40' xmlns='http://www.w3.org/2000/svg'>\n\
      <g><path d='m399 210h18c52.383 0 95-42.617 95-95v-40c0-41.355-33.645-75-75-75h-332c-57.897 0-105 47.103-105 105v211.236c0 57.897 47.103 105 105 105h69v15c0 41.777 33.987 75.764 75.764 75.764h167.236c52.383 0 95-42.617 95-95v-67c0-52.383-42.617-95-95-95h-18c-8.271 0-15-6.729-15-15v-15c0-8.271 6.729-15 15-15zm-339 80.236h-30v-30h30zm339-5.236h18c35.841 0 65 29.159 65 65v67c0 35.841-29.159 65-65 65h-167.236c-25.235 0-45.764-20.529-45.764-45.764v-20c0-13.785-11.215-25-25-25h-74c-40.013 0-72.805-31.497-74.891-71h30.079c2.032 22.945 21.348 41 44.813 41h83.236c25.234 0 45.763 20.53 45.763 45.764v20c0 13.785 11.215 25 25 25h148c24.813 0 45-20.187 45-45v-47c0-24.813-20.187-45-45-45h-11.292c-39.54 0-71.708-32.168-71.708-71.708v-21.584c0-39.54 32.168-71.708 71.708-71.708h21.292c19.299 0 35-15.701 35-35v-20c0-19.299-15.701-35-35-35h-312c-24.813 0-45 20.187-45 45v125.236h-30v-125.236c0-41.355 33.645-75 75-75h332c24.813 0 45 20.187 45 45v40c0 35.841-29.159 65-65 65h-18c-24.813 0-45 20.187-45 45v15c0 24.813 20.187 45 45 45zm-3.292 60h11.292c8.271 0 15 6.729 15 15v47c0 8.271-6.729 15-15 15h-143v-15c0-41.776-33.987-75.764-75.764-75.764h-83.236c-8.271 0-15-6.729-15-15v-211.236c0-8.271 6.729-15 15-15h312c2.757 0 5 2.243 5 5v20c0 2.757-2.243 5-5 5h-21.292c-56.082 0-101.708 45.626-101.708 101.708v21.584c0 56.082 45.626 101.708 101.708 101.708z'/></g> \n\
    </svg>\n\
	  <span id='total_laps'></span> Laps\n\
  </span>\n\
</div>  \n\
<div id='right'> \n\
  <! –– Icons made by http://www.freepik.com/ from https://www.flaticon.com/  ––> \n\
	<svg id='svg-title-icon' height='40' viewBox='0 0 512.392 512.392' width='40' xmlns='http://www.w3.org/2000/svg'> \n\
		<g><g><g>	\n\
			<path d='m484.534 89.03c-1.158-12.986-11.863-22.779-24.901-22.779h-64.68v-9.532h1.259c9.236 0 16.75-7.514 16.75-16.749v-23.221c0-9.235-7.514-16.749-16.75-16.749h-190.37c-4.142 0-7.5 3.357-7.5 7.5s3.358 7.5 7.5 7.5h190.37c.965 0 1.75.784 1.75 1.749v23.221c0 .965-.785 1.749-1.75 1.749h-280.406c-.965 0-1.75-.784-1.75-1.749v-23.221c0-.965.785-1.749 1.75-1.749h54.871c4.142 0 7.5-3.357 7.5-7.5s-3.358-7.5-7.5-7.5h-54.871c-9.236 0-16.75 7.514-16.75 16.749v23.221c0 9.235 7.514 16.749 16.75 16.749h1.259v9.532h-64.68c-13.038 0-23.743 9.793-24.901 22.779-.152 1.698-3.522 42.121 13.031 86.891 15.145 40.962 49.891 93.264 129.501 113.79 12.816 10.126 27.45 18.05 43.327 23.185l3.775 6.231c7.135 11.779 10.906 25.704 10.906 40.269 0 8.897-1.418 17.589-4.214 25.832l-5.242 15.448h-17.441c-17.898 0-32.46 14.562-32.46 32.461v15.711h-27.296c-7.703 0-13.97 6.268-13.97 13.971v35.23c0 7.703 6.267 13.97 13.97 13.97h229.277c7.703 0 13.97-6.267 13.97-13.97v-35.23c0-7.703-6.267-13.971-13.97-13.971h-27.296v-15.711c0-17.899-14.562-32.461-32.46-32.461h-17.441l-5.242-15.448c-2.797-8.242-4.215-16.934-4.215-25.832 0-14.564 3.771-28.489 10.906-40.269l3.775-6.231c15.877-5.135 30.51-13.058 43.327-23.185 79.61-20.526 114.356-72.828 129.501-113.79 16.553-44.77 13.183-85.192 13.031-86.891zm-364.692 119.324c-13.984-13.092-24.697-29.333-32.024-48.69-5.091-13.45-8.423-28.053-9.906-43.413h39.153v64.412c0 9.48.959 18.74 2.777 27.691zm-65.257-37.634c-15.395-41.638-12.299-78.796-12.16-80.357.463-5.194 4.745-9.111 9.96-9.111h64.68v20h-39.774c-4.052 0-7.94 1.717-10.668 4.711-2.731 2.998-4.081 7.034-3.704 11.074 1.579 16.923 5.236 33.052 10.87 47.938 11.127 29.398 29.138 52.353 53.792 68.695 4.851 11.708 11.258 22.614 18.964 32.459-54.338-22.523-79.929-62.871-91.96-95.409zm315.033 293.128v33.171h-227.218v-33.171zm-58.726-48.172c9.627 0 17.46 7.833 17.46 17.461v15.711h-144.685v-15.711c0-9.628 7.833-17.461 17.46-17.461zm-36.887-25.628 3.606 10.628h-43.205l3.607-10.629c3.324-9.799 5.01-20.111 5.01-30.651 0-14.753-3.297-28.996-9.567-41.629 7.343 1.204 14.874 1.84 22.553 1.84 7.678 0 15.21-.636 22.552-1.84-6.27 12.633-9.567 26.876-9.567 41.629.001 10.541 1.686 20.853 5.011 30.652zm-17.996-85.442c-68.343 0-123.944-55.601-123.944-123.943v-123.944h247.888v123.944c0 68.343-55.601 123.943-123.944 123.943zm201.425-133.886c-12.031 32.539-37.623 72.891-91.964 95.414 7.72-9.863 14.137-20.79 18.992-32.522 21.169-14.057 37.472-32.989 48.698-56.695 1.773-3.743.175-8.216-3.568-9.988-3.742-1.772-8.214-.176-9.988 3.568-6.95 14.677-16.116 27.322-27.434 37.903 1.824-8.965 2.785-18.24 2.785-27.736v-64.412h39.153c-.746 7.717-1.986 15.37-3.687 22.758-.929 4.037 1.59 8.063 5.627 8.991 4.033.934 8.062-1.589 8.991-5.626 1.894-8.229 3.26-16.754 4.062-25.338.377-4.04-.973-8.076-3.704-11.074-2.728-2.994-6.616-4.711-10.668-4.711h-39.774v-20h64.68c5.215 0 9.497 3.917 9.96 9.112.137 1.56 3.233 38.718-12.161 80.356z'/> \n\
			<path d='m305.328 145.663-21.506-3.125c-.937-.137-1.746-.725-2.165-1.573l-9.618-19.488c-3.033-6.146-9.175-9.964-16.029-9.964s-12.996 3.818-16.029 9.964l-9.618 19.488c-.419.849-1.228 1.437-2.165 1.573l-21.506 3.125c-6.783.985-12.312 5.646-14.43 12.165s-.385 13.54 4.523 18.325l15.562 15.168c.678.661.987 1.612.827 2.546l-3.674 21.42c-1.159 6.755 1.566 13.453 7.111 17.482 5.544 4.027 12.758 4.55 18.826 1.361l19.235-10.113c.838-.441 1.838-.441 2.676 0l19.236 10.113c2.639 1.387 5.494 2.072 8.334 2.072 3.691 0 7.358-1.157 10.491-3.434 5.545-4.029 8.27-10.728 7.111-17.482l-3.674-21.42c-.16-.934.149-1.885.827-2.545l15.563-15.17c4.908-4.784 6.641-11.806 4.523-18.324s-7.649-11.179-14.431-12.164zm-.563 19.748-15.563 15.17c-4.213 4.107-6.135 10.021-5.141 15.821l3.674 21.42c.263 1.533-.604 2.42-1.144 2.813-.539.393-1.651.945-3.028.219l-19.236-10.113c-2.604-1.369-5.461-2.054-8.318-2.054s-5.714.685-8.318 2.054l-19.235 10.113c-1.376.725-2.488.173-3.029-.219-.54-.393-1.406-1.279-1.144-2.813l3.674-21.42c.995-5.8-.927-11.714-5.141-15.822l-15.562-15.168c-1.114-1.086-.935-2.313-.728-2.948.206-.635.782-1.733 2.321-1.957l21.506-3.125c5.824-.847 10.855-4.502 13.459-9.778l9.618-19.488c.688-1.395 1.911-1.603 2.578-1.603s1.89.208 2.578 1.603l9.618 19.488c2.604 5.276 7.635 8.932 13.459 9.778l21.507 3.125c1.539.224 2.115 1.322 2.321 1.957.208.635.387 1.862-.726 2.947z'/> \n\
		</g></g></g> \n\
	</svg> \n\
	<span id='lap-titles'>Best: <span id='best_lap_time'>0.00</span></span> \n\
</div>  \n\  
<script>";

//Including nipple.js library: https://yoannmoi.net/nipplejs/
ptr += "!function(t){if(\"object\"==typeof exports&&\"undefined\"!=typeof module)module.exports=t();else if(\"function\"==typeof define&&define.amd)define([],t);\
else{var e;e=\"undefined\"!=typeof window?window:\"undefined\"!=typeof global?global:\"undefined\"!=typeof self?self:this,e.nipplejs=t()}}(function(){function t(){}function e(t,i){return this.identifier=i.identifier,this.position=i.position,this.frontPosition=i.frontPosition,this.collection=t,this.defaults={size:100,threshold:.1,color:\"white\",fadeTime:250,dataOnly:!1,restJoystick:!0,restOpacity:.5,mode:\"dynamic\",zone:document.body,lockX:!1,lockY:!1},this.config(i),\"dynamic\"===this.options.mode&&(this.options.restOpacity=0),this.id=e.id,e.id+=1,this.buildEl().stylize(),this.instance={el:this.ui.el,on:this.on.bind(this),off:this.off.bind(this),show:this.show.bind(this),hide:this.hide.bind(this),add:this.addToDom.bind(this),remove:this.removeFromDom.bind(this),destroy:this.destroy.bind(this),resetDirection:this.resetDirection.bind(this),computeDirection:this.computeDirection.bind(this),trigger:this.trigger.bind(this),position:this.position,frontPosition:this.frontPosition,ui:this.ui,identifier:this.identifier,id:this.id,options:this.options},this.instance}function i(t,e){var n=this;return n.nipples=[],n.idles=[],n.actives=[],n.ids=[],n.pressureIntervals={},n.manager=t,n.id=i.id,i.id+=1,n.defaults={zone:document.body,multitouch:!1,maxNumberOfNipples:10,mode:\"dynamic\",position:{top:0,left:0},catchDistance:200,size:100,threshold:.1,color:\"white\",fadeTime:250,dataOnly:!1,restJoystick:!0,restOpacity:.5,lockX:!1,lockY:!1},n.config(e),\"static\"!==n.options.mode&&\"semi\"!==n.options.mode||(n.options.multitouch=!1),n.options.multitouch||(n.options.maxNumberOfNipples=1),n.updateBox(),n.prepareNipples(),n.bindings(),n.begin(),n.nipples}function n(t){var e=this;e.ids={},e.index=0,e.collections=[],e.config(t),e.prepareCollections();var i;return c.bindEvt(window,\"resize\",function(t){clearTimeout(i),i=setTimeout(function(){var t,i=c.getScroll();e.collections.forEach(function(e){e.forEach(function(e){t=e.el.getBoundingClientRect(),e.position={x:i.x+t.left,y:i.y+t.top}})})},100)}),e.collections}var o,r=!!(\"ontouchstart\"in window),s=!!window.PointerEvent,d=!!window.MSPointerEvent,a={touch:{start:\"touchstart\",move:\"touchmove\",end:\"touchend, touchcancel\"},mouse:{start:\"mousedown\",move:\"mousemove\",end:\"mouseup\"},pointer:{start:\"pointerdown\",move:\"pointermove\",end:\"pointerup, pointercancel\"},MSPointer:{start:\"MSPointerDown\",move:\"MSPointerMove\",end:\"MSPointerUp\"}},p={};s?o=a.pointer:d?o=a.MSPointer:r?(o=a.touch,p=a.mouse):o=a.mouse;var c={};c.distance=function(t,e){var i=e.x-t.x,n=e.y-t.y;return Math.sqrt(i*i+n*n)},c.angle=function(t,e){var i=e.x-t.x,n=e.y-t.y;return c.degrees(Math.atan2(n,i))},c.findCoord=function(t,e,i){var n={x:0,y:0};return i=c.radians(i),n.x=t.x-e*Math.cos(i),n.y=t.y-e*Math.sin(i),n},c.radians=function(t){return t*(Math.PI/180)},c.degrees=function(t){return t*(180/Math.PI)},c.bindEvt=function(t,e,i){for(var n,o=e.split(/[ ,]+/g),r=0;r<o.length;r+=1)n=o[r],t.addEventListener?t.addEventListener(n,i,!1):t.attachEvent&&t.attachEvent(n,i)},c.unbindEvt=function(t,e,i){for(var n,o=e.split(/[ ,]+/g),r=0;r<o.length;r+=1)n=o[r],t.removeEventListener?t.removeEventListener(n,i):t.detachEvent&&t.detachEvent(n,i)},c.trigger=function(t,e,i){var n=new CustomEvent(e,i);t.dispatchEvent(n)},c.prepareEvent=function(t){return t.preventDefault(),t.type.match(/^touch/)?t.changedTouches:t},c.getScroll=function(){return{x:void 0!==window.pageXOffset?window.pageXOffset:(document.documentElement||document.body.parentNode||document.body).scrollLeft,y:void 0!==window.pageYOffset?window.pageYOffset:(document.documentElement||document.body.parentNode||document.body).scrollTop}},c.applyPosition=function(t,e){e.top||e.right||e.bottom||e.left?(t.style.top=e.top,t.style.right=e.right,t.style.bottom=e.bottom,t.style.left=e.left):(t.style.left=e.x+\"px\",t.style.top=e.y+\"px\")},c.getTransitionStyle=function(t,e,i){var n=c.configStylePropertyObject(t);for(var o in n)if(n.hasOwnProperty(o))if(\"string\"==typeof e)n[o]=e+\" \"+i;else{for(var r=\"\",s=0,d=e.length;s<d;s+=1)r+=e[s]+\" \"+i+\", \";n[o]=r.slice(0,-2)}return n},c.getVendorStyle=function(t,e){var i=c.configStylePropertyObject(t);for(var n in i)i.hasOwnProperty(n)&&(i[n]=e);return i},c.configStylePropertyObject=function(t){var e={};return e[t]=\"\",[\"webkit\",\"Moz\",\"o\"].forEach(function(i){e[i+t.charAt(0).toUpperCase()+t.slice(1)]=\"\"}),e},c.extend=function(t,e){for(var i in e)e.hasOwnProperty(i)&&(t[i]=e[i]);return t},c.safeExtend=function(t,e){var i={};for(var n in t)t.hasOwnProperty(n)&&e.hasOwnProperty(n)?i[n]=e[n]:t.hasOwnProperty(n)&&(i[n]=t[n]);return i},c.map=function(t,e){if(t.length)for(var i=0,n=t.length;i<n;i+=1)e(t[i]);else e(t)},t.prototype.on=function(t,e){var i,n=this,o=t.split(/[ ,]+/g);n._handlers_=n._handlers_||{};for(var r=0;r<o.length;r+=1)i=o[r],n._handlers_[i]=n._handlers_[i]||[],n._handlers_[i].push(e);return n},t.prototype.off=function(t,e){var i=this;return i._handlers_=i._handlers_||{},void 0===t?i._handlers_={}:void 0===e?i._handlers_[t]=null:i._handlers_[t]&&i._handlers_[t].indexOf(e)>=0&&i._handlers_[t].splice(i._handlers_[t].indexOf(e),1),i},t.prototype.trigger=function(t,e){var i,n=this,o=t.split(/[ ,]+/g);n._handlers_=n._handlers_||{};for(var r=0;r<o.length;r+=1)i=o[r],n._handlers_[i]&&n._handlers_[i].length&&n._handlers_[i].forEach(function(t){t.call(n,{type:i,target:n},e)})},t.prototype.config=function(t){var e=this;e.options=e.defaults||{},t&&(e.options=c.safeExtend(e.options,t))},t.prototype.bindEvt=function(t,e){var i=this;return i._domHandlers_=i._domHandlers_||{},i._domHandlers_[e]=function(){\"function\"==typeof i[\"on\"+e]?i[\"on\"+e].apply(i,arguments):console.warn('[WARNING] : Missing \"on'+e+'\" handler.')},c.bindEvt(t,o[e],i._domHandlers_[e]),p[e]&&c.bindEvt(t,p[e],i._domHandlers_[e]),i},t.prototype.unbindEvt=function(t,e){var i=this;return i._domHandlers_=i._domHandlers_||{},c.unbindEvt(t,o[e],i._domHandlers_[e]),p[e]&&c.unbindEvt(t,p[e],i._domHandlers_[e]),delete i._domHandlers_[e],this},e.prototype=new t,e.constructor=e,e.id=0,e.prototype.buildEl=function(t){return this.ui={},this.options.dataOnly?this:(this.ui.el=document.createElement(\"div\"),this.ui.back=document.createElement(\"div\"),this.ui.front=document.createElement(\"div\"),this.ui.el.className=\"nipple collection_\"+this.collection.id,this.ui.back.className=\"back\",this.ui.front.className=\"front\",this.ui.el.setAttribute(\"id\",\"nipple_\"+this.collection.id+\"_\"+this.id),this.ui.el.appendChild(this.ui.back),this.ui.el.appendChild(this.ui.front),this)},e.prototype.stylize=function(){if(this.options.dataOnly)return this;var t=this.options.fadeTime+\"ms\",e=c.getVendorStyle(\"borderRadius\",\"50%\"),i=c.getTransitionStyle(\"transition\",\"opacity\",t),n={};return n.el={position:\"absolute\",opacity:this.options.restOpacity,display:\"block\",zIndex:999},n.back={position:\"absolute\",display:\"block\",width:this.options.size+\"px\",height:this.options.size+\"px\",marginLeft:-this.options.size/2+\"px\",marginTop:-this.options.size/2+\"px\",background:this.options.color,opacity:\".5\"},n.front={width:this.options.size/2+\"px\",height:this.options.size/2+\"px\",position:\"absolute\",display:\"block\",marginLeft:-this.options.size/4+\"px\",marginTop:-this.options.size/4+\"px\",background:this.options.color,opacity:\".5\"},c.extend(n.el,i),c.extend(n.back,e),c.extend(n.front,e),this.applyStyles(n),this},e.prototype.applyStyles=function(t){for(var e in this.ui)if(this.ui.hasOwnProperty(e))for(var i in t[e])this.ui[e].style[i]=t[e][i];return this},e.prototype.addToDom=function(){return this.options.dataOnly||document.body.contains(this.ui.el)?this:(this.options.zone.appendChild(this.ui.el),this)},e.prototype.removeFromDom=function(){return this.options.dataOnly||!document.body.contains(this.ui.el)?this:(this.options.zone.removeChild(this.ui.el),this)},e.prototype.destroy=function(){clearTimeout(this.removeTimeout),clearTimeout(this.showTimeout),clearTimeout(this.restTimeout),this.trigger(\"destroyed\",this.instance),this.removeFromDom(),this.off()},e.prototype.show=function(t){var e=this;return e.options.dataOnly?e:(clearTimeout(e.removeTimeout),clearTimeout(e.showTimeout),clearTimeout(e.restTimeout),e.addToDom(),e.restCallback(),setTimeout(function(){e.ui.el.style.opacity=1},0),e.showTimeout=setTimeout(function(){e.trigger(\"shown\",e.instance),\"function\"==typeof t&&t.call(this)},e.options.fadeTime),e)},e.prototype.hide=function(t){var e=this;return e.options.dataOnly?e:(e.ui.el.style.opacity=e.options.restOpacity,clearTimeout(e.removeTimeout),clearTimeout(e.showTimeout),clearTimeout(e.restTimeout),e.removeTimeout=setTimeout(function(){var i=\"dynamic\"===e.options.mode?\"none\":\"block\";e.ui.el.style.display=i,\"function\"==typeof t&&t.call(e),e.trigger(\"hidden\",e.instance)},e.options.fadeTime),e.options.restJoystick&&e.restPosition(),e)},e.prototype.restPosition=function(t){var e=this;e.frontPosition={x:0,y:0};var i=e.options.fadeTime+\"ms\",n={};n.front=c.getTransitionStyle(\"transition\",[\"top\",\"left\"],i);var o={front:{}};o.front={left:e.frontPosition.x+\"px\",top:e.frontPosition.y+\"px\"},e.applyStyles(n),e.applyStyles(o),e.restTimeout=setTimeout(function(){\"function\"==typeof t&&t.call(e),e.restCallback()},e.options.fadeTime)},e.prototype.restCallback=function(){var t=this,e={};e.front=c.getTransitionStyle(\"transition\",\"none\",\"\"),t.applyStyles(e),t.trigger(\"rested\",t.instance)},e.prototype.resetDirection=function(){this.direction={x:!1,y:!1,angle:!1}},e.prototype.computeDirection=function(t){var e,i,n,o=t.angle.radian,r=Math.PI/4,s=Math.PI/2;if(o>r&&o<3*r&&!t.lockX?e=\"up\":o>-r&&o<=r&&!t.lockY?e=\"left\":o>3*-r&&o<=-r&&!t.lockX?e=\"down\":t.lockY||(e=\"right\"),t.lockY||(i=o>-s&&o<s?\"left\":\"right\"),t.lockX||(n=o>0?\"up\":\"down\"),t.force>this.options.threshold){var d={};for(var a in this.direction)this.direction.hasOwnProperty(a)&&(d[a]=this.direction[a]);var p={};\
this.direction={x:i,y:n,angle:e},t.direction=this.direction;for(var a in d)d[a]===this.direction[a]&&(p[a]=!0);if(p.x&&p.y&&p.angle)return t;p.x&&p.y||this.trigger(\"plain\",t),p.x||this.trigger(\"plain:\"+i,t),p.y||this.trigger(\"plain:\"+n,t),p.angle||this.trigger(\"dir dir:\"+e,t)}return t},i.prototype=new t,i.constructor=i,i.id=0,i.prototype.prepareNipples=function(){var t=this,e=t.nipples;e.on=t.on.bind(t),e.off=t.off.bind(t),e.options=t.options,e.destroy=t.destroy.bind(t),e.ids=t.ids,e.id=t.id,e.processOnMove=t.processOnMove.bind(t),e.processOnEnd=t.processOnEnd.bind(t),e.get=function(t){if(void 0===t)return e[0];for(var i=0,n=e.length;i<n;i+=1)if(e[i].identifier===t)return e[i];return!1}},i.prototype.bindings=function(){var t=this;t.bindEvt(t.options.zone,\"start\"),t.options.zone.style.touchAction=\"none\",t.options.zone.style.msTouchAction=\"none\"},i.prototype.begin=function(){var t=this,e=t.options;if(\"static\"===e.mode){var i=t.createNipple(e.position,t.manager.getIdentifier());i.add(),t.idles.push(i)}},i.prototype.createNipple=function(t,i){var n=this,o=c.getScroll(),r={},s=n.options;if(t.x&&t.y)r={x:t.x-(o.x+n.box.left),y:t.y-(o.y+n.box.top)};else if(t.top||t.right||t.bottom||t.left){var d=document.createElement(\"DIV\");d.style.display=\"hidden\",d.style.top=t.top,d.style.right=t.right,d.style.bottom=t.bottom,d.style.left=t.left,d.style.position=\"absolute\",s.zone.appendChild(d);var a=d.getBoundingClientRect();s.zone.removeChild(d),r=t,t={x:a.left+o.x,y:a.top+o.y}}var p=new e(n,{color:s.color,size:s.size,threshold:s.threshold,fadeTime:s.fadeTime,dataOnly:s.dataOnly,restJoystick:s.restJoystick,restOpacity:s.restOpacity,mode:s.mode,identifier:i,position:t,zone:s.zone,frontPosition:{x:0,y:0}});return s.dataOnly||(c.applyPosition(p.ui.el,r),c.applyPosition(p.ui.front,p.frontPosition)),n.nipples.push(p),n.trigger(\"added \"+p.identifier+\":added\",p),n.manager.trigger(\"added \"+p.identifier+\":added\",p),n.bindNipple(p),p},i.prototype.updateBox=function(){var t=this;t.box=t.options.zone.getBoundingClientRect()},i.prototype.bindNipple=function(t){var e,i=this,n=function(t,n){e=t.type+\" \"+n.id+\":\"+t.type,i.trigger(e,n)};t.on(\"destroyed\",i.onDestroyed.bind(i)),t.on(\"shown hidden rested dir plain\",n),t.on(\"dir:up dir:right dir:down dir:left\",n),t.on(\"plain:up plain:right plain:down plain:left\",n)},i.prototype.pressureFn=function(t,e,i){var n=this,o=0;clearInterval(n.pressureIntervals[i]),n.pressureIntervals[i]=setInterval(function(){var i=t.force||t.pressure||t.webkitForce||0;i!==o&&(e.trigger(\"pressure\",i),n.trigger(\"pressure \"+e.identifier+\":pressure\",i),o=i)}.bind(n),100)},i.prototype.onstart=function(t){var e=this,i=e.options;t=c.prepareEvent(t),e.updateBox();var n=function(t){e.actives.length<i.maxNumberOfNipples&&e.processOnStart(t)};return c.map(t,n),e.manager.bindDocument(),!1},i.prototype.processOnStart=function(t){var e,i=this,n=i.options,o=i.manager.getIdentifier(t),r=t.force||t.pressure||t.webkitForce||0,s={x:t.pageX,y:t.pageY},d=i.getOrCreate(o,s);d.identifier!==o&&i.manager.removeIdentifier(d.identifier),d.identifier=o;var a=function(e){e.trigger(\"start\",e),i.trigger(\"start \"+e.id+\":start\",e),e.show(),r>0&&i.pressureFn(t,e,e.identifier),i.processOnMove(t)};if((e=i.idles.indexOf(d))>=0&&i.idles.splice(e,1),i.actives.push(d),i.ids.push(d.identifier),\"semi\"!==n.mode)a(d);else{if(!(c.distance(s,d.position)<=n.catchDistance))return d.destroy(),void i.processOnStart(t);a(d)}return d},i.prototype.getOrCreate=function(t,e){var i,n=this,o=n.options;return/(semi|static)/.test(o.mode)?(i=n.idles[0])?(n.idles.splice(0,1),i):\"semi\"===o.mode?n.createNipple(e,t):(console.warn(\"Coudln't find the needed nipple.\"),!1):i=n.createNipple(e,t)},i.prototype.processOnMove=function(t){var e=this,i=e.options,n=e.manager.getIdentifier(t),o=e.nipples.get(n);if(!o)return console.error(\"Found zombie joystick with ID \"+n),void e.manager.removeIdentifier(n);o.identifier=n;var r=o.options.size/2,s={x:t.pageX,y:t.pageY},d=c.distance(s,o.position),a=c.angle(s,o.position),p=c.radians(a),l=d/r;d>r&&(d=r,s=c.findCoord(o.position,d,a));var u=s.x-o.position.x,f=s.y-o.position.y;i.lockX&&(f=0),i.lockY&&(u=0),o.frontPosition={x:u,y:f},i.dataOnly||c.applyPosition(o.ui.front,o.frontPosition);var h={identifier:o.identifier,position:s,force:l,pressure:t.force||t.pressure||t.webkitForce||0,distance:d,angle:{radian:p,degree:a},instance:o,lockX:i.lockX,lockY:i.lockY};h=o.computeDirection(h),h.angle={radian:c.radians(180-a),degree:180-a},o.trigger(\"move\",h),e.trigger(\"move \"+o.id+\":move\",h)},i.prototype.processOnEnd=function(t){var e=this,i=e.options,n=e.manager.getIdentifier(t),o=e.nipples.get(n),r=e.manager.removeIdentifier(o.identifier);o&&(i.dataOnly||o.hide(function(){\"dynamic\"===i.mode&&(o.trigger(\"removed\",o),e.trigger(\"removed \"+o.id+\":removed\",o),e.manager.trigger(\"removed \"+o.id+\":removed\",o),o.destroy())}),clearInterval(e.pressureIntervals[o.identifier]),o.resetDirection(),o.trigger(\"end\",o),e.trigger(\"end \"+o.id+\":end\",o),e.ids.indexOf(o.identifier)>=0&&e.ids.splice(e.ids.indexOf(o.identifier),1),e.actives.indexOf(o)>=0&&e.actives.splice(e.actives.indexOf(o),1),/(semi|static)/.test(i.mode)?e.idles.push(o):e.nipples.indexOf(o)>=0&&e.nipples.splice(e.nipples.indexOf(o),1),e.manager.unbindDocument(),/(semi|static)/.test(i.mode)&&(e.manager.ids[r.id]=r.identifier))},i.prototype.onDestroyed=function(t,e){var i=this;i.nipples.indexOf(e)>=0&&i.nipples.splice(i.nipples.indexOf(e),1),i.actives.indexOf(e)>=0&&i.actives.splice(i.actives.indexOf(e),1),i.idles.indexOf(e)>=0&&i.idles.splice(i.idles.indexOf(e),1),i.ids.indexOf(e.identifier)>=0&&i.ids.splice(i.ids.indexOf(e.identifier),1),i.manager.removeIdentifier(e.identifier),i.manager.unbindDocument()},i.prototype.destroy=function(){var t=this;t.unbindEvt(t.options.zone,\"start\"),t.nipples.forEach(function(t){t.destroy()});for(var e in t.pressureIntervals)t.pressureIntervals.hasOwnProperty(e)&&clearInterval(t.pressureIntervals[e]);t.trigger(\"destroyed\",t.nipples),t.manager.unbindDocument(),t.off()},n.prototype=new t,n.constructor=n,n.prototype.prepareCollections=function(){var t=this;t.collections.create=t.create.bind(t),t.collections.on=t.on.bind(t),t.collections.off=t.off.bind(t),t.collections.destroy=t.destroy.bind(t),t.collections.get=function(e){var i;return t.collections.every(function(t){return!(i=t.get(e))}),i}},n.prototype.create=function(t){return this.createCollection(t)},n.prototype.createCollection=function(t){var e=this,n=new i(e,t);return e.bindCollection(n),e.collections.push(n),n},n.prototype.bindCollection=function(t){var e,i=this,n=function(t,n){e=t.type+\" \"+n.id+\":\"+t.type,i.trigger(e,n)};t.on(\"destroyed\",i.onDestroyed.bind(i)),t.on(\"shown hidden rested dir plain\",n),t.on(\"dir:up dir:right dir:down dir:left\",n),t.on(\"plain:up plain:right plain:down plain:left\",n)},n.prototype.bindDocument=function(){var t=this;t.binded||(t.bindEvt(document,\"move\").bindEvt(document,\"end\"),t.binded=!0)},n.prototype.unbindDocument=function(t){var e=this;Object.keys(e.ids).length&&!0!==t||(e.unbindEvt(document,\"move\").unbindEvt(document,\"end\"),e.binded=!1)},n.prototype.getIdentifier=function(t){var e;return t?void 0===(e=void 0===t.identifier?t.pointerId:t.identifier)&&(e=this.latest||0):e=this.index,void 0===this.ids[e]&&(this.ids[e]=this.index,this.index+=1),this.latest=e,this.ids[e]},n.prototype.removeIdentifier=function(t){var e={};for(var i in this.ids)if(this.ids[i]===t){e.id=i,e.identifier=this.ids[i],delete this.ids[i];break}return e},n.prototype.onmove=function(t){return this.onAny(\"move\",t),!1},n.prototype.onend=function(t){return this.onAny(\"end\",t),!1},n.prototype.oncancel=function(t){return this.onAny(\"end\",t),!1},n.prototype.onAny=function(t,e){var i,n=this,o=\"processOn\"+t.charAt(0).toUpperCase()+t.slice(1);e=c.prepareEvent(e);var r=function(t,e,i){i.ids.indexOf(e)>=0&&(i[o](t),t._found_=!0)},s=function(t){i=n.getIdentifier(t),c.map(n.collections,r.bind(null,t,i)),t._found_||n.removeIdentifier(i)};return c.map(e,s),!1},n.prototype.destroy=function(){var t=this;t.unbindDocument(!0),t.ids={},t.index=0,t.collections.forEach(function(t){t.destroy()}),t.off()},n.prototype.onDestroyed=function(t,e){var i=this;if(i.collections.indexOf(e)<0)return!1;i.collections.splice(i.collections.indexOf(e),1)};var l=new n;return{create:function(t){return l.create(t)},factory:l}});\n";

ptr +="//Global variables \n\
	let steering=90; \n\
	let speed=0; \n\
	let direction=1; \n\
  var http = new XMLHttpRequest()  \n\
  http.onreadystatechange = function() { \n\
    if (this.readyState == 4 && this.status == 200) { \n\
        //the response we are getting looks like this: L|lap_time|best_lap_time|total_laps \n\
        var response = this.responseText \n\
        var response_array = response.split('|'); \n\
        if (response_array[0]=='L')  \n\
        { \n\
            var lap_time = response_array[1]; \n\
            var best_lap_time = response_array[2]; \n\
            var total_laps = response_array[3]; \n\
            document.getElementById('lap_time').innerHTML = lap_time;  \n\
            document.getElementById('best_lap_time').innerHTML = best_lap_time;  \n\
            document.getElementById('total_laps').innerHTML = total_laps;  \n\
        }\n\
    } \n\
  }; \n\
	//Configure joysticks \n\
	var joystickL = nipplejs.create({ \n\
		zone: document.getElementById('left'), \n\
		mode: 'static', \n\
		position: { left: '50%', top: '50%' }, \n\
		color: 'green', \n\
		size: 200, \n\
    fadeTime: 25, \n\
		lockX: true \n\
	}); \
	var joystickR = nipplejs.create({ \n\
		zone: document.getElementById('right'), \n\
		mode: 'static', \n\
		position: { left: '50%', top: '50%' }, \n\
		color: 'red', \n\
		size: 200, \n\
    fadeTime: 25, \n\
		lockY: true \n\
	}); \n\
	this.joystickR.on('move', (evt, data) => { \n\
		direction = data.direction.y; \n\
		//Mapping: Joystick will return direction as 'up'/'down', we need to map it to: 1->forward , 2->back \n\
		if (direction=='up') \n\
			direction=1; \n\
		else \n\
			direction=2;	 \n\
		//Mapping: Joystick will give us 0-100 , The range of speed is 0-255 , we need to make simple mapping \n\
		speed = Math.round(data.distance * 2.55) ; \n\
		console.log('direction', direction, speed); \n\
	}); \n\
	this.joystickR.on('end', (evt, data) => { \n\
		speed = 0 ; \n\
		console.log('released: speed=0'); \n\
	});	 \n\			
	this.joystickL.on('move', (evt, data) => { \n\
		steering_direction = data.direction.x; \n\
		//Mapping: Joystick will give us 0-100 , The range of the steering is 0-180 (90 angles on each side) \n\
		steering = Math.round(data.distance*0.9) ; \n\ 
		if (steering_direction=='left') \n\
			steering = steering + 90; \n\
		else	\n\
			steering= 90-steering; \n\
		console.log('steering_direction', steering_direction, steering); \n\ 
	});		\n\	
	this.joystickL.on('end', (evt, data) => { \n\
		steering = 90 ; \n\
		console.log('released: steering=90'); \n\
	});		\n\
	setInterval(function(){ \n\
		var message = '/drive?steering='+steering+'&speed='+speed+'&direction='+direction; \n\
    http.open('GET', message, true); \n\
    http.send(); \n\
	}, 200); \n\
</script> \n\
</body> \n\
</html>"; 
  return ptr;
}

void handle_OnConnect() {
  server.send(200, "text/html", SendHTML()); 
}


void setup() {
  
  Serial.begin(115200);

  //We are using permanent memory of the ESP32 to store the total number of laps robot drove
  //This value will be stored even if esp32 is restarted or turned off
  // Initialize EEPROM 
  EEPROM.begin(512);  
 

  //Read the total number of laps from flash memory (address=0)

  EEPROM.get(0,total_laps_counter);
  if (total_laps_counter<0)
  {
    //This means this memory cell was no used before and we need to initialize it with 0 count
    //EEPROM.put(address, value);
    EEPROM.put(0, 0);
    EEPROM.commit();

    total_laps_counter=0;
  }
    Serial.println("total_laps_counter");
    Serial.println(total_laps_counter);
    
    EEPROM.put(0, 501);
    EEPROM.commit();




  //Play Music

  Serial.println();
  Serial.println("Melody Player - Simple Play (blocking vs non-blocking play");
  
  Serial.println("Loading melody...");
  String notes[] = { "C4", "G3", "G3", "A3", "G3", "SILENCE", "B3", "C4" };
  // Load and play a correct melody
  Melody melody = MelodyFactory.load("Nice Melody", 175, notes, 8);
  
  Serial.println(String(" Title:") + melody.getTitle());
  Serial.println(String(" Time unit:") + melody.getTimeUnit());
  Serial.print("Play in blocking mode...");
  player.playAsync(melody);
  Serial.println("The end!");

  
  
  // set up LEDs 
  FastLED.addLeds<WS2812, 33, GRB>(leds, 16);
  FastLED.setBrightness(30);


  //LED Matrix Animation  
  for(int i = 0; i < 4; i++)  
      {   
          //The animation is lighting all the leds one after another (first loop)
          //Then it turns off all the leds one after another (second loop)
          for(int dot = 0; dot < 16; dot++) {
              leds[dot] = CRGB::Blue;
              FastLED.show();         
              delay(15);
          }      
          for(int dot = 0; dot < 16; dot++) {
              leds[dot] = CRGB::Black;
              FastLED.show();      
              delay(15);
          }   
      } 

  //After the animation we want to dispaly a simple shape, blue rectangle with green rectangle inside of it
  leds[0] = CRGB::Blue;   
  leds[1] = CRGB::Blue;   
  leds[2] = CRGB::Blue;   
  leds[3] = CRGB::Blue;   

  leds[4] = CRGB::Blue; 
  leds[8] = CRGB::Blue; 
  
  leds[12] = CRGB::Blue;   
  leds[13] = CRGB::Blue;   
  leds[14] = CRGB::Blue;   
  leds[15] = CRGB::Blue;    

  leds[7] = CRGB::Blue; 
  leds[11] = CRGB::Blue;  

  leds[5] = CRGB::Red; 
  leds[6] = CRGB::Red;  
  leds[9] = CRGB::Red; 
  leds[10] = CRGB::Red;    

  FastLED.show();
  
  //Wifi MODE
  /*
  // Replace with your network credentials
  const char* ssid     = "Kilometer_2.4G";
  const char* password = "kilo2005#";

  //const char* ssid     = "PlayRobotics";
  //const char* password = "";

  //const char* ssid     = "slom";
  //const char* password = "308765205";

  //WIFI
  Serial.println("Connecting to ");
  Serial.println(ssid);

  //connect to your local wi-fi network
  WiFi.begin(ssid, password);

  //check wi-fi is connected to wi-fi network
  delay(1000);
  while (WiFi.status() != WL_CONNECTED) {
  delay(500);
  Serial.print(".");
  }
  Serial.println("");
  Serial.print("Got IP: ");  Serial.println(WiFi.localIP());
  */


  //Acccess Point Mode

  /* Put IP Address details */
  IPAddress local_ip(192,168,1,4);
  IPAddress gateway(192,168,1,1);
  IPAddress subnet(255,255,255,0);

  const char* ssid     = "Playro";
  const char* password = "playrobotics";
  WiFi.persistent(false);
  WiFi.softAP(ssid, password);
  WiFi.softAPConfig(local_ip, gateway, subnet);
  delay(100);

  //Server 
  server.on("/", handle_OnConnect);

  server.on("/drive", []() {

    int steering = server.arg("steering").toInt();
    //Serial.printf("Steering %d\n", steering);
    steering_rc(steering);


    int speed = server.arg("speed").toInt();
    Serial.printf("Speed %d\n", speed);
    int direction = server.arg("direction").toInt();
    Serial.printf("direction %d\n", direction);  
    if (direction==1)
      drive_rc(true,  speed); //forward
    else  
      drive_rc(false, speed*-1); //back
    server.sendHeader("Access-Control-Allow-Origin", "*");
    String lap_time_response = "L|";
    lap_time_response  += formatted_lap_time;
    lap_time_response  += "|";
    lap_time_response  += formatted_best_lap_time;
    lap_time_response  += "|";
    lap_time_response  += total_laps_counter;
          
    server.send(200, "text/html", lap_time_response); 

  });

  server.begin();
  Serial.println("HTTP server started");


  irrecv.enableIRIn();  // Start the receiver
  while (!Serial)  // Wait for the serial connection to be established.
    delay(50);
  Serial.println();
  Serial.print("IRrecvDemo is now running and waiting for IR message on Pin ");
  Serial.println(kRecvPin);
  
  //activate ir send pin
  irsend.begin();

  //motors

  pinMode(MOTOR_PIN_IN1, OUTPUT);
  pinMode(MOTOR_PIN_IN2, OUTPUT);
  ledcSetup(PWMB_CHANNEL, PWM_FREQ, SPEED_RESOLUTION);
  ledcAttachPin(MOTOR_PIN_PWMB, PWMB_CHANNEL);

  //Servo
  myservo.setPeriodHertz(50);// Standard 50hz servo
  myservo.attach(SERVO_PIN, 500, 2400);   // attaches the servo on pin 18 to the servo object
 //Center the servo on startup (you can adjust servo_offset value to find the real center at the top of the file)
  myservo.write(servo_center+servo_offset);
  radio.begin();
  radio.openReadingPipe(1, robot);
  
  radio.setPALevel(RF24_PA_MAX);
  radio.startListening();

  //we need to initialize those variables with initial values, so our lap counter will work 
  int last_lap_time = millis();
  int last_sensor_lap_reading = millis();
 
}

void loop() {
/*
  //Ultrasonic
   long duration, inches, cm;
   pinMode(trigPin, OUTPUT);
   digitalWrite(trigPin, LOW);
   delayMicroseconds(2);
   digitalWrite(trigPin, HIGH);
   delayMicroseconds(10);
   digitalWrite(trigPin, LOW);
   pinMode(echoPin, INPUT);
   duration = pulseIn(echoPin, HIGH);
   cm = duration / 29 / 2;
  Serial.println(cm);
   if ((cm<50)&&(cm>0))
   {
      for(int i=0; i<16; i++) {
              leds[i] = CRGB::Red;
      }        
      
   }  
   else  
  {
      for(int i=0; i<16; i++) {
              leds[i] = CRGB::Black;
      }        
      
   }
   FastLED.show();  
  
*/
  

  server.handleClient();

  //Check if we got some ir value
  read_from_remote();
  //delay(10);
  if (irrecv.decode(&results)) {
    Serial.println("GOT IR:");
    Serial.print((uint32_t)results.value, HEX);
    Serial.println("");
    
    if((results.value == 0X9999)||(results.value == 0X8888)||(results.value == 0X7777)||(results.value == 0X6666))
    {
      //We know we got the correct IR , but we need to add some protections:

      //Problem #1
      //When the robot passes the finish line it might get multiple ir readings of corect code
      //We want to make sure there is at least 3 seconds gap between the pervious lap and the current one, 
      //Otherwise this is not really a new lap, but multiple readings of the sensor when the robot passes the finish line
      
      //Problem #2
      //If the robot is just standing at the finish line, sensor will continuesly receive the ir code
      //So not only we need 3 seconds gap between laps, we actually need 3 seconds between sensor readings 
      //(if we get correct IR code for lap, we need to make sure the last time we got this code before was 3 seconds ago or more )
      
      //You can change 2000 (2 seconds)to other value if you wish 

      //Another solution to this both those problems can be to use another IR led with different code as checkpoint 
      //(requires another lap counter or any other way to send ir code)

      current_time = millis();
      if((current_time - last_sensor_lap_reading) > 2000 )
      {
        //Time that the lap took
        float lap_time = current_time - last_lap_time;
        //From miliseconds to seconds        
        lap_time = lap_time / 1000;

        //Now our lap time is a float number , which can be something like this 12.3425235234
        //We will use a function to turn it into something like 12.34 (lenght=4, digits after deimal point=2)
        dtostrf(lap_time,4, 2, formatted_lap_time);

        Serial.println("=====Lap Time=====");
        Serial.println(lap_time);
        Serial.println("==================");

        //Set last lap time to current time because the lap is over and we are stating a new one
        last_lap_time = current_time;  

        //Now we can check if this is the best lap or just a regular lap
        //First lap will always be best because the default value of float best_lap_time is very high
        if (lap_time<best_lap_time)
        {
          //This is a best lap
          //We will send a best lap message to the remote control, so it can display it on screen
          //And we will play a special melody

          //update best lap time
          best_lap_time = lap_time;
          
          //Our lap time is already formated and stored in formatted_lap_time varaible
          //We will also fomat formatted_BEST_lap_time variable in order to save this value for later use
          dtostrf(best_lap_time,4, 2, formatted_best_lap_time);
          
          //Send the lap time to the remote control
          Serial.println("Sending --== BEST ==- lap time to remote");
          // "B" - Is a command which means - Best lap time
          String lap_time_to_send = "B";
          lap_time_to_send  += formatted_best_lap_time;

          //We will be using an array of chars to send the message
          char message_to_send[6];
          //We will conert our string to an array of chars, because it is required by the radio library
          lap_time_to_send.toCharArray(message_to_send, 50);
          
          //Set the radio to transmitter mode
          radio.stopListening();
          radio.openWritingPipe(remote_control);
          if (!radio.write(&message_to_send, sizeof(message_to_send)))
              Serial.println("----====Sending to remote control failed====-----");
          else
              Serial.println("----====SENT: REMOTE CONTROL");
          delay(5);
          //Now we ill send the same message to race controller
          radio.openWritingPipe(race_controller);   
          if (!radio.write(&message_to_send, sizeof(message_to_send)))
              Serial.println("----====Sending to race controller failed====-----");
          else
              Serial.println("----====SENT: RACE CONTROLLER");

          Serial.println("Loading melody...");
          String notes[] = { "C4", "G3", "C4"};
          // Load and play a correct melody
          Melody melody = MelodyFactory.load("Nice Melody", 175, notes, 3);
          player.playAsync(melody);      

          //Now display random color on LED ring
          //Pick a random number          
          int random_number = random(8);

          //We dont want to have the same color twice, so we need to make sure the random number is new
          //If it is not new we will just try another random
          while (last_random_number==random_number)
            random_number = random(8);
          //Now we surely got a new random number, lets save it for next time
          last_random_number = random_number;
          CRGB randomcolor;
          //Depending on the number we will choose a color
          switch (random_number) {
            case 1:
               randomcolor  =CRGB::Blue;   
              break;
            case 2:
               randomcolor  =CRGB::Red;   
              break;
            case 3:
               randomcolor  =CRGB::Purple;   
              break;
            case 4:
               randomcolor  =CRGB::Brown;   
              break;
            case 5:
               randomcolor  =CRGB::Green;   
              break;
            case 6:
               randomcolor  =CRGB::Yellow;                 
              break;                                          
            case 7:
               randomcolor  =CRGB::Pink;                 
            default:
               randomcolor  =CRGB::White;   
              break;
          }
          
          //Paint all 16 leds of our ring in this color
          for(int i=0; i<16; i++) {
              leds[i] = randomcolor;
              //FastLED.show();
          }        
          FastLED.show();
 
          delay(5);
          //Once finished to send the message we need to continue listening to remote control commands asap
          radio.startListening();
        }
        else
        {
          //NOT a best lap
          //We will send a lap message to the remote control, so it can display it on screen
          //And we will play a melody

          //Send the lap time to the remote control
          Serial.println("Sending lap time to remote");
          // "L" - Is a command which means - Lap time
          String lap_time_to_send = "L";
          lap_time_to_send  += formatted_lap_time;

          //We will be using an array of chars to send the message
          char message_to_send[6];
          //We will conert our string to an array of chars, because it is required by the radio library
          lap_time_to_send.toCharArray(message_to_send, 50);
          
          //Set the radio to transmitter mode
          radio.stopListening();
          radio.openWritingPipe(remote_control);
          delay(5);
          if (!radio.write(&message_to_send, sizeof(message_to_send)))
              Serial.println("----====Sending to remote control failed====-----");
          else
              Serial.println("----====SENT: REMOTE CONTROL");
          delay(5);
          //Now we ill send the same message to race controller
          radio.openWritingPipe(race_controller);   
          if (!radio.write(&message_to_send, sizeof(message_to_send)))
              Serial.println("----====Sending to race controller failed====-----");
          else
              Serial.println("----====SENT: RACE CONTROLLER");

          Serial.println("Loading melody...");
          String notes[] = { "C5", "G4"};
          // Load and play a correct melody
          Melody melody = MelodyFactory.load("Nice Melody", 175, notes, 2);
          player.playAsync(melody);                 
          delay(5);
          //Once finished to send the message we need to continue listening to remote control commands asap
          radio.startListening();
        }
        
        //Update our total laps counter (number of laps the robot ever drove);
        total_laps_counter++;
        //Write this value to flash memory for permanent storage
        EEPROM.put(0, total_laps_counter);
        EEPROM.commit();

        
      }
      //Doesn't metter if we counted this lap or not we need to restart this variable to solve problem #2 explained above
      last_sensor_lap_reading = current_time;


        
    }
    
    irrecv.resume();  // Receive the next value
  }



}
 

אם לאחר העלאת הקוד פליירו לא נוסע ישר (כלומר פליירו נוטה לאחד הצדדים גם כאשר אתם נוסעים קדימה בלי לגעת בהיגוי כלל) , ניתן לשחק עם הערך של המשתנה המוגדר בתחילת הקוד:

servo_center = 90

תפקידו של משתנה זה הוא להגדיר את זווית המרכז של הסרבו.

במקום 90, ניתן לתת למשתנה הזה ערכים בטווח בין 85 ל95 (אם פיירו נוטה ימינה, נסו לתת למשתנה זה ערך 95, אם הוא נוטה שמאלה, נסו ערך 85). לא לשכוח להעלות את הקוד כל פעם שמחליפים ערך.

חלק 7: הפעלת חיישן המרחק של פליירו