Télécommande IR

De Wiki LOGre
Aller à la navigation Aller à la recherche

Projet réalisé par Fma38.

En cours.

Présentation

Martÿn est le papa d'un petit garçon qui ne peut communiquer que via le regard, en utilisant du matériel et logiciel conçu pour ça (marque Tobii).

Il souhaiterait pouvoir donner la possibilité à son fils de contrôler des jouets réels, plutôt que de le laisser uniquement interagir sur ordinateur. Il est donc venu à La Casemate pour demande de l'aide, et Steeve, le responsable des Open Labs, l'a renvoyé vers nous.

Après avoir acheté un jeu de construction sur une brocante, dont les moteurs sont pilotables par une télécommande IR, il est rapidement apparu que la solution la plus simple était de reproduire la télécommande. Un proto a été créé avec succés :

Télécommande hackée.jpg

Pour aider Martÿn, et toutes les familles dans des situations similaires, il serait intéressant de construire un appareil universel, facile d'emploi, qui pourrait contrôler tout un tas d'appareils IR. La configuration devrait pouvoir être faite par des non spécialistes, les parents dans ce genre de situation n'ont pas franchement le temps de recompiler un firmware !

La première idée était de développer une module wifi, que le logiciel Tobii pourrait contrôler. Mais il s'avère que ce logiciel ne permet pas de lier un bouton à une url ! Le plus simple est donc de faire quelque chose d'autonome, genre interface à boutons. Et plutôt que de déployer une appli sur le PC, il vaudrait mieux tout intégrer dans le module, lequel proposerait une interface web pour d'une part la partir pilotage, et d'autre part la partie configuration.

Cahier des charges

En vrac.

  • communication wifi
  • interface web pour le contrôle et la configuration
  • chargeur de batterie intégré
  • batterie remplaçable rapidement si pas le temps de charger
  • codes IR configurables, via apprentissage
  • sorties à relais additionnelles
  • ergonomique et robuste
  • projet libre

Pour Marc :

  • utilisation de W3.CSS
  • une page présentant 6 boutons (2 lignes / 3 colonnes) aussi gros que possible sur la page :
    • 2 boutons verts à gauche
    • 2 boutons bleus au milieu
    • 2 boutons rouges à droite
  • une page de configuration permettant, pour chaque bouton, de sélectionner :
    • le mode (toggle ou tempo) TODO: trouver des noms en français
    • le délai (mode tempo uniquement)

Électronique

Une première version a été réalisée en utilisant un petit ATOM Lite, de M5Stack. Ce module intègre une led IR, un bouton, et une led RGB.

Firmware

Merci à Marc pour la partie html !

Les fichiers index.html, config.html, et ir.css doivent être mis dans un répertoire data/ afin de pouvoir être uploadés par les outils Arduino IDE.

IR_remote_ESP32.ino

#include <Arduino.h>
#include <stdint.h>

// Wifi stuff
#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <EspHtmlTemplateProcessor.h>

//#include <ESPmDNS.h>
//#include <ArduinoOTA.h>

// IR stuff
#include <IrSenderPwmSoftDelay.h>

// JSON stuff
#include <ArduinoJson.h>

// EEPROM management
#include <EEPROM.h>
// -------------------------------------------

#define AP_SSID "IR Remote"
#define AP_PASSWORD "12345678"
#define AP_IP_ADDRESS IPAddress(192, 168, 187, 8)
#define AP_GATEWAY IPAddress(0, 0, 0, 0)
#define AP_SUBNET IPAddress(255, 255, 255, 0)
#define AP_PORT 5
#define AP_HIDDEN true
#define AP_MAX_CONNECTION 1
// -------------------------------------------

WebServer server(80);
EspHtmlTemplateProcessor templateProcessor(&server);

static constexpr frequency_t frequency = 36800U;

static const uint8_t PIN_IR = G12;

static const uint16_t SHORT = 800;
static const uint16_t LONG = 2400;

static const uint16_t stopArray[] = {
   SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT
};
static const uint16_t greenUpArray[] = {
   SHORT, SHORT, LONG, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, LONG, SHORT
};
static const uint16_t greenDownArray[] = {
   SHORT, SHORT, LONG, SHORT, LONG, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT
};
static const uint16_t blueUpArray[] = {
   SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, LONG, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, LONG, SHORT
};
static const uint16_t blueDownArray[] = {
   SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, LONG, SHORT, LONG, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT
};
static const uint16_t redUpArray[] = {
   SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, LONG, SHORT, SHORT, SHORT, LONG, SHORT
};
static const uint16_t redDownArray[] = {
   SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, LONG, SHORT, LONG, SHORT, SHORT, SHORT
};

static IrSequence stopSeq(stopArray, sizeof(stopArray)/sizeof(microseconds_t));
static IrSequence greenUpSeq(greenUpArray, sizeof(greenUpArray)/sizeof(microseconds_t));
static IrSequence greenDownSeq(greenDownArray, sizeof(greenDownArray)/sizeof(microseconds_t));
static IrSequence blueUpSeq(blueUpArray, sizeof(blueUpArray)/sizeof(microseconds_t));
static IrSequence blueDownSeq(blueDownArray, sizeof(blueDownArray)/sizeof(microseconds_t));
static IrSequence redUpSeq(redUpArray, sizeof(redUpArray)/sizeof(microseconds_t));
static IrSequence redDownSeq(redDownArray, sizeof(redDownArray)/sizeof(microseconds_t));

static IrSequence sequences[] = {
    greenUpSeq, greenDownSeq, blueUpSeq, blueDownSeq, redUpSeq, redDownSeq
};

static IrSender* irSender;

long lastSeqStartTime = millis();

int8_t ir = -1;
uint16_t counter = 0;

// Vars saved in EEPROM
uint16_t buttonsSize = 300;
uint16_t buttonsFontSize = 200;

String buttonMode[6] = {
    "delay",
    "delay",
    "delay",
    "delay",
    "delay",
    "delay"
};
uint16_t buttonDelay[6] = {
    2000,
    2000,
    2000,
    2000,
    2000,
    2000
};
String buttonText[6] = {
    "+",
    "-",
    "+",
    "-",
    "+",
    "-"
};

StaticJsonDocument<200> configDoc;

// -------------------------------------------

void setup()
{

    // Init serial coms
    Serial.begin(115200);
    while (!Serial);
    Serial.println();
    yield();

    // Init EEPROM
//    EEPROM.begin();

    // Start file support
    SPIFFS.begin();

    delay(500);

    // Starting AP
    Serial.println(F("Server: configuring AP..."));
    if (!WiFi.softAPConfig(AP_IP_ADDRESS, AP_GATEWAY, AP_SUBNET)) {
        Serial.println(F("WiFi.softAPConfig() failed"));
        errorBlinkForever();
    }
    Serial.println(F("Server: AP configured"));
    yield();

    Serial.println(F("Server: starting AP..."));
    if (!WiFi.softAP(AP_SSID, AP_PASSWORD)) {  //, AP_PORT, AP_HIDDEN, AP_MAX_CONNECTION)) {
        Serial.println(F("WiFi.softAP() failed"));
        errorBlinkForever();
    }
    IPAddress myIP = WiFi.softAPIP();
    Serial.print(F("Server: AP IP address: "));
    Serial.println(myIP);
    Serial.println(F("Server: AP started"));
    yield();

    // Start webserver
    Serial.println(F("Server: starting Web server..."));
    server.on("/", handleRoot);
    server.on("/config", handleConfig);
    server.on("/getConfig", handleGetConfig);
    server.on("/ir", handleIr);
    server.serveStatic("/ir.css", SPIFFS, "/ir.css");
    server.begin();
    Serial.println(F("Server: web server started"));
    yield();

    // Create IR Sender
    irSender = new IrSenderPwmSoftDelay(PIN_IR);

    Serial.print(F("Shooting @ pin "));
    Serial.println(irSender->getPin(), DEC);

    // TODO: get config from EEPROM
}


void loop()
{
    unsigned long prevMillis = millis();

    server.handleClient();
    yield();

    if (ir == -1) {
        sendSeq(&stopSeq);
    }

    else {
        Serial.print("active ir=");
        Serial.println(ir);
        sendSeq(&sequences[ir]);

        // Button mode is delay
        if (buttonMode[ir] == "delay") {
            Serial.print("counter=");
            Serial.println(counter);
            
            uint16_t elapsed = millis() - prevMillis;  // compute elapsed time since previous iteration
            prevMillis = millis();

            // Counter big enough
            if (counter > elapsed) {
                counter -= elapsed;  // decrement counter with elapsed time
            }

            // End of counter
            else {
                ir = -1;  // stop IR
            }
        }
    }
}


void sendSeq(IrSequence *seq)
{
    // Ensure delay between sequences
    while ((millis()-lastSeqStartTime) < 101);

    lastSeqStartTime = millis();
    irSender->send(*seq, frequency);
}


String processor(const String& key)
{
    if (key == "BUTTONS_SIZE") {
        return String(buttonsSize);
    }
    else if (key == "BUTTONS_FONT_SIZE") {
        return String(buttonsFontSize);
    }
    else if (key == "BUTTON_0_DELAY") {
        return String(buttonDelay[0]);
    }
    else if (key == "BUTTON_1_DELAY") {
        return String(buttonDelay[1]);
    }
    else if (key == "BUTTON_2_DELAY") {
        return String(buttonDelay[2]);
    }
    else if (key == "BUTTON_3_DELAY") {
        return String(buttonDelay[3]);
    }
    else if (key == "BUTTON_4_DELAY") {
        return String(buttonDelay[4]);
    }
    else if (key == "BUTTON_5_DELAY") {
        return String(buttonDelay[5]);
    }
    else if (key == "BUTTON_0_TEXT") {
        return String(buttonText[0]);
    }
    else if (key == "BUTTON_1_TEXT") {
        return String(buttonText[1]);
    }
    else if (key == "BUTTON_2_TEXT") {
        return String(buttonText[2]);
    }
    else if (key == "BUTTON_3_TEXT") {
        return String(buttonText[3]);
    }
    else if (key == "BUTTON_4_TEXT") {
        return String(buttonText[4]);
    }
    else if (key == "BUTTON_5_TEXT") {
        return String(buttonText[5]);
    }
    else {
        return "{{" + key + "}}";
    }
}


void handleRoot()
{
    templateProcessor.processAndSend("/index.html", processor);
}


void handleConfig()
{
    bool saveToEEPROM = false;
    
    if (server.hasArg("buttonsSize")) {
        buttonsSize = server.arg("buttonsSize").toInt();
        Serial.print("new buttonsSize=");
        Serial.println(buttonsSize);
        saveToEEPROM = true;
    }
    if (server.hasArg("buttonsFontSize")) {
        buttonsFontSize = server.arg("buttonsFontSize").toInt();
        Serial.print("new buttonsFontSize=");
        Serial.println(buttonsFontSize);
        saveToEEPROM = true;
    }

    if (server.hasArg("button0Mode")) {
        buttonMode[0] = server.arg("button0Mode");
        Serial.print("new buttonMode[0]=");
        Serial.println(buttonMode[0]);
        saveToEEPROM = true;
    }
    if (server.hasArg("button0Delay")) {
        buttonDelay[0] = server.arg("button0Delay").toInt();
        Serial.print("new buttonDelay[0]=");
        Serial.println(buttonDelay[0]);
        saveToEEPROM = true;
    }
    if (server.hasArg("button0Text")) {
        buttonText[0] = server.arg("button0Text");
        Serial.print("new buttonText[0]=");
        Serial.println(buttonText[0]);
        saveToEEPROM = true;
    }
    if (server.hasArg("button1Mode")) {
        buttonMode[1] = server.arg("button1Mode");
        Serial.print("new buttonMode[1]=");
        Serial.println(buttonMode[1]);
        saveToEEPROM = true;
    }
    if (server.hasArg("button1Delay")) {
        buttonDelay[1] = server.arg("button1Delay").toInt();
        Serial.print("new buttonDelay[1]=");
        Serial.println(buttonDelay[1]);
        saveToEEPROM = true;
    }
    if (server.hasArg("button1Text")) {
        buttonText[1] = server.arg("button1Text");
        Serial.print("new buttonText[1]=");
        Serial.println(buttonText[1]);
        saveToEEPROM = true;
    }
    if (server.hasArg("button2Mode")) {
        buttonMode[2] = server.arg("button2Mode");
        Serial.print("new buttonMode[2]=");
        Serial.println(buttonMode[2]);
        saveToEEPROM = true;
    }
    if (server.hasArg("button2Delay")) {
        buttonDelay[2] = server.arg("button2Delay").toInt();
        Serial.print("new buttonDelay[2]=");
        Serial.println(buttonDelay[2]);
        saveToEEPROM = true;
    }
    if (server.hasArg("button2Text")) {
        buttonText[2] = server.arg("button2Text");
        Serial.print("new buttonText[2]=");
        Serial.println(buttonText[2]);
        saveToEEPROM = true;
    }
    if (server.hasArg("button3Mode")) {
        buttonMode[3] = server.arg("button3Mode");
        Serial.print("new buttonMode[3]=");
        Serial.println(buttonMode[3]);
        saveToEEPROM = true;
    }
    if (server.hasArg("button3Delay")) {
        buttonDelay[3] = server.arg("button3Delay").toInt();
        Serial.print("new buttonDelay[3]=");
        Serial.println(buttonDelay[3]);
        saveToEEPROM = true;
    }
    if (server.hasArg("button3Text")) {
        buttonText[3] = server.arg("button3Text");
        Serial.print("new buttonText[3]=");
        Serial.println(buttonText[3]);
        saveToEEPROM = true;
    }
    if (server.hasArg("button4Mode")) {
        buttonMode[4] = server.arg("button4Mode");
        Serial.print("new buttonMode[4]=");
        Serial.println(buttonMode[4]);
        saveToEEPROM = true;
    }
    if (server.hasArg("button4Delay")) {
        buttonDelay[4] = server.arg("button4Delay").toInt();
        Serial.print("new buttonDelay[4]=");
        Serial.println(buttonDelay[4]);
        saveToEEPROM = true;
    }
    if (server.hasArg("button4Text")) {
        buttonText[4] = server.arg("button4Text");
        Serial.print("new buttonText[4]=");
        Serial.println(buttonText[4]);
        saveToEEPROM = true;
    }
    if (server.hasArg("button5Mode")) {
        buttonMode[5] = server.arg("button5Mode");
        Serial.print("new buttonMode[5]=");
        Serial.println(buttonMode[5]);
        saveToEEPROM = true;
    }
    if (server.hasArg("button5Delay")) {
        buttonDelay[5] = server.arg("button5Delay").toInt();
        Serial.print("new buttonDelay[5]=");
        Serial.println(buttonDelay[5]);
        saveToEEPROM = true;
    }
    if (server.hasArg("button5Text")) {
        buttonText[5] = server.arg("button5Text");
        Serial.print("new buttonText[5]=");
        Serial.println(buttonText[5]);
        saveToEEPROM = true;
    }

    if (saveToEEPROM == true) {
        // TODO
    }


    templateProcessor.processAndSend("/config.html", processor);
}


void handleGetConfig()
{
    Serial.println("handleGetConfig()");

    configDoc["button0Mode"] = buttonMode[0];
    configDoc["button1Mode"] = buttonMode[1];
    configDoc["button2Mode"] = buttonMode[2];
    configDoc["button3Mode"] = buttonMode[3];
    configDoc["button4Mode"] = buttonMode[4];
    configDoc["button5Mode"] = buttonMode[5];

    configDoc["button0Text"] = buttonText[0];
    configDoc["button1Text"] = buttonText[1];
    configDoc["button2Text"] = buttonText[2];
    configDoc["button3Text"] = buttonText[3];
    configDoc["button4Text"] = buttonText[4];
    configDoc["button5Text"] = buttonText[5];

    String response;
    serializeJson(configDoc, response);

    server.send(200, "application/json", response);
}

void handleIr()
{
//    Serial.println("handleIr()");

    if (server.hasArg("channel")) {
        uint8_t ir_ = server.arg("channel").toInt();
        Serial.print("ir_=");
        Serial.println(ir_);

        // Button mode is 'delay'
        if (buttonMode[ir_] == "delay") {

            // Button was not active
            if (ir_ != ir) {
                ir = ir_;                    // set current IR
                counter = buttonDelay[ir_];  // load counter with button delay
            }
        }

        // Button mode is 'toggle'
        else {
            if (ir_ == ir) {
                ir = -1;  // switch off IR
            }
            else {
                ir = ir_;  // set new IR
            }
        }
        Serial.print("new ir=");
        Serial.println(ir);
    }

    server.send(200, "text/plain", String(ir).c_str());
}

index.html

<!DOCTYPE html>
<html lang="fr">
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <link rel="stylesheet" href="ir.css">

        <script>
            function onLoad()
            {
                // Register getIr() function
                setInterval (function() {
                    getIr();
                }, 250);
            }

            function getIr()
            {
                let xhttp = new XMLHttpRequest();
                xhttp.onreadystatechange = irChanged;
                xhttp.open("POST", "/ir", true);
                xhttp.send();
            }

            function setIr(irChannel)
            {
                let xhttp = new XMLHttpRequest();
                xhttp.open("POST", "/ir", true);
                xhttp.timeout = 500;
                xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
                xhttp.send("channel=" + irChannel);
            }

            // Callbacks
            function irChanged()
            {
                if (this.readyState == XMLHttpRequest.DONE && this.status == 200) {
                    let ir = this.responseText;
                    document.getElementById("ir_0").style.color = "black";  // restore color
                    document.getElementById("ir_1").style.color = "black";  // restore color
                    document.getElementById("ir_2").style.color = "black";  // restore color
                    document.getElementById("ir_3").style.color = "black";  // restore color
                    document.getElementById("ir_4").style.color = "black";  // restore color
                    document.getElementById("ir_5").style.color = "black";  // restore color
                    if (ir != -1) {
                        document.getElementById("ir_"+ir).style.color = "white";  // set color
                    }
                }
            }
        </script>

        <title>Télécommande IR</title>
    </head>

    <body onload="onLoad()">
        <button class="button green" style="width:{{BUTTONS_SIZE}}px; height:{{BUTTONS_SIZE}}px; font-size:{{BUTTONS_FONT_SIZE}}px" id="ir_0" onclick="setIr(0)">{{BUTTON_0_TEXT}}</button>
        <button class="button red" style="width:{{BUTTONS_SIZE}}px; height:{{BUTTONS_SIZE}}px; font-size:{{BUTTONS_FONT_SIZE}}px" id="ir_2" onclick="setIr(2)">{{BUTTON_2_TEXT}}</button>
        <button class="button blue" style="width:{{BUTTONS_SIZE}}px; height:{{BUTTONS_SIZE}}px; font-size:{{BUTTONS_FONT_SIZE}}px" id="ir_4" onclick="setIr(4)">{{BUTTON_4_TEXT}}</button>
        <br/>
        <button class="button green" style="width:{{BUTTONS_SIZE}}px; height:{{BUTTONS_SIZE}}px; font-size:{{BUTTONS_FONT_SIZE}}px" id="ir_1" onclick="setIr(1)">{{BUTTON_1_TEXT}}</button>
        <button class="button red" style="width:{{BUTTONS_SIZE}}px; height:{{BUTTONS_SIZE}}px; font-size:{{BUTTONS_FONT_SIZE}}px" id="ir_3" onclick="setIr(3)">{{BUTTON_3_TEXT}}</button>
        <button class="button blue" style="width:{{BUTTONS_SIZE}}px; height:{{BUTTONS_SIZE}}px; font-size:{{BUTTONS_FONT_SIZE}}px" id="ir_5" onclick="setIr(5)">{{BUTTON_5_TEXT}}</button>
        <br/>
    </body>

</html>

config.html

<!DOCTYPE html>
<html lang="fr">
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <!-- W3.CSS support -->
        <link rel="stylesheet" href="ir.css">

        <script>
            function onLoad()
            {
                getConfig();
                updatePreview();
                document.getElementById("buttonPreview").disabled = true;
            }

            function updatePreview(){
                ButtonsSize = document.getElementById("buttonsSize").value  //document.forms['settings']['buttonsSize'].value;
                buttonsFontSize = document.getElementById("buttonsFontSize").value  //document.forms['settings']['buttonsFontSize'].value;
                document.getElementById("buttonPreview").style.width = ButtonsSize + "px";
                document.getElementById("buttonPreview").style.height = ButtonsSize + "px";
                document.getElementById("buttonPreview").style.fontSize = buttonsFontSize + "px";
            }

            function getConfig()
            {
                let xhttp = new XMLHttpRequest();
                xhttp.onreadystatechange = configChanged;
                xhttp.open("POST", "/getConfig", true);
                xhttp.responseType = 'json';
                xhttp.send();
            }

            function saveConfig(){
                settings = new FormData(document.getElementById("settings"));
                let xhttp = new XMLHttpRequest();
                xhttp.open("POST", "/config", true);
                xhttp.timeout = 500;
                xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
                xhttp.send(settings);
            }

            // Callbacks
            function configChanged()
            {
                if (this.readyState == XMLHttpRequest.DONE && this.status == 200) {
                    let responseObj = this.response;
                    document.getElementById("button0Mode").selectedIndex = ((responseObj.button0Mode == "toggle") ? 0 : 1);
                    document.getElementById("button1Mode").selectedIndex = ((responseObj.button1Mode == "toggle") ? 0 : 1);
                    document.getElementById("button2Mode").selectedIndex = ((responseObj.button2Mode == "toggle") ? 0 : 1);
                    document.getElementById("button3Mode").selectedIndex = ((responseObj.button3Mode == "toggle") ? 0 : 1);
                    document.getElementById("button4Mode").selectedIndex = ((responseObj.button4Mode == "toggle") ? 0 : 1);
                    document.getElementById("button5Mode").selectedIndex = ((responseObj.button5Mode == "toggle") ? 0 : 1);
                }
            }
        </script>

        <title>Télécommande IR - Configuration</title>
    </head>

    <body onload="onLoad()">

        <form id="settings" action="/config" method="POST">

            <!-- Buttons -->
            <table>
                <tbody>

                    <!-- Bouton 0 -->
                    <tr>
                        <td>
                            <div class="buttonConfig green">{{BUTTON_0_TEXT}}</div>
                        </td>
                        <td>
                            <select name="button0Mode" id="button0Mode">
                                <option value="toggle">Bascule</option>
                                <option value="delay">Délai</option>
                            </select>
                        </td>
                        <td>
                            <input name="button0Delay" size="4" type="text" value={{BUTTON_0_DELAY}}>
                        </td>
                        <td>ms</td>
                    </tr>
                </tbody>

                    <!-- Bouton 1 -->
                    <tr>
                        <td>
                            <div class="buttonConfig green">{{BUTTON_1_TEXT}}</div>
                        </td>
                        <td>
                            <select name="button1Mode" id="button1Mode">
                                <option value="toggle">Bascule</option>
                                <option value="delay">Délai</option>
                            </select>
                        </td>
                        <td>
                            <input name="button1Delay" size="4" type="text" value={{BUTTON_1_DELAY}}>
                        </td>
                        <td>ms</td>
                    </tr>

                    <!-- Bouton 2 -->
                    <tr>
                        <td>
                            <div class="buttonConfig red">{{BUTTON_2_TEXT}}</div>
                        </td>
                        <td>
                            <select name="button2Mode" id="button2Mode">
                                <option value="toggle">Bascule</option>
                                <option value="delay">Délai</option>
                            </select>
                        </td>
                        <td>
                            <input name="button2Delay" size="4" type="text" value={{BUTTON_2_DELAY}}>
                        </td>
                        <td>ms</td>
                    </tr>

                    <!-- Bouton 3 -->
                    <tr>
                        <td>
                            <div class="buttonConfig red">{{BUTTON_3_TEXT}}</div>
                        </td>
                        <td>
                            <select name="button3Mode" id="button3Mode">
                                <option value="toggle">Bascule</option>
                                <option value="delay">Délai</option>
                            </select>
                        </td>
                        <td>
                            <input name="button3Delay" size="4" type="text" value={{BUTTON_3_DELAY}}>
                        </td>
                        <td>ms</td>
                    </tr>

                    <!-- Bouton 4 -->
                    <tr>
                        <td>
                            <div class="buttonConfig blue">{{BUTTON_4_TEXT}}</div>
                        </td>
                        <td>
                            <select name="button4Mode" id="button4Mode">
                                <option value="toggle">Bascule</option>
                                <option value="delay">Délai</option>
                            </select>
                        </td>
                        <td>
                            <input name="button4Delay" size="4" type="text" value={{BUTTON_4_DELAY}}>
                        </td>
                        <td>ms</td>
                    </tr>

                    <!-- Bouton 5 -->
                    <tr>
                        <td>
                            <div class="buttonConfig blue">{{BUTTON_5_TEXT}}</div>
                        </td>
                        <td>
                            <select name="button5Mode" id="button5Mode">
                                <option value="toggle">Bascule</option>
                                <option value="delay">Délai</option>
                            </select>
                        </td>
                        <td>
                            <input name="button5Delay" size="4" type="text" value={{BUTTON_5_DELAY}}>
                        </td>
                        <td>ms</td>
                    </tr>
            </table>

            <!-- Global -->
            <br />
            <table>
                <tbody>
                    <tr>
                        <td>Taille des boutons</td>
                        <td><input name="buttonsSize" id="buttonsSize" size="4" type="text" value={{BUTTONS_SIZE}} /></td> <!--onkeyup=updatePreview()>-->
                    </tr>
                    <tr>
                        <td>Taille de police</td>
                        <td><input name="buttonsFontSize" id="buttonsFontSize" size="4" type="text" value={{BUTTONS_FONT_SIZE}} /></td> <!--onkeyup=updatePreview()>-->
                    </tr>
                </tbody>
            </table>

            <button id="buttonPreview" class="button green">+</button>
            <br />
            <button>Save</button>

        </form>

    </body>
</html>

ir.css

body {
    background-color: darkgray;
}

.button {
    border-color: gray;
    border-width: 10px;
    border-radius: 20px;
    font-weight: bold;
    color: black;
    margin: 20px;
    text-align: center;
    vertical-align: middle;
}

.buttonConfig {
    width: 32px;
    height: 32px;
    border-color: gray;
    border-width: 5px;
    border-radius: 5px;
    font-weight: bold;
    color: black;
    display: flex;
    align-items: center;
    justify-content: center;
}

.green {
    background-color: green;
}

.red {
    background-color: red;
}

.blue {
    background-color: blue;
}

:hover {
  border-color: yellow
}

Prototype

IMG 20220723 084720.jpg IMG 20220723 084729.jpg IMG 20220723 084734.jpg

Liens

Matériel

Code