Hardware components | ||||||
| × | 5 | ||||
| × | 4 | ||||
| × | 56 | ||||
| × | 4 | ||||
| × | 4 | ||||
| × | 4 | ||||
| × | 4 | ||||
| × | 4 | ||||
| × | 4 | ||||
Hand tools and fabrication machines | ||||||
|
https://photos.app.goo.gl/bTA0y0GDYsPbmkWq1
Some images...
SomeVideos...
Taekwondo training (theWorld Champion)
https://photos.app.goo.gl/2n0t39ThD1HRY7qz2
Personal trainer and cell phone
https://photos.app.goo.gl/KoA6mo5NUn9zrMAD2
Tennis Training
https://photos.app.goo.gl/D83am7ToD8Yj4oo22
https://photos.app.goo.gl/XlMRqd4BkXRQL6rw2
Fitness Sport Training
https://photos.app.goo.gl/9vXE4j9x8HDfFQYj2
There are 4 (or more), lights, one of them is the "server"
The server holds 2 nodeMCU boards one is the server itself (the WiFi hotspot) and the other one is the sensor light, all other lights holds only 1 nodeMCU board each.
They are interconnected without wires via WiFi.
The user connects with the cell phone to this same server via WiFi.
Configure the system with parameters such as ignition time, delay, range of activation distances, and type of sequence (sequential or random).
All settings are available in a very simple screen shown in the cell phone.
No need to install software on the phone, just access a link in the browser to see the screen.
Connect the cell phone to the server via WiFi, browse to http://192.168.4.1 use TrainerLights named WiFi spot and password 1234567890
TrainerLights server code
C/C++// author: Ricardo Lerch @RickLerch
// ricardo.lerch@gmail.com
#include <ESP8266mDNS.h>
#include <DNSServer.h> //Local DNS Server used for redirecting all requests to the configuration portal
#include <WebSocketsServer.h>
#include <ESP8266WebServer.h> //Local WebServer used to serve the configuration portal
#include <WiFiManager.h> //https://github.com/tzapu/WiFiManager WiFi Configuration Magic
#include <TaskScheduler.h>
#include <ArduinoJson.h>
#include <LinkedList.h>
#include <Time.h>
#include <pgmspace.h>
extern "C" {
#include "user_interface.h"
}
#define LEDA D7 // led testigo
#define LEDE D6 // led estimulo de salida
#define TRIGGER_PIN D5 // pulsador uso multiple
#define TRIGGER D1 // trigger ultrasonico
#define ECHO D2 // echo ultrasonico
const char htmlHeader[] PROGMEM = R"rawliteral(
<!DOCTYPE html><html lang=en><head><title>TrainerLights</title><meta name=viewport content="width=device-width,initial-scale=1,minimum-scale=1"><meta charset=UTF-8></head><body><div class=maincontainer><div class=topmenubar><div class=logo><a href=/ ><h1>TrainerLights</h1></a></div></div><div class=topmargin></div><div class=success-message></div><div class=main-wrapper>
)rawliteral";
const char htmlFooter[] PROGMEM = R"rawliteral(
</div><div class=bottommargin></div><div class=footerbar><a href=/ >© TrainerLights</a> | <a href=/ >TrainerLights.cc</a></div><script>function listSensors(){connection.send('{"type":"list_sensors"}')}function restartESP(){var e='{"type":"restart"}';document.getElementById("restartESP").checked&&(document.getElementById("restartESP").checked=!1,console.log(e),connection.send(e))}function sendConfig(){var e='{"type":"config"';e+=',"tmode":"'+document.getElementById("tmode").value+'"',e+=',"min_delay":"'+document.getElementById("min_delay").value+'"',e+=',"max_delay":"'+document.getElementById("max_delay").value+'"',e+=',"mim_timeout":"'+document.getElementById("mim_timeout").value+'"',e+=',"max_timeout":"'+document.getElementById("max_timeout").value+'"',e+=',"accelerate_delay_percent":"'+document.getElementById("accelerate_delay_percent").value+'"',e+=',"accelerate_delay_per_seconds":"'+document.getElementById("accelerate_delay_per_seconds").value+'"',e+=',"accelerate_timeout_percent":"'+document.getElementById("accelerate_timeout_percent").value+'"',e+=',"accelerate_timeout_per_seconds":"'+document.getElementById("accelerate_timeout_per_seconds").value+'"',e+=',"min_detection_range":"'+document.getElementById("min_detection_range").value+'"',e+=',"max_detection_range":"'+document.getElementById("max_detection_range").value+'"',e+="}",console.log(e),connection.send(e)}function startTest(){connection.send('{"type":"start_test"}')}function stopTest(){connection.send('{"type":"stop_test"}')}function updateTimer(){d=new Date,n=d.getTime(),tm=n-startTime+timeOffset;var e,t=Math.floor(tm/1e3/60/60),m=Math.floor(tm/6e4)%60,o=(o=tm/1e3%60).toString().match(/^-?\d+(?:\.\d{0,-1})?/)[0];e=(e=("00"+tm).slice(-3)/10).toString().match(/^-?\d+(?:\.\d{0,-1})?/)[0],m=(m<10?"0":"")+m,o=(o<10?"0":"")+o,e=(e<10?"0":"")+e,0==(t+=t>0?":":"")&&(t=""),document.getElementById("timer").innerHTML=t+m+":"+o+"<small>."+e+"</small>"}function pauseTimer(){timeOffset=tm,clearInterval(timerInterval),document.getElementById("startTimer").href="javascript:startTimer();",document.getElementById("startTimer").innerHTML="Iniciar",stopTest()}function startTimer(){d=new Date,n=d.getTime(),startTime=n,clearInterval(timerInterval),timerInterval=setInterval(updateTimer,10),document.getElementById("startTimer").href="javascript:pauseTimer();",document.getElementById("startTimer").innerHTML="Detener",startTest()}function resetTimer(){timeOffset=0,tm=0,clearInterval(timerInterval),document.getElementById("timer").innerHTML="00:00<small>.00</small>",document.getElementById("startTimer").href="javascript:startTimer();",document.getElementById("startTimer").innerHTML="Iniciar",stopTest()}var loc;loc=location.hostname,"localhost"==location.hostname&&(loc="192.168.4.1");var connection=new WebSocket("ws://"+loc+":81/",["arduino"]);connection.onopen=function(){var e='{"type":"app_connected"';e+=',"current_time":"'+(new Date).getTime()+'"',e+="}",connection.send(e)},connection.onerror=function(e){console.log("WebSocket Error ",e)},connection.onmessage=function(e){console.log("Server: ",e.data);var t,n="";if("sensor_list"==(t=JSON.parse(e.data)).type){for(var m=0,o=t.sensors.length;m<o;++m){t.sensors[m];console.log("Sensor IP: ",t.sensors[m].ip+" | num: "+t.sensors[m].num),n+='<div class="sensor"><h1>'+(m+1)+"</h1></div>"}document.getElementById("sensors").innerHTML=n}"stats"==t.type&&(document.getElementById("test_score").innerHTML=t.test_score,document.getElementById("test_errors").innerHTML=t.test_errors,document.getElementById("max_distance").innerHTML=t.max_distance,document.getElementById("min_distance").innerHTML=t.min_distance,document.getElementById("avg_distance").innerHTML=t.avg_distance,document.getElementById("max_response_time").innerHTML=t.max_response_time,document.getElementById("min_response_time").innerHTML=t.min_response_time,document.getElementById("avg_response_time").innerHTML=t.avg_response_time)};var timerInterval,d=new Date,n=d.getTime(),startTime=n,timeOffset=0,tm=0</script></body></html>
)rawliteral";
const char htmlCss[] PROGMEM = R"rawliteral(
<style>/* CSS Bootstrap Customizations Ricardo Lerch @RickLerch */body{width:100%;padding:0;margin:0;font-size:14px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;color:#333;background-color:#fff;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}table{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}hr{margin-top:2px;margin-bottom:2px;border:0;border-top:1px solid #CCC}h1,h2,h3{margin-top:5px;margin-bottom:5px;font-weight:700}h1{font-size:20px}h2{font-size:18px}h3{font-size:16px}a,a:visited{color:#428bca;text-decoration:none}a:active,a:hover{outline:0;text-decoration:none;color:#516496}a.btn,a.btn:visited,a.btn:hover,a.btn:active{color:#FFF;text-decoration:none}ul{margin:0}.big{font-size:60px;margin:auto}.mid{font-size:40px;margin:auto}.red{color:#982713}.green{color:#408114}.blue{color:#144881}input{width:100%;padding:12px 4px;font-size:18px;border:2px solid #ccc;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;outline:none;margin:0;margin-bottom:10px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}textarea{resize:none;width:100%;padding:12px 4px;font-size:18px;line-height:30px;border:2px solid #ccc;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;outline:none;margin:0;margin-bottom:10px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=range]{-webkit-appearance:none;margin:18px 0;width:100%;border:none}input[type=range]::-webkit-slider-runnable-track{height:30%;cursor:pointer;animate:.2s;background:-webkit-linear-gradient(top,#555,#444,#222,#444,#555);border-radius:3px;border:2px solid #010101}input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;border:1px solid #000;height:40px;width:60px;border-radius:5px;background:-webkit-linear-gradient(left,#AAA,#BBB,#BBB,#BBB,#CCC,#AAA,#CCC,#AAA,#CCC,#AAA,#BBB,#BBB,#BBB,#AAA);cursor:pointer;border:2px solid #010101;margin-top:-20px}input[type=radio],input[type=checkbox]{display:none}label:before{content:"";display:inline-block;width:50px;height:50px;margin-right:10px;position:relative;left:0;box-sizing:border-box}.slabelo:before{border-radius:15px}.slabel:before{border-radius:4px;background:#FFF;border:2px solid #ccc}.checkbox label{margin-bottom:10px}.checkbox label:before{border-radius:3px}input[type=radio] + label:before{border-radius:25px}input[type=radio]:checked + label:before{content:"\2022";color:green;font-size:100px;text-align:center;font-weight:700;line-height:48px}input[type=checkbox]:checked + label:before{content:"\2713";text-shadow:1px 1px 1px rgba(0,0,0,.2);font-size:40px;font-weight:700;color:green;text-align:center;line-height:50px}.sensor{width:30%;float:left;text-align:center;margin:1%;border:#000 1px solid;background:#eaffe4}.sensors{width:100%;display:inline-block;text-align:center;border:#000 1px solid}.entire{width:100%;padding:1%;box-sizing:border-box}.half{width:47%;float:left;padding:1%}.third{width:31%;float:left;padding:1%}.twothird{width:64%;float:left;padding:1%}.cont{width:100%;display:inline-block;text-align:center}.contone{width:98%;display:inline-block;text-align:center;padding:1%}.main-wrapper{display:block;padding:10px}.left-wrapper{background-color:#FAFAFA;display:none}.edit-controls{padding:15px}.form-container{width:90%;margin:auto}.detail-title{float:left}.topmenubar,.footerbar{color:#FFF;width:100%;background:#16191B;color:#1f3853;font-size:12px;font-weight:400;z-index:2000;position:relative;display:inline-block;box-sizing:border-box}.topmargin{display:inline-block;height:45px;width:100%}.bottommargin{display:inline-block;height:100px;width:100%}.topmenubar{top:0;left:0;height:45px;position:fixed}.footerbar{bottom:0;vertical-align:middle;text-align:center;z-index:auto}.topmenubar a,.footerbar a{color:#FFF}.topmenubar a:hover,.footerbar a:hover{color:#CCC;text-decoration:none}.logo{display:inline-block;float:left;margin-right:15px;color:#fff;font-size:13px;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.5);margin-left:15px;margin-top:10px}.searchbar{display:inline-block;width:100%;float:left;margin-top:15px}.feed{position:relative;display:block;width:100%;background:#fff;border-bottom:1px solid #CCC;min-height:136px;overflow:hidden}.view-detail{position:absolute;right:5px;bottom:5px}.maincontainer{width:100%;box-sizing:border-box;display:inline-block}.option small{font-weight:400}.option{position:relative;font-size:18px;font-weight:700;padding:15px 5px;margin:0;background-color:#FFF;border-style:solid;border-width:1px;border-color:#BBB transparent transparent;cursor:pointer;vertical-align:middle}.opt,.opt:hover,.opt:visited{color:#000}.option:hover,.feed:hover{background-color:#F7F8FF}.option:hover .circ{background:#3a4951}.options-top-bar{width:1102px;padding-left:5px;background:#FFF;box-sizing:border-box;display:inline-block;transition:.5s}.options-top-bar-title{float:left;padding-top:5px}.option-price-title{float:left;text-align:right;margin-left:10px;width:40%}.circ{float:right;background-color:#FFF;border-style:solid;border-width:2px;border-color:#BBB;border-radius:12px;width:20px;height:20px;position:absolute;right:5px;top:14px}.optdesc{float:left}.optsubtitle{margin-top:5px}h1 small,h2 small,h3 small{color:#929292}.selector{width:100%;font-size:18px;padding:10px 0;background:#FFF;border:2px solid #ccc;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;outline:none;margin:0;margin-bottom:10px;height:50px}.btn{outline:0;display:inline-block;margin-bottom:0;font-weight:700;text-align:center;vertical-align:middle;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:12px;font-size:14px;line-height:1.428571429;border-radius:4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.btn-block{display:block;width:100%;padding-left:0;padding-right:0}.btn-success{background:green;color:#FFF}.btn-danger{background:#970101;color:#FFF}.btn-danger:hover,.btn-danger:active,.btn-danger.active{background:#B32626;color:#FFF}.btn-success:hover,.btn-success:active,.btn-success.active{background:#409E40;color:#FFF}.btn-blue{background:#0007A7;color:#FFF}.btn-blue:hover,.btn-blue:active,.btn-blue.active{background:#516496;color:#FFF}.btn-lblue{background:#2FC2EF;color:#FFF}.btn-lblue:hover,.btn-lblue:active,.btn-lblue.active{background:#68D5F7;color:#FFF}.btn-red{background:#970101;color:#FFF}.btn-red:hover,.btn-red:active,.btn-red.active{background:#B32626;color:#FFF}</style>
)rawliteral";
const char htmlContent[] PROGMEM = R"rawliteral(
<div class=entire><h1>Tiempo de la prueba</h1><div class=cont><h1 class=big id=timer style=font-family:monospace>00:00.<small>00</small></h1></div><br><br><div class=cont><div class=twothird><a class="btn btn-block btn-lg btn-success"href=javascript:startTimer(); id=startTimer>Iniciar</a></div><div class=third><a class="btn btn-block btn-lg btn-danger"href=javascript:resetTimer();>Reset</a></div></div><hr><div class=cont><div class=half><h1>Puntos</h1><h1 class="big green"id=test_score>0</h1></div><div class=half><h1>Errores</h1><h1 class="big red"id=test_errors>0</h1></div></div><hr><h1>Tiempos de reacción (ms)</h1><div class=cont><div class=third><h1>Promedio</h1><h1 class="mid blue"id=avg_response_time>0</h1></div><div class=third><h1>Mínimo</h1><h1 class="mid green"id=min_response_time>0</h1></div><div class=third><h1>Máximo</h1><h1 class="mid red"id=max_response_time>0</h1></div></div><hr><h1>Distancias de reacción (cm)</h1><div class=cont><div class=third><h1>Promedio</h1><h1 class="mid blue"id=avg_distance>0</h1></div><div class=third><h1>Mínimo</h1><h1 class="mid green"id=min_distance>0</h1></div><div class=third><h1>Máximo</h1><h1 class="mid red"id=max_distance>0</h1></div></div></div><hr><br><br><div class=entire><h2>Modos de entrenamiento</h2><select class=selector id=tmode><option value=random>Al Azar<option value=sequence>En Secuencia</select></div><div class=entire style=display:none><h2>Modos de estímulo</h2><select class=selector id=stimulus_mode><option value=on>Luz Encendida<option value=blink>Parpadea<option value=blink_once>Parpadea una vez</select></div><div class=entire style=display:none><h2>Tiempo de la prueba</h2>Tiempo de la prueba en segundos <input id=time_test type=number value=0></div><div class=entire><h2>Delay</h2>Elije un valor de delay (ms) al azar entre:</div><div class=cont><div class=half><input id=min_delay type=number value=0></div><div class=half><input id=max_delay type=number value=0></div></div><div class=entire><h2>Timeout</h2>Elije un valor de timeout (ms) al azar entre:</div><div class=cont><div class=half>Mínimo <input id=mim_timeout type=number value=1000></div><div class=half>Máximo <input id=max_timeout type=number value=1000></div></div><div class=entire style=display:none><h2>Acelerar delay</h2>Acelera el delay x% por cada x seg</div><div class=cont style=display:none><div class=half>% <input id=accelerate_delay_percent type=number value=0></div><div class=half>Segundos <input id=accelerate_delay_per_seconds type=number value=0></div></div><div class=entire style=display:none><h2>Acelerar Timeout</h2>Acelera el timeout x% por cada x seg</div><div class=cont style=display:none><div class=half>% <input id=accelerate_timeout_percent type=number value=0></div><div class=half>Segundos <input id=accelerate_timeout_per_seconds type=number value=0></div></div><div class=entire><h2>Rango de detección</h2>Detecta un objeto entre mínimo y máximo (cm)</div><div class=cont><div class=half>Mínimo <input id=min_detection_range type=number value=0></div><div class=half>Máximo <input id=max_detection_range type=number value=50></div></div><div class=entire><a class="btn btn-block btn-lg btn-success"href=javascript:sendConfig();>Configurar</a></div><br><br><hr><br><br><div class=entire><a class="btn btn-block btn-lg btn-success"href=javascript:listSensors();>Listar Sensores</a><h1>Sensores Conectados:</h1><div class=sensors id=sensors></div></div><br><br><hr><br><br><div class=cont style=display:none><div class=third><center><input id=restartESP type=checkbox value=0><label class=slabel for=restartESP></label></center></div><div class=twothird><a class="btn btn-block btn-lg btn-danger"href=javascript:restartESP();>Reiniciar sistema</a></div></div>
)rawliteral";
// configuration
String tmode = "random";
int min_delay = 0;
int max_delay = 0;
int mim_timeout = 1000;
int max_timeout = 1000;
int accelerate_delay_percent = 0;
int accelerate_delay_per_seconds = 0;
int accelerate_timeout_percent = 0;
int accelerate_timeout_per_seconds = 0;
int min_detection_range = 0;
int max_detection_range = 50;
int timeout = 1000;
int tdelay = 0;
bool isTesting; // si esta dentro del tiempo de prueba mada los datos a la app
int currentSensor;
// test variables
int test_score = 0;
int test_errors = 0;
int max_distance = 0;
int min_distance = 9999;
int avg_distance = 0;
int max_response_time = 0;
int min_response_time = 9999;
int avg_response_time = 0;
int test_count = 0;
time_t app_time = 0;
Scheduler ts;
void MeasureDistance();
void StimulusTimeout();
// Tasks
// Task tMeasureDistance(30, TASK_FOREVER, &MeasureDistance, &ts, true);
Task tStimulusTimeout(2000, TASK_ONCE, &StimulusTimeout, &ts, false);
ESP8266WebServer webServer(80);
WebSocketsServer webSocket = WebSocketsServer(81);
const char* apName = "TrainerLights";
const char* apPassword = "1234567890";
const char* host = "";
// class para la lista de sensores conectados
class Sensor {
public:
IPAddress ip; // ip del sensor
bool isEnabled; // esta enabled
uint8_t num;
};
LinkedList<Sensor*> sensorList = LinkedList<Sensor*>();
uint8_t appConnected = NULL;
// class para guardar el sensor que se esta ejecutando el estimulo
class sensorStimulating {
public:
IPAddress ip; // ip del sensor
};
// esto se ejecuta cuando una estacion se desconecta
WiFiEventHandler stationDisconnectedHandler;
void onStationDisconnected(const WiFiEventSoftAPModeStationDisconnected& evt) {
Serial.println("** Station disconnected: **");
}
bool stimulating = false;
int lastSensor = 1000; // last sensor stimulating in sensorList
void setup() {
delay(100);
pinMode(TRIGGER_PIN, INPUT);
pinMode(LEDE , OUTPUT);
pinMode(LEDA , OUTPUT);
pinMode(TRIGGER, OUTPUT);
pinMode(ECHO, INPUT);
Serial.begin (115200);
Serial.setDebugOutput(false);
Serial.println(" ");
Serial.println(" ");
Serial.println(" ");
Serial.println("*****************************");
Serial.println("* *");
Serial.println("* TrainerLights *");
Serial.println("* By: Ricardo Lerch *");
Serial.println("* ricardo.lerch@gmail.com *");
Serial.println("* *");
Serial.println("*****************************");
Serial.println(" ");
Serial.println(" ");
Serial.println(" ");
// Serial.end();
// https://github.com/esp8266/Arduino/issues/570
// aumenta el maximo de conexiones de 4(default) a 32
struct softap_config config;
wifi_softap_get_config(&config); // Get config first.
config.max_connection = 32; // how many stations can connect to ESP8266 softAP at most.
wifi_softap_set_config(&config);// Set ESP8266 softap config
// no duerme el wifi
wifi_set_sleep_type(NONE_SLEEP_T);
//
//
// WiFi.disconnect();
WiFi.mode(WIFI_AP_STA);
// definition
// bool softAP(const char* ssid, const char* passphrase = NULL, int channel = 1, int ssid_hidden = 0, int max_connection = 4);
// WiFi.softAP(apName, apPassword, 1, 0, 32);
WiFi.softAP(apName, apPassword);
Serial.print("AP IP address ");
Serial.println(WiFi.softAPIP());
// start webSocket server
webSocket.begin();
webSocket.onEvent(webSocketEvent);
if(MDNS.begin("esp8266")) {
Serial.println("MDNS responder started");
}
// handle index
webServer.on("/", []() {
// send index.html
webServer.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
webServer.sendHeader("Pragma", "no-cache");
webServer.sendHeader("Expires", "-1");
webServer.setContentLength(strlen_P(htmlHeader) + strlen_P(htmlCss) + strlen_P(htmlContent) + strlen_P(htmlFooter));
webServer.send(200, "text/html", "");
webServer.sendContent_P(htmlHeader);
webServer.sendContent_P(htmlCss);
webServer.sendContent_P(htmlContent);
webServer.sendContent_P(htmlFooter);
webServer.client().stop();
});
// start webServer server
webServer.begin();
// Add service to MDNS
MDNS.addService("http", "tcp", 80);
MDNS.addService("ws", "tcp", 81);
// avisa cuando un cliente se desconecta del ap
stationDisconnectedHandler = WiFi.onSoftAPModeStationDisconnected(&onStationDisconnected);
}
void loop() {
webSocket.loop();
webServer.handleClient();
ts.execute();
// ejecutar programa de entrenamiento
// si no esta estimulando elige un sensor al azar y lo pone on
Sensor *s;
String a;
if (!stimulating){
// stimulustTimeout = stimulustTimeout - stimulustTimeout * 0.009;
// elige un sensor al azar
if (tmode == "random") {
currentSensor = random(0,sensorList.size());
}else{
currentSensor++;
if (currentSensor >= sensorList.size()){
currentSensor = 0;
}
}
timeout = random(mim_timeout, max_timeout+1);
tdelay = random(min_delay, max_delay+1);
if (timeout < 100){
timeout = 100;
}
if (tdelay < 0){
tdelay = 0;
}
if (currentSensor != lastSensor && sensorList.size() > 0){ // solo elije uno diferente al ultimo
lastSensor = currentSensor;
s = sensorList.get(currentSensor);
if (s){
a = "{\"type\":\"stimulus\"";
a += ",\"timeout\":\"" + String(timeout) + "\"";
a += ",\"delay\":\"" + String(tdelay) + "\"";
a += ",\"min_detection_range\":\"" + String(min_detection_range) + "\"";
a += ",\"max_detection_range\":\"" + String(max_detection_range) + "\"";
a += ",\"light\":{\"mode\":\"on\",\"intensity\":\"100\",\"color\":{\"R\":\"255\",\"G\":\"255\",\"B\":\"255\"}}}";
webSocket.sendTXT(s->num, a);
// pone el timeout del sensor mas 1000ms
tStimulusTimeout.setInterval(tdelay+timeout+1000);
tStimulusTimeout.restartDelayed();
stimulating = true;
Serial.println(a);
}
}
}
}
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {
IPAddress ip = webSocket.remoteIP(num);
switch(type) {
case WStype_DISCONNECTED:
Serial.printf("[%u] Disconnected!\n", num);
break;
case WStype_CONNECTED:
Serial.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
// send message to client
webSocket.sendTXT(num, "{\"status\":\"connected\"}");
break;
case WStype_TEXT:
Serial.printf("[%u] get Text: %s\n", num, payload);
// recibe json desde el js en la pagina de control
StaticJsonDocument<512> root;
auto error = deserializeJson(root, payload);
if (error) {
Serial.print(F("deserializeJson() failed with code "));
Serial.println(error.c_str());
Serial.println("JSON parsing failed!");
}
const char* jtype = root["type"];
Serial.print("recibe: ");
Serial.println(jtype);
Sensor *s;
int i;
String a="";
// ************************************
// List Sensors
if (String(jtype) == String("list_sensors")){ // lista sensores conectados
Serial.println("List Sensors");
// devuelve un json con la lista de sensores conectados
a="{\"type\":\"sensor_list\",\"sensors\":[";
for (i = 0; i < sensorList.size(); i++){
// Get sensors from list
s = sensorList.get(i);
Serial.println(s->ip);
// arma el json con la ip
if (i){
a +=",";
}
a += "{\"id\":\"" + String(s->ip[0]) + String(s->ip[1]) + String(s->ip[2]) + String(s->ip[3]) + "\"";
a += ",\"ip\":\"" + String(s->ip[0]) + "." + String(s->ip[1]) + "." + String(s->ip[2]) + "." + String(s->ip[3]) + "\"";
a += ",\"num\":\"" + String(s->num) + "\"";
a += ",\"state\":\"on\"}";
// String(WiFi.localIP()[0]) + "." + String(WiFi.localIP()[1]) + "." + String(WiFi.localIP()[2]) + "." + String(WiFi.localIP()[3])
// a.concat("\"}");
// ************************************
}
a +="]}";
char jstr[a.length()+1];
a.toCharArray(jstr, a.length()+1);
// send websocket packet
webSocket.sendTXT(num, a);
}
// ************************************
// recibe un nuevo sensor via websocket
if (String(jtype) == String("sensor")){
Serial.println("SENSOR");
Sensor *newSensor = new Sensor();
newSensor->ip = ip;
newSensor->isEnabled = true;
newSensor->num = num;
bool sensorExists = false;
Sensor *s;
int i;
for(i = 0; i < sensorList.size(); i++){
// Get sensors from list
s = sensorList.get(i);
if (s->ip == newSensor->ip){ // el sensor ya existe en la lista lo deja ahi
sensorExists = true;
}
}
if (!sensorExists){
sensorList.add(newSensor);
}
Serial.println("Sensores conectados: ");
// manda la lista de sensores conectados al serial
for (i = 0; i < sensorList.size(); i++){
// Get sensors from list
s = sensorList.get(i);
Serial.println(s->ip);
}
}
// ************************************
// response
if (String(jtype) == String("response")){
Serial.println("SENSOR response");
tStimulusTimeout.disable();
stimulating = false;
// si esta en modo test y la app_connected manda info a la app conectada
if (isTesting){
int test_error = root["error"];
if (test_error){
test_errors++;
}else{
test_score++;
if (test_count){ // solo la primer cuenta
avg_response_time = (avg_response_time + int(root["time"])) / 2;
avg_distance = (avg_distance + int(root["distance"])) / 2;
}else{
test_score = 0;
test_errors = 0;
max_distance = int(root["distance"]);
min_distance = int(root["distance"]);
avg_distance = int(root["distance"]);
max_response_time = int(root["time"]);
min_response_time = int(root["time"]);
avg_response_time = int(root["time"]);
}
if (max_response_time < int(root["time"])){max_response_time = int(root["time"]);}
if (min_response_time > int(root["time"])){min_response_time = int(root["time"]);}
if (max_distance < int(root["distance"])){max_distance = int(root["distance"]);}
if (min_distance > int(root["distance"])){min_distance = int(root["distance"]);}
}
// si esta conectada la app manda la info via websocket
if (appConnected != NULL){
String a;
a = "{\"type\":\"stats\"";
a += ",\"test_score\":\"" + String(test_score) + "\"";
a += ",\"test_errors\":\"" + String(test_errors) + "\"";
a += ",\"max_distance\":\"" + String(max_distance) + "\"";
a += ",\"min_distance\":\"" + String(min_distance) + "\"";
a += ",\"avg_distance\":\"" + String(avg_distance) + "\"";
a += ",\"max_response_time\":\"" + String(max_response_time) + "\"";
a += ",\"min_response_time\":\"" + String(min_response_time) + "\"";
a += ",\"avg_response_time\":\"" + String(avg_response_time) + "\"";
a += "}";
webSocket.sendTXT(appConnected, a);
Serial.println(a);
}
test_count++;
}
}
// ************************************
// start_test
if (String(jtype) == String("start_test")){
Serial.println("start_test");
isTesting = true;
// test variables
test_score = 0;
test_errors = 0;
max_distance = 0;
min_distance = 9999;
avg_distance = 0;
max_response_time = 0;
min_response_time = 9999;
avg_response_time = 0;
test_count = 0;
}
// ************************************
// stop_test
if (String(jtype) == String("stop_test")){
Serial.println("stop_test");
isTesting = false;
}
// ************************************
// app_connected
if (String(jtype) == String("app_connected")){
appConnected = num;
app_time = root["current_time"];
Serial.print("current_time: ");
Serial.println(app_time);
Serial.print("app_connected: ");
Serial.println(appConnected);
}
// ************************************
// restart
if (String(jtype) == String("restart")){
for (i = 0; i < sensorList.size(); i++){
s = sensorList.get(i);
a="{\"type\":\"restart\"}";
webSocket.sendTXT(s->num, a);
}
Serial.println("RESET");
ESP.reset();
}
// ************************************
// config
if (String(jtype) == String("config")){ // lista sensores conectados
Serial.println("Get Configuration");
const char* ctmode = root["tmode"];
tmode = String(ctmode);
min_delay = root["min_delay"];
max_delay = root["max_delay"];
mim_timeout = root["mim_timeout"];
max_timeout = root["max_timeout"];
accelerate_delay_percent = root["accelerate_delay_percent"];
accelerate_delay_per_seconds = root["accelerate_delay_per_seconds"];
accelerate_timeout_percent = root["accelerate_timeout_percent"];
accelerate_timeout_per_seconds = root["accelerate_timeout_per_seconds"];
min_detection_range = root["min_detection_range"];
max_detection_range = root["max_detection_range"];
Serial.println(max_detection_range);
}
break;
}
}
void StimulusTimeout(){
// el ultimo sensor estimulado no respondio lo saca de la lista
sensorList.remove(lastSensor);
lastSensor = 1000;
stimulating = false;
Serial.println("StimulusTimeout");
}
TrainerLights client code
C/C++#include <ESP8266WiFi.h>
#include <TaskScheduler.h>
#include <ArduinoJson.h>
#include <WebSocketsClient.h>
//#include <ESP8266HTTPClient.h>
extern "C" {
#include "user_interface.h"
}
#define LEDA D8 // D7 // led testigo BUILTIN_LED = 16;
#define LEDE D6 // led estimulo de salida
#define LEDERR D7 // led estimulo de salida
#define LEDOK D8 // led estimulo de salida
#define TRIGGER_PIN D5 // pulsador uso multiple
#define TRIGGER D1 // trigger ultrasonico
#define ECHO D2 // echo ultrasonico
bool webSocketConnected = false;
WebSocketsClient webSocket; //Client
//HTTPClient http;
String a;
int timeout;
int tdelay;
int min_detection_range = 0;
int max_detection_range = 50;
unsigned long time_start = millis();
unsigned long time_now = millis();
Scheduler ts;
void MeasureDistance();
void StimulusTimeout();
void StimulusStart();
void LedsOff();
// Tasks
Task tMeasureDistance(30, TASK_FOREVER, &MeasureDistance, &ts, true);
Task tStimulusStart(0, TASK_ONCE, &StimulusStart, &ts, false);
Task tStimulusTimeout(10000, TASK_ONCE, &StimulusTimeout, &ts, false);
Task tLedsOff(100, TASK_ONCE, &LedsOff, &ts, false);
//char ssid[] = "Speedy-16DD33"; // "NeuroTrainer";
//char password[] = "4314110680";
char ssid[] = "TrainerLights"; // "TrainerLights";
char password[] = "1234567890";
bool stimulating = false;
void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
switch(type) {
case WStype_DISCONNECTED:
Serial.printf("[WSc] Disconnected!\n");
webSocket.begin("192.168.4.1", 81, "/");
webSocketConnected = false;
break;
case WStype_CONNECTED:
Serial.printf("[WSc] Connected to url: %s\n", payload);
webSocketConnected = true;
// arma el json con la ip
a = "{\"type\":\"sensor\",\"ip\":\"";
a += String(WiFi.localIP()[0]) + "." + String(WiFi.localIP()[1]) + "." + String(WiFi.localIP()[2]) + "." + String(WiFi.localIP()[3]);
a += "\"}";
// send message to server
webSocket.sendTXT(a);
break;
case WStype_TEXT:{
Serial.printf("[WSc] get text: %s\n", payload);
// recibe json desde el js en la pagina de control
StaticJsonDocument<512> root;
auto error = deserializeJson(root, payload);
if (error) {
Serial.print(F("deserializeJson() failed with code "));
Serial.println(error.c_str());
Serial.println("JSON parsing failed!");
}
const char* jtype = root["type"];
Serial.print("recibe: ");
Serial.println(jtype);
//************************************
// estimulo
if (String(jtype) == String("stimulus")){
Serial.println("ESTIMULO");
timeout = root["timeout"];
tdelay = root["delay"];
min_detection_range = root["min_detection_range"];
max_detection_range = root["max_detection_range"];
tStimulusStart.setInterval(tdelay);
tStimulusStart.restartDelayed();
}
// ************************************
// restart
if (String(jtype) == String("restart")){
ESP.restart();
}
// send message to server
// webSocket.sendTXT("message here");
}
break;
case WStype_BIN:
Serial.printf("[WSc] get binary length: %u\n", length);
hexdump(payload, length);
// send data to server
// webSocket.sendBIN(payload, length);
break;
}
}
void monitorWiFi()
{
// We start by connecting to a WiFi network
if(WiFi.status() != WL_CONNECTED){
Serial.println();
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
digitalWrite(LEDA, LEDA!=16); //titla el led hasta que se conecta
delay(300);
digitalWrite(LEDA, LEDA==16); // si es el 16 BUILTIN_LED lo deja HIGH para apagarlo
delay(200);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
webSocket.begin("192.168.4.1", 81, "/");
//webSocket.beginSocketIO("192.168.4.1", 81, "/");
// event handler
webSocket.onEvent(webSocketEvent);
// try ever 5000 again if connection has failed
webSocket.setReconnectInterval(1000);
}
}
void setup() {
delay(10);
pinMode(TRIGGER_PIN, INPUT);
pinMode(LEDE , OUTPUT);
pinMode(LEDA , OUTPUT);
pinMode(LEDERR , OUTPUT);
pinMode(LEDOK , OUTPUT);
pinMode(TRIGGER, OUTPUT);
pinMode(ECHO, INPUT);
Serial.begin (115200);
Serial.setDebugOutput(true);
Serial.println(" ");
Serial.println(" ");
Serial.println(" ");
Serial.println("*****************************");
Serial.println("* *");
Serial.println("* TrainerLights *");
Serial.println("* By: Ricardo Lerch *");
Serial.println("* ricardo.lerch@gmail.com *");
Serial.println("* *");
Serial.println("*****************************");
Serial.println(" ");
Serial.println(" ");
Serial.println(" ");
// Serial.end();
// no duerme el wifi
wifi_set_sleep_type(NONE_SLEEP_T);
}
void loop() {
if (stimulating && !tMeasureDistance.isEnabled()){
tMeasureDistance.enable();
}
if (!stimulating && tMeasureDistance.isEnabled()){
tMeasureDistance.disable();
}
//isEnabled()
// if (stimulating){ // si esta estimulando mide, si no, no
// tMeasureDistance.enable();
// }else{
// tMeasureDistance.disable();
// }
monitorWiFi();
webSocket.loop();
ts.execute();
}
long duration, distance, ldistance;
void MeasureDistance(){
digitalWrite(TRIGGER, LOW);
delayMicroseconds(2);
digitalWrite(TRIGGER, HIGH);
delayMicroseconds(10);
digitalWrite(TRIGGER, LOW);
duration = pulseIn(ECHO, HIGH);
distance = (duration/2) / 29.1;
if (distance <= max_detection_range && distance >= min_detection_range) // aca detecto un objeto entre 3 y 50 cm
{
// detecto algo y...
tStimulusTimeout.disable();
time_now = millis();
unsigned long response_time = time_now - time_start ;
if (response_time > 50) { // si la respuesta es menor a 50 ms lo toma como error
// apaga el led de estimulo
digitalWrite(LEDE, LOW);
digitalWrite(LEDOK, HIGH);
tLedsOff.setInterval(100);
tLedsOff.restartDelayed();
// se pone en modo no stimulating
stimulating = false;
// manda info al server
a = "{\"type\":\"response\",\"time\":\"" + String(response_time) + "\"";
a += ",\"ip\":\"";
a += String(WiFi.localIP()[0]) + "." + String(WiFi.localIP()[1]) + "." + String(WiFi.localIP()[2]) + "." + String(WiFi.localIP()[3]) + "\"" ;
a += ",\"distance\":\"" + String(distance) + "\"";
a += ",\"error\":\"0\"";
a += "}";
webSocket.sendTXT(a);
if (distance != ldistance){
Serial.println(distance);
ldistance = distance;
}
}else{
StimulusTimeout();
}
}
}
void StimulusTimeout(){
// apaga el led de estimulo
digitalWrite(LEDE, LOW);
// se pone en modo no stimulating
stimulating = false;
a = "{\"type\":\"response\",\"time\":\"" + String(tdelay + timeout) + "\"";
a += ",\"ip\":\"";
a += String(WiFi.localIP()[0]) + "." + String(WiFi.localIP()[1]) + "." + String(WiFi.localIP()[2]) + "." + String(WiFi.localIP()[3]) + "\"" ;
a += ",\"error\":\"1\"";
a += "}";
webSocket.sendTXT(a);
stimulating = false;
digitalWrite(LEDERR, HIGH);
tLedsOff.setInterval(200);
tLedsOff.restartDelayed();
}
void LedsOff(){
digitalWrite(LEDERR, LOW);
digitalWrite(LEDOK, LOW);
}
void StimulusStart(){
// empieza el estimulo con el delay
// prende el led de estimulo y se pone en modo stimulating
digitalWrite(LEDE, HIGH);
time_start = millis();
stimulating = true;
tStimulusTimeout.setInterval(timeout);
tStimulusTimeout.restartDelayed();
}
Comments