je-menvol/prog/prog.ino
2025-05-07 18:23:21 +02:00

367 lines
11 KiB
C++

#include <ESP32Servo.h>
#include <Wire.h>
#include "Adafruit_VL53L0X.h"
#include <Adafruit_PCF8574.h>
#include "terminal.hpp"
//
// ATTENTION : pour que les commandes se déclarent automatiquement au démarrage
// auprès du terminal, le fichier conteant la fonction setup() et loop() doit
// être mis en derniere position dans la liste des fichiers lors de l'édition
// des liens.
// Dans platformio, pour faire cela, il suffit de commencer le nom du fichier
// contenant les fonctions loop() et setup() par la lettre z.
// Par exemple : zzz_main.cpp.
//
extern terminal_n::Terminal terminal;
TERMINAL_PARAMETER_INT(
terminal,
power_mode, // variable name to declare
"power_mode", // EEPROM name (15 char. max),
// If you dont want to save in EEPROM, set ""
"the power mode", // description
0, // default constructor value
false, // auto save in eeprom after command modification
terminal_n::filter_nothing, // function to filter the value
terminal_n::do_nothing // function executed at the end of the command
);
TERMINAL_COMMAND(terminal, ping, "Print pong followoing by the received arguments")
{
terminal.print("pong");
for (uint32_t i=0; i<argc; i++) {
terminal.print(" ");
terminal.print(argv[i]);
}
terminal.println("");
}
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
struct pin {
int id;
int is_extender;
};
struct tube {
float height;
float target;
struct {
Servo hard;
struct pin pin;
int last_angle;
int equilibrium;
} servo;
struct {
Adafruit_VL53L0X hard;
struct pin gpio;
struct pin xshut;
} tof;
int id;
};
struct tube all_tubes[6] = {0};
const float minHeight = 50;
const float maxHeight = 445;
int minServo = 0;
int maxServo = 180;
// Replace with your network credentials
const char* ssid = "ESP_ping";
const char* password = "pong";
const char* PARAM_INPUT_1 = "output";
const char* PARAM_INPUT_2 = "state";
// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
<title>Balle Ping Pong flottante</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
<style>
html {font-family: Arial; display: inline-block; text-align: center;}
h2 {font-size: 3.0rem;}
p {font-size: 3.0rem;}
body {max-width: 600px; margin:0px auto; padding-bottom: 25px;}
.switch {position: relative; display: inline-block; width: 120px; height: 68px}
.switch input {display: none}
.slider {background-color: #ccc; border-radius: 6px}
.slider:before {position: absolute; content: ""; height: 52px; width: 52px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 3px}
input:checked+.slider {background-color: #b30000}
input:checked+.slider:before {-webkit-transform: translateX(52px); -ms-transform: translateX(52px); transform: translateX(52px)}
</style>
</head>
<body>
<h2>Balle Ping Pong flottante</h2>
%BUTTONPLACEHOLDER%
<script>
function sendSliderValue(id, element) {
var slider = document.getElementById(`slider${id}`);
var xhr = new XMLHttpRequest();
xhr.open("GET", "/update?output="+id+"&state=" + slider.value, true);
xhr.send();
}
</script>
</body>
</html>
)rawliteral";
// Replaces placeholder with button section in your web page
String processor(const String& var){
//Serial.println(var);
if(var == "BUTTONPLACEHOLDER"){
String buttons = "";
buttons += "<div class=\"slidecontainer\"><input type=\"range\" min=\"20\" max=\"450\" value=\"250\" class=\"slider\" id=\"slider0\" onchange=\"sendSliderValue(0, this.value)\"\"></div>";
buttons += "<div class=\"slidecontainer\"><input type=\"range\" min=\"20\" max=\"450\" value=\"250\" class=\"slider\" id=\"slider1\" onchange=\"sendSliderValue(1, this.value)\"\"></div>";
buttons += "<div class=\"slidecontainer\"><input type=\"range\" min=\"20\" max=\"450\" value=\"250\" class=\"slider\" id=\"slider2\" onchange=\"sendSliderValue(2, this.value)\"\"></div>";
buttons += "<div class=\"slidecontainer\"><input type=\"range\" min=\"20\" max=\"450\" value=\"250\" class=\"slider\" id=\"slider3\" onchange=\"sendSliderValue(3, this.value)\"\"></div>";
buttons += "<div class=\"slidecontainer\"><input type=\"range\" min=\"20\" max=\"450\" value=\"250\" class=\"slider\" id=\"slider4\" onchange=\"sendSliderValue(4, this.value)\"\"></div>";
return buttons;
}
return String();
}
auto mesureHeight(Adafruit_VL53L0X lox, struct tube *tube){
VL53L0X_RangingMeasurementData_t measure;
lox.rangingTest(&measure, false); // pass in 'true' to get debug data printout!
if (measure.RangeStatus != 4) { // phase failures have incorrect data
float height = measure.RangeMilliMeter;
if (height < 1000.0){
tube->height = height;
return height;
} else {
Serial.println("Incoherent measurement. Returning previous one.");
return tube->height;
}
}
else {
Serial.println(" out of range ");
return tube->height;
}
}
void wifisetup() {
Serial.print("Setting AP (Access Point)…");
WiFi.softAP(ssid);
IPAddress IP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(IP);
// Route for root / web page
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", index_html, processor);
});
// Send a GET request to <ESP_IP>/update?output=<inputMessage1>&state=<inputMessage2>
server.on("/update", HTTP_GET, [] (AsyncWebServerRequest *request) {
String inputMessage1;
String inputMessage2;
// GET input1 value on <ESP_IP>/update?output=<inputMessage1>&state=<inputMessage2>
if (request->hasParam(PARAM_INPUT_1) && request->hasParam(PARAM_INPUT_2)) {
inputMessage1 = request->getParam(PARAM_INPUT_1)->value();
inputMessage2 = request->getParam(PARAM_INPUT_2)->value();
all_tubes[0].target = inputMessage2.toFloat();
}
else {
inputMessage1 = "No message sent";
inputMessage2 = "No message sent";
}
Serial.print("GPIO: ");
Serial.print(inputMessage1);
Serial.print(" - Set to: ");
Serial.println(inputMessage2);
request->send(200, "text/plain", "OK");
});
// Start server
server.begin();
}
Adafruit_PCF8574 pcf8574;
void change_pin(struct pin pin, int state) {
if (pin.is_extender) {
pcf8574.digitalWrite(pin.id, state);
} else {
digitalWrite(pin.id, state);
}
}
Adafruit_VL53L0X tofs_talk_through[6] = {Adafruit_VL53L0X(), Adafruit_VL53L0X(), Adafruit_VL53L0X(), Adafruit_VL53L0X(), Adafruit_VL53L0X(), Adafruit_VL53L0X()};
void init_tube(struct tube *tube, char id, struct pin servo_pin, struct pin gpio, struct pin xshut) {
Serial.print(" Initialiazing tube #");
Serial.println((int) id);
tube->id = id;
tube->servo.pin = servo_pin;
tube->tof.gpio = gpio;
tube->tof.xshut = xshut;
tube->height = 250.0f;
tube->target = 250.0f;
change_pin(gpio, HIGH);
change_pin(xshut, HIGH);
change_pin(servo_pin, HIGH);
Serial.println("Beginning boot sequence VL53L0X");
if (!tofs_talk_through[id].begin(0x30 + id)) {
Serial.println(F("Failed to boot VL53L0X"));
while(1);
}
tube->servo.hard.attach(tube->servo.pin.id);
tube->servo.hard.write(minServo);
delay(5000);
Serial.println("Setuping downards");
float h = mesureHeight(tofs_talk_through[id], tube);
int angle = minServo;
while (h > maxHeight) {
++angle;
tube->servo.hard.write(angle);
delay(300);
h = mesureHeight(tofs_talk_through[id], tube);
}
angle -= 5;
tube->servo.last_angle = angle;
tube->servo.hard.write(tube->servo.last_angle);
tube->servo.equilibrium = angle;
Serial.println("Done.");
}
const int ACTIVES = 1;
void setup_all_tubes() {
const struct pin all_xshuts[6] = {
{.id = 17, .is_extender = 0},
{.id = 2, .is_extender = 1},
{.id = 3, .is_extender = 1},
{.id = 5, .is_extender = 1},
{.id = 7, .is_extender = 1},
{.id = 1, .is_extender = 1}
};
const struct pin all_gpios[6] = {
{.id = 5, .is_extender = 0},
{.id = 18, .is_extender = 0},
{.id = 19, .is_extender = 0},
{.id = 4, .is_extender = 1},
{.id = 6, .is_extender = 1},
{.id = 0, .is_extender = 1}
};
const struct pin all_servos[6] = {
{.id = 32, .is_extender = 0},
{.id = 33, .is_extender = 0},
{.id = 23, .is_extender = 0},
{.id = 16, .is_extender = 0},
{.id = 14, .is_extender = 0},
{.id = 2, .is_extender = 0}
};
for (int i = 0; i < ACTIVES; ++i) {
change_pin(all_xshuts[i], LOW);
}
delay(10);
for (int i = 0; i < ACTIVES; ++i) {
change_pin(all_xshuts[i], HIGH);
}
delay(10);
for (int i = 0; i < ACTIVES; ++i) {
change_pin(all_xshuts[i], LOW);
}
for (int i = 0; i < ACTIVES; ++i) {
init_tube(all_tubes, i, all_servos[i], all_gpios[i], all_xshuts[i]);
}
}
TERMINAL_ADD_DEFAULT_COMMANDS(terminal) // help, params, load_config, reset_config, echo ...
terminal_n::Terminal terminal("Pingpong flottant.\n\r\n\r");
void setup() {
Serial.begin(115200); // Starts the serial communication
while (! Serial) {
delay(1);
}
if (!pcf8574.begin(0x38, &Wire)) {
Serial.println("Couldn't find PCF8574");
while (1);
}
for (uint8_t p=0; p<8; p++) {
pcf8574.pinMode(p, OUTPUT);
}
Serial.println("PCF8574 seemks OK");
wifisetup();
terminal.setup(&Serial);
setup_all_tubes();
Serial.println("Done setuping have funning");
}
auto heightRatio(float height){
return max(min(1.0f, (height - minHeight) / (maxHeight-minHeight)), 0.0f);
}
int servoPosition(float ratio){
return (ratio * maxServo) + ((1-ratio) * minServo);
}
void loop() {
terminal.update();
for (int i = 0; i < ACTIVES; i++) {
struct tube *tube = all_tubes + i;
/*
Serial.print("Tube ");
Serial.println(tube->id);
*/
tube->height = mesureHeight(tofs_talk_through[tube->id], tube);
/*
Serial.print("Measure = ");
Serial.println(tube->height);
*/
int angle = 0;
float diff = tube->height - tube->target;
if (abs(diff) > 5.0f) {
angle = tube->servo.equilibrium + (diff * 1 / (7));
} else if (abs(diff) > 2.0f) {
angle = tube->servo.equilibrium + (diff * 1 / (7));
} else {
angle = tube->servo.equilibrium;
}
/*
Serial.print("angle = ");
Serial.print(angle);
Serial.print("; equilibrium = ");
Serial.println(tube->servo.equilibrium);
*/
tube->servo.last_angle = min(max(angle, minServo), maxServo);
/* Serial.print("Diff = ");
Serial.print(diff);
Serial.print("; Requested = ");
Serial.print(tube->target);
Serial.print("; Angle = ");
Serial.println(tube->servo.last_angle);
*/tube->servo.hard.write(tube->servo.last_angle);
}
delay(1);
}