Moteur pas a pas "bug" avec ESP8866

Bonjour à tous
j'ai un moteur 28 BYJ 48 sur un ESP8866
et quand je commande le moteur il patine et il fait demi-tour en plein déplacement
c'est ce projet que j'ai réalisé
https://www.webastro.net/forums/topic/195389-r%C3%A9alisation-roue-a-filtre-en-impression-3d-motoris%C3%A9e-et-pilot%C3%A9e-par-microcontroleur-esp8266esp32/
j'ai testé avec un autre moteur et c'est la même chose
avec une meilleure alimentation toujours rien qui change
des idées ???
Merci :wink:
cdt

Bonsoir,

Peut tu donner plus d'explications stp.

Tu dis que le moteur "patine" :

  1. est-ce le moteur à vide ou intégré dans sa mécanique ?
  2. comment est alimenté ce moteur ?
    Si c'est sur le 5 V de la carte ce n'est pas terrible à plusieurs points de vue : courant disponible qui peut être faiblard et pollution de l'alim de la carte, d'où possible dysfonctionnement du microcontrôleur, j'ai "possible" ce n'est pas une affirmation.

Le moindre chargeur de téléphone fait maintenant 5 V 2 A il serait préférable d'en utiliser un pour le moteur

Il existe sur le net des centaines d'exemples pour ce moteur hyper courant.
En as-tu réalisé ? Avec juste un moteur à l'air libre sans charge pour se mettre dans les conditions matérielles des tutos "YouTube".

Information : ce moteur est du type unipolaire à 4 bobinages. L'ULN 2003 est constitué de transistors bipolaires utilisés en montage darlington, son inconvénient est la tension de déchet Vcesat de 1 V, j'espère que le moteur n'est pas alimenté à partir du 3,3 V.

Bonsoir astronomy

Sur le schéma, le câblage est bon. Ces fonctionnements erratiques sont typiques de mauvaises masses (GND). Elles doivent toutes câblées ensemble avec de bons contacts.
Mets ton programme en ligne.

Cordialement
jpbbricole

C'est ce que j'ai fait, j'ai décrit le problème à l'air libre
quand le moteur est chargé il ne bouge pas du tout
je l'ai alimenté avec une prise USB supplémentaire et il n'y a rien qui change
cdt

//
// =============================================================================
// SMO FILTER WHEEL
// =============================================================================
//
// Mandatory hardware
//  ESP8266 or ESP32
//  Stepper Motor 28BYJ-48 + UNL2003
//

//
// =============================================================================
// SMO DEFINES (optional compil)
// =============================================================================
//

// Choose ESP8266 or ESP32
//#define SMO_USE_ESP32
#define SMO_USE_ESP8266
// Verif
#if defined(SMO_USE_ESP32) and defined(SMO_USE_ESP8266)
  #error "Choose ESP8266 or ESP32"
#endif

// OPTIONAL: SMO_DEBUG : Debug or not...
#define SMO_DEBUG

// OPTIONAL: SMO_USE_WIFI : Wifi or not...
#define SMO_USE_WIFI
#define SMO_USE_WIFI_CLIENT
#define SMO_USE_WIFI_ADHOC
// Verif
#if defined(SMO_USE_WIFI) and !defined(SMO_USE_WIFI_CLIENT) and !defined(SMO_USE_WIFI_ADHOC)
  #error "Define one Wifi mode at least (CLIENT/AP/CLIENT+AP)"
#endif

#if defined(SMO_USE_WIFI) and defined(SMO_USE_WIFI_CLIENT)
  // Mandatory, one wifi ssid and password
  #define SSID_1      "XXX"
  #define PASSWORD_1  "XXX"
  // If you need to add another ssid/password, uncoment the following lines
  #define SSID_2      "reseau2"
  #define PASSWORD_2  "password2"
  #define SSID_3      "reseau3"
  #define PASSWORD_3  "password3"
#endif
#if defined (SMO_USE_WIFI) and defined (SMO_USE_WIFI_ADHOC)
    #define SSID_AP      "mls-rf"
    #define PASSWORD_AP  "Tus0r@p@"
#endif

// OPTIONAL: SMO_USE_WIFI_OTA : ESP8266 OTA or not... (need SMO_USE_WIFI)
#ifdef SMO_USE_WIFI
  #define SMO_USE_WIFI_OTA
  #define SMO_OTA_PASSWORD "123"
#endif

// MANDATORY: SMO_PROTOCOL_xxx : Choose one protocol to handle serial commands...
// note: OPTEC do not work at this time...
// note: choose QUANTUM and disable DEBUG serial outputif you want to use the wheel with EKOS/INDI
//#define SMO_PROTOCOL_PERSO
#define SMO_PROTOCOL_QUANTUM
//#define SMO_PROTOCOL_OPTEC

// OPTIONAL: for immediate or background move (choose background to avoid timeout in Ekos/Indi)
#define SMO_BACKGROUND_MOVE

//
// =============================================================================
// INCLUDES
// =============================================================================
//

#include <AccelStepper.h>
#include <EEPROM.h>

#ifdef SMO_USE_WIFI
  #ifdef SMO_USE_WIFI_ADHOC
    #ifdef SMO_USE_ESP8266
      #include <ESP8266WiFi.h>
    #endif
    #ifdef SMO_USE_ESP32
      #include <WiFi.h>
    #endif   
  #endif
  #ifdef SMO_USE_ESP8266
    #include <ESP8266WiFiMulti.h>
    #include <ESP8266mDNS.h>
    #include <ESP8266WebServer.h>
  #endif
  #ifdef SMO_USE_ESP32
    #include <WiFiMulti.h>
    #include <ESPmDNS.h>
    #include <WebServer.h>
  #endif
  #ifdef SMO_USE_WIFI_OTA
    #include <ArduinoOTA.h>
  #endif
#endif

//
// =============================================================================
// DEFINES (values)
// =============================================================================
//

#define SMO_PRODUCT   "SMO-RF"
#define SMO_VERSION   "1.1.1"
#define SMO_HOSTNAME  "smo-rf"

// Caracteristiques Moteur
#define motorPin1  5                // IN1 pin on the ULN2003A driver to pin D1 on NodeMCU board
#define motorPin2  4                // IN2 pin on the ULN2003A driver to pin D2 on NodeMCU board
#define motorPin3  0                // IN3 pin on the ULN2003A driver to pin D3 on NodeMCU board
#define motorPin4  2                // IN4 pin on the ULN2003A driver to pin D4 on NodeMCU board
//sur ESP32 ne as utiliser GPIO 6-11 et 34-39

// Steppers
#define defStepperMaxSpeed     1000
#define minStepperMaxSpeed      100
#define maxStepperMaxSpeed     2000
#define defStepperSpeed         800
#define minStepperSpeed         100
#define maxStepperSpeed        2000
#define defStepperAccel         200
#define minStepperAccel         100
#define maxStepperAccel        1000
#define defStepsPerRevolution  64.0
#define minStepsPerRevolution   1.0
#define maxStepsPerRevolution 100.0

// Wheel
#define defStepperMaxPos      20000
#define minStepperMaxPos      10000
#define maxStepperMaxPos      40000
#define defStepperMaxAngle      180
#define minStepperMaxAngle       10
#define maxStepperMaxAngle      360
#define defNbDentsAxeMoteur      20
#define minNbDentsAxeMoteur      10
#define maxNbDentsAxeMoteur     100
#define defNbDentsAxeRoue       100
#define minNbDentsAxeRoue        50
#define maxNbDentsAxeRoue       500

// FilterWheel
#define maxFilterNameSize         8

#define eepromSize  1024

#ifdef SMO_PROTOCOL_PERSO
  #define SERIAL_BAUD     9600
  #define SERIAL_TIMEOUT  250
  // Caracteristiques roue a filtre
  #define defFilterSlots 5
  #define maxFilterSlots 8
  const unsigned long SMO1 = 339126000;
  const unsigned long SMO2 = 339126001;
  const int eepromStepperAddress = 0;   //Adresse de départ pour enregistrement
  const int eepromWheelAddress = 50;   //Adresse de départ pour enregistrement
#endif

#ifdef SMO_PROTOCOL_QUANTUM
  #define SERIAL_BAUD     9600
  #define SERIAL_TIMEOUT  250
  // Caracteristiques roue a filtre
  #define defFilterSlots 5
  #define maxFilterSlots 7
  const unsigned long SMO1 = 339126000;
  const unsigned long SMO2 = 339126002;
  const int eepromStepperAddress = 0;   //Adresse de départ pour enregistrement
  const int eepromWheelAddress = 200;   //Adresse de départ pour enregistrement
#endif

#ifdef SMO_PROTOCOL_OPTEC
  #define SERIAL_BAUD     19200
  #define SERIAL_TIMEOUT  250
  // Caracteristiques roue a filtre
  #define defFilterSlots 5
  #define maxFilterSlots 9
  #define defFilterWheels 1
  #define maxFilterWheels 5
  const unsigned long SMO1 = 339126000;
  const unsigned long SMO2 = 339126003;
  const int eepromStepperAddress = 0;   //Adresse de départ pour enregistrement
  const int eepromWheelAddress = 300;   //Adresse de départ pour enregistrement
#endif

//
// =============================================================================
// TYPES
// =============================================================================
//

// ma structure config stepper (10 octets)
struct stepperConfigStruct {
  int stepperMaxSpeed = defStepperMaxSpeed;
  int stepperSpeed = defStepperSpeed;
  int stepperAccel = defStepperAccel;
  float stepsPerRevolution = defStepsPerRevolution;        // steps per revolution
//  float stepsPerRevolution = 71.8;        // steps per revolution
};

// ma structure config wheel (34 octets)
struct wheelConfigStruct {
  unsigned long smo;
  stepperConfigStruct stepperConfig;
  long maxPosition = defStepperMaxPos;
  long currentPosition = 0;
  float maxAngle = defStepperMaxAngle;
  float currentAngle = 0;
  int nbDentsAxeMoteur = defNbDentsAxeMoteur;
  int nbDentsAxeRoue = defNbDentsAxeRoue;
  float ratioAxe = (float)nbDentsAxeRoue / nbDentsAxeMoteur;
  float degreePerRevolution = 360.0 / (stepperConfig.stepsPerRevolution * ratioAxe) ;    // degree per revolution
};

// ma structure config (102 octets)  SMO_PROTOCOL_PERSO
// ma structure config (90 octets)   SMO_PROTOCOL_QUANTUM
// ma structure config (556 octets)  SMO_PROTOCOL_OPTEC
struct filterWheelConfigStruct {
  unsigned long smo;
  #if defined(SMO_PROTOCOL_PERSO) or defined(SMO_PROTOCOL_QUANTUM)
    int nbFilterSlots = defFilterSlots;
    int slotsPositions[maxFilterSlots];
    int slotsOffsets[maxFilterSlots];
    char slotsNames[maxFilterSlots][maxFilterNameSize];
  #endif
  #if defined(SMO_PROTOCOL_OPTEC)
    int nbFilterWhells = defFilterWheels;
    int nbFilterSlots[maxFilterWheels];
    int slotsPositions[maxFilterWheels][maxFilterSlots];
    int slotsOffsets[maxFilterWheels][maxFilterSlots];
    char slotsNames[maxFilterWheels][maxFilterSlots][maxFilterNameSize];
  #endif
  // Keeps track of the position in slotsPosition[] we are about to run
  int currentSlot = 0; 
  int targetSlot = 0;
  #ifdef SMO_PROTOCOL_OPTEC
    int currentFilterWheel = 0;
  #endif
};

//
// ============================================================
// GLOBAL VAR
// ============================================================
//

char incomingChar;
String incomingMessage;
String lastInputMessage;
String lastOutputMessage;
bool connected = false;

#ifdef SMO_USE_WIFI
  bool connected_wifi = false;
  bool wifi_request = false;
  #ifdef SMO_USE_ESP8266
    ESP8266WiFiMulti wifiMulti;
    ESP8266WebServer server(80);
  #endif
  #ifdef SMO_USE_ESP32
    WiFiMulti wifiMulti;
    WebServer server(80);
  #endif
#endif

/*
 * AccelStepper::FULL2WIRE (2) means: 2 wire stepper (2 pins needed). 
 * AccelStepper::FULL3WIRE (3) means: 3 wire stepper, like a harddisk motor (3 pins needed). 
 * AccelStepper::FULL4WIRE (4) means: 4 wire stepper (4 pins needed). 
 * AccelStepper::HALF3WIRE (6) means: 3 wire half stepper, like a harddisk motor (3 pins needed) 
 * AccelStepper::HALF4WIRE (8) means: 4 wire half stepper (4 pins needed) 
 *
 * AccelStepper uses AccelStepper::FULL4WIRE (4 pins needed) by default.
 */
//AccelStepper stepper(AccelStepper::HALF4WIRE, motorPin1, motorPin3, motorPin2, motorPin4);
AccelStepper stepper(AccelStepper::HALF4WIRE, motorPin1, motorPin3, motorPin2, motorPin4);

wheelConfigStruct wheelConfig;
filterWheelConfigStruct filterWheelConfig;

#if defined(SMO_PROTOCOL_PERSO) or defined(SMO_PROTOCOL_QUANTUM)
  String slotsNames[maxFilterSlots];
#endif
#ifdef SMO_PROTOCOL_OPTEC
  char filterWheelsNames[maxFilterWheels] = { 'A', 'B', 'C', 'D', 'E'};
  String slotsNames[maxFilterWheels][maxFilterSlots];
#endif

// HELP PROTOCOL MESSAGE
#ifdef SMO_PROTOCOL_PERSO
  String helpmessage = FPSTR("<pre>SMO_PROTOCOL pour Roue a Filtre Perso<br>WE     --> IE      Wheel Filter Entering Serial Mode<br>WX     --> IX      Wheel Filter eXiting Serial Mode<br>WI     --> II      Wheel Init, set slot 1 and step 0<br>WR     --> IR      Wheel Reset, move to 1 and set step 0<br>WD     --> ID      Wheel Dump, Debug Dump<br>WFSG   --> IFS&lt;x&gt;    Wheel Filter Slots Get, get slots amount  1-9 (int)<br>WFSS&lt;x&gt;  --> IFS&lt;x&gt;    Wheel Filter Slots Set, set slots amount  1-9 (int)<br>WFPG   --> IFP&lt;x&gt;    Wheel Filter Pos Get, get slot position   1-9 (int)<br>WFPS&lt;x&gt;  --> IFP&lt;x&gt;    Wheel Filter Pos Set, slot number to go   1-9 (int)<br>WFPS+  --> IFP&lt;x&gt;    Wheel Filter Pos Set Next slot            1-9 (int)<br>WFPS-  --> IFP&lt;x&gt;    Wheel Filter Pos Set Previous slot        1-9 (int)<br>WFNG&lt;x&gt;  --> IFN&lt;x&gt;&lt;y&gt;   Wheel Filter Name Get, x=1-9, y=name    (max 8 char)<br>WFNS&lt;x&gt;&lt;y&gt; --> IFN&lt;x&gt;&lt;y&gt;   Wheel Filter Name Set, x=1-9, y=newname (max 8 char)<br>WFOG&lt;x&gt;  --> IFO&lt;x&gt;&lt;y&gt;   Wheel Filter Offset Get, x=1-9, y=value    1-1000 (int)<br>WFOS&lt;x&gt;&lt;y&gt; --> IFO&lt;x&gt;&lt;y&gt;   Wheel Filter Offset Set, x=1-9, y=newvalue 1-1000 (int)<br>WMSG   --> IMS&lt;x&gt; Wheel Motor Step Get, get motor step position<br>WMSS+  --> IMS&lt;x&gt; Wheel Motor Step Set +10 step (calibration move)<br>WMSS-  --> IMS&lt;x&gt; Wheel Motor Step Set -10 step (calibration move)<br>WMSS>  --> IMS&lt;x&gt; Wheel Motor Step Set +100 step (calibration move)<br>WMSS<  --> IMS&lt;x&gt; Wheel Motor Step Set -100 step (calibration move)<br>WPG&lt;x&gt;  --> IP&lt;x&gt;&lt;y&gt;     Wheel Param Get, param=x value=y<br>WPS&lt;x&gt;&lt;y&gt; --> IP&lt;x&gt;&lt;y&gt;     Wheel Param Set, param=x value=y,  Modif params<br>    param=MS   MaxSpeed           100-2000    (int)   default=1000<br>    param=CS   CurrentSpeed       100-2000    (int)   default=500<br>    param=CA   CurrentAccel       100-1000    (int)   default=200<br>    param=SR   StepPerRevolutiion 1.00-100.00 (float) default=64<br>    param=GM   GearMotor tooth    10-100      (int)   default=20<br>    param=GR   GearWheel tooth    50-500      (int)   default=100<br></pre>");
/*  const char helpmessage[] PROGMEM = R"rawliteral(
<pre>
  SMO_PROTOCOL pour Roue a Filtre Perso

  WE     --> IE      Wheel Filter Entering Serial Mode
  WX     --> IX      Wheel Filter eXiting Serial Mode
  WI     --> II      Wheel Init, set slot 1 and step 0
  WR     --> IR      Wheel Reset, move to 1 and set step 0
  WD     --> ID      Wheel Dump, Debug Dump
  WFSG   --> IFSx    Wheel Filter Slots Get, get slots amount  1-9 (int)
  WFSSx  --> IFSx    Wheel Filter Slots Set, set slots amount  1-9 (int)
  WFPG   --> IFPx    Wheel Filter Pos Get, get slot position   1-9 (int)
  WFPSx  --> IFPx    Wheel Filter Pos Set, slot number to go   1-9 (int)
  WFPS+  --> IFPx    Wheel Filter Pos Set Next slot            1-9 (int)
  WFPS-  --> IFPx    Wheel Filter Pos Set Previous slot        1-9 (int)
  WFNGx  --> IFNxy   Wheel Filter Name Get, x=1-9, y=name    (max 8 char)
  WFNSxy --> IFNxy   Wheel Filter Name Set, x=1-9, y=newname (max 8 char)
  WFOGx  --> IFOxy   Wheel Filter Offset Get, x=1-9, y=value    1-1000 (int)
  WFOSxy --> IFOxy   Wheel Filter Offset Set, x=1-9, y=newvalue 1-1000 (int)
  WMSG   --> IMSxxxx Wheel Motor Step Get, get motor step position
  WMSS+  --> IMSxxxx Wheel Motor Step Set +10 step (calibration move)
  WMSS-  --> IMSxxxx Wheel Motor Step Set -10 step (calibration move)
  WMSS>  --> IMSxxxx Wheel Motor Step Set +100 step (calibration move)
  WMSS<  --> IMSxxxx Wheel Motor Step Set -100 step (calibration move)
  WPGx  --> IPxy     Wheel Param Get, param=x value=y
  WPSxy --> IPxy     Wheel Param Set, param=x value=y,  Modif params
      param=MS   MaxSpeed           100-2000    (int)   default=1000
      param=CS   CurrentSpeed       100-2000    (int)   default=500
      param=CA   CurrentAccel       100-1000    (int)   default=200
      param=SR   StepPerRevolutiion 1.00-100.00 (float) default=64
      param=GM   GearMotor tooth    10-100      (int)   default=20
      param=GR   GearWheel tooth    50-500      (int)   default=100
</pre>
)rawliteral";
*/
#endif
#ifdef SMO_PROTOCOL_QUANTUM
  String helpmessage = FPSTR("<pre>Quantum SMO_PROTOCOL<br>EN      -->  N&lt;n&gt;        Get number of filter positions<br>DS      -->  none        Disconnect<br>G&lt;n&gt;    -->  P&lt;n&gt;        Go to filter position n<br>F&lt;n&gt;    -->  F&lt;n&gt;&lt;name&gt;     Get filter name<br>f&lt;n&gt;&lt;name&gt; -->  none        Set filter name<br>O&lt;n&gt;    -->  O&lt;n&gt;&lt;offset&gt;     Get filter offset<br>o&lt;n&gt;&lt;offset&gt; -->  none        Set filter offset<br> SN      -->  SN&lt;desc&gt; Get description<br>VR      -->  VR&lt;version&gt; Get version<br></pre>");
/*  const char helpmessage[] PROGMEM = R"rawliteral(
<pre>
 Quantum SMO_PROTOCOL

 Get number of filter positions   EN           N<number>
 Disconnect                       DS
 Go to filter position            G<number>
 position is returned when the move finishes  P<number>
 get filter name                  F<number>   F<number><name>
 set filter name                  f<number><name>
 Get filter offset                O<number>   O<number><offset>
 set filter offset                o<number><offset>
 Get description                  SN          SN<details>
 get version                      VR          VR<details>
</pre>
)rawliteral";
*/
#endif
#ifdef SMO_PROTOCOL_OPTEC
  String helpmessage = FPSTR("<pre>SMO_PROTOCOLE pour Roue a Filtre Optec<br>WSMODE  --> !<br>WVAAAA  --> V= 2.04<br>WHOME   --> A, B, C, D, E ou F, G, H<br>WIDENT  --> A, B, C, D, E ou F, G, H<br>WFILTR  --> x (1 a 5 ou 1 a 8)<br>WGOTO&lt;x&gt;  --> *<br>WREAD   --> n1..40 ou n64  (chaine de 40 ou 64 caracteres)<br>WLOAD&lt;y&gt;*&lt;n1..n40&gt; ou n64 --> !<br>WEXITS  --> END<br></pre>");
/*  const char helpmessage[] PROGMEM = R"rawliteral(
<pre>
  SMO_PROTOCOLE pour Roue a Filtre Optec

    WSMODE  --> !
    WVAAAA  --> V= 2.04
    WHOME   --> A, B, C, D, E ou F, G, H
    WIDENT  --> A, B, C, D, E ou F, G, H
    WFILTR  --> x (1 à 5 ou 1 à 8)
    WGOTOx  --> *
    WREAD   --> n1..40 ou n64  (chaine de 40 ou 64 caracteres)
    WLOADy*n1..n40 ou n64 --> !
    WEXITS  --> END
</pre>
)rawliteral";
*/
#endif

//
// =============================================================================
// FUNCTIONS (common)
// =============================================================================
//

// =============================================================================
#ifdef SMO_DEBUG
void DebugVarStepper(AccelStepper as) {
  
  Serial.println(F("DF:DebugVarStepper"));
  Serial.println(F("D:########################"));
  Serial.println(F("D:##  DEBUG VAR STEPPER ##"));
  Serial.println(F("D:########################"));
  Serial.print(F("D:maxSpeed:"));
  Serial.println(as.maxSpeed());
  Serial.print(F("D:speed:"));
  Serial.println(as.speed());
  Serial.print(F("D:currentPosition:"));
  Serial.println(as.currentPosition());
  Serial.print(F("D:targetPosition:"));
  Serial.println(as.targetPosition());
  Serial.print(F("D:distanceToGo:"));
  Serial.println(as.distanceToGo());
  Serial.println(F("D:########################"));
}
#endif

// =============================================================================
#ifdef SMO_DEBUG
void DebugVarWheelConfig(wheelConfigStruct sc) {  

  Serial.println(F("DF:DebugVar"));
  Serial.println(F("D:######################"));
  Serial.println(F("D:##  DEBUG VAR WHEEL ##"));
  Serial.println(F("D:######################"));
  Serial.print(F("D:stepperMaxSpeed:"));
  Serial.println(sc.stepperConfig.stepperMaxSpeed);
  Serial.print(F("D:stepperSpeed:"));
  Serial.println(sc.stepperConfig.stepperSpeed);
  Serial.print(F("D:stepperAccel:"));
  Serial.println(sc.stepperConfig.stepperAccel);
  Serial.print(F("D:stepsPerRevolution:"));
  Serial.println(sc.stepperConfig.stepsPerRevolution, 2);
  Serial.print(F("D:maxPosition:"));
  Serial.println(sc.maxPosition);
  Serial.print(F("D:currentPosition:"));
  Serial.println(sc.currentPosition);
  Serial.print(F("D:maxAngle:"));
  Serial.println(sc.maxAngle, 2);
  Serial.print(F("D:currentAngle:"));
  Serial.println(sc.currentAngle, 2);
  Serial.print(F("D:nbDentsAxeMoteur:"));
  Serial.println(sc.nbDentsAxeMoteur);
  Serial.print(F("D:nbDentsAxeRoue:"));
  Serial.println(sc.nbDentsAxeRoue); 
  Serial.print(F("D:ratioAxe:"));
  Serial.println(sc.ratioAxe, 2);
  Serial.print(F("D:degreePerRevolution:"));
  Serial.println(sc.degreePerRevolution, 2);
  Serial.println(F("D:######################"));
}
#endif

// =============================================================================
#ifdef SMO_DEBUG
void DebugVarFilterWheelConfig(filterWheelConfigStruct fwc) {
  int i, j;
  
  Serial.println(F("DF:DebugVar"));
  Serial.println(F("D:############################"));
  Serial.println(F("D:##  DEBUG VAR FILTERWHEEL ##"));
  Serial.println(F("D:############################"));
  #ifdef SMO_PROTOCOL_OPTEC
    Serial.print(F("D:nbFilterSlots:"));
    Serial.println(fwc.nbFilterSlots[fwc.currentFilterWheel]);
    Serial.print(F("D:slotsPositions:"));
    for (i = 0; i < fwc.nbFilterSlots[fwc.currentFilterWheel]; i++) {
      Serial.print(fwc.slotsPositions[fwc.currentFilterWheel][i]);
      Serial.print("|"); 
    }
    Serial.println("");
    Serial.print(F("D:slotsOffsets:"));
    for (i = 0; i < fwc.nbFilterSlots[fwc.currentFilterWheel]; i++) {
      Serial.print(fwc.slotsOffsets[fwc.currentFilterWheel][i]);
      Serial.print("|"); 
    }
    Serial.println("");
    Serial.print(F("D:slotsNames:"));
    for (i = 0; i < fwc.nbFilterSlots[fwc.currentFilterWheel]; i++) {
      for (j = 0; j < maxFilterNameSize; j++) {
        Serial.print(fwc.slotsNames[fwc.currentFilterWheel][i][j]);
      }
      Serial.print("|"); 
    }
    Serial.println("");
    Serial.print(F("D:slotsNames2:"));
    for (i = 0; i < fwc.nbFilterSlots[fwc.currentFilterWheel]; i++) {
      Serial.print(slotsNames[fwc.currentFilterWheel][i]);
      Serial.print("|"); 
    }
    Serial.println("");
  #else
    Serial.print(F("D:nbFilterSlots:"));
    Serial.println(fwc.nbFilterSlots);
    Serial.print(F("D:slotsPositions:"));
    for (i = 0; i < fwc.nbFilterSlots; i++) {
      Serial.print(fwc.slotsPositions[i]);
      Serial.print("|"); 
    }
    Serial.println("");
    Serial.print(F("D:slotsOffsets:"));
    for (i = 0; i < fwc.nbFilterSlots; i++) {
      Serial.print(fwc.slotsOffsets[i]);
      Serial.print("|"); 
    }
    Serial.println("");
    Serial.print(F("D:slotsNames:"));
    for (i = 0; i < fwc.nbFilterSlots; i++) {
      for (j = 0; j < maxFilterNameSize; j++) {
        Serial.print(fwc.slotsNames[i][j]);
      }
      Serial.print("|"); 
    }
    Serial.println("");
    Serial.print(F("D:slotsNames2:"));
    for (i = 0; i < fwc.nbFilterSlots; i++) {
      Serial.print(slotsNames[i]);
      Serial.print("|"); 
    }
    Serial.println("");
  #endif
  #ifdef SMO_PROTOCOL_OPTEC
    Serial.print(F("D:currentFilterWheel:"));
    Serial.println(fwc.currentFilterWheel);
  #endif
  Serial.print(F("D:currentSlot:"));
  Serial.println(fwc.currentSlot);
  Serial.print(F("D:targetSlot:"));
  Serial.println(fwc.targetSlot);
  Serial.println(F("D:######################"));
}
#endif

// =============================================================================
#ifdef SMO_DEBUG
void DebugVar() {  

  Serial.println(F("DF:DebugVar"));
  Serial.println(F("D:################"));
  Serial.println(F("D:##  DEBUG VAR ##"));
  Serial.println(F("D:################"));
  DebugVarWheelConfig(wheelConfig);
  DebugVarStepper(stepper);
  DebugVarFilterWheelConfig(filterWheelConfig);
  Serial.println(F("D:################"));
}
#endif

// =============================================================================
#if defined(SMO_DEBUG) and defined(SMO_USE_WIFI)
void DebugWifi() {  

  Serial.println(F("DF:DebugWifi"));
  Serial.println(F("D:#################"));
  Serial.println(F("D:##  DEBUG WIFI ##"));
  Serial.println(F("D:#################"));
  #ifdef SMO_USE_WIFI_ADHOC
    Serial.println(F("D:WiFi created"));
    Serial.print(F("D:MAC : "));
    Serial.println(WiFi.softAPmacAddress());
    Serial.print(F("D:IP address: "));
    Serial.println(WiFi.softAPIP());
  #endif
  #ifdef SMO_USE_WIFI_CLIENT
    Serial.println(F("D:WiFi connected"));
    Serial.print(F("D:MAC : "));
    Serial.println(WiFi.macAddress());
    Serial.print(F("D:IP address: "));
    Serial.println(WiFi.localIP());
    Serial.print(F("D:SSID: "));
    Serial.println(WiFi.SSID());
    WiFi.printDiag(Serial);
  #endif
  Serial.println(F("D:################"));
}
#endif

// =============================================================================
#if defined(SMO_USE_WIFI) and defined(SMO_USE_WIFI_OTA)
void InitOTA(void) {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:InitOTA"));
  #endif

  // Hostname defaults to esp8266-[ChipID]
  ArduinoOTA.setHostname(SMO_HOSTNAME);

  // No authentication by default
  ArduinoOTA.setPassword((const char *)SMO_OTA_PASSWORD);

  ArduinoOTA.onStart([]() {
    Serial.println(F("\nOTA Start"));
  });
  ArduinoOTA.onEnd([]() {
    Serial.println(F("\nOTA End"));
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("OTA Progress: %u%%\r", (progress / (total / 100)));
  });
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) Serial.println(F("OTA Auth Failed"));
    else if (error == OTA_BEGIN_ERROR) Serial.println(F("OTA Begin Failed"));
    else if (error == OTA_CONNECT_ERROR) Serial.println(F("OTA Connect Failed"));
    else if (error == OTA_RECEIVE_ERROR) Serial.println(F("OTA Receive Failed"));
    else if (error == OTA_END_ERROR) Serial.println(F("OTA End Failed"));
  });
  ArduinoOTA.begin();
}
#endif

// =============================================================================
#ifdef SMO_USE_WIFI
void HandleRoot() {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:HandleRoot"));
  #endif
  HandleSelectFilter();  
}
#endif

// =============================================================================
#ifdef SMO_USE_WIFI
void HandleSelectFilter() {
  String html_page;
  int i;

  #ifdef SMO_DEBUG
    Serial.println(F("DF:HandleSelectFilter"));
  #endif
  html_page = "<!DOCTYPE html><html lang=\"en-us\">";
  html_page += "<head><meta charset=\"UTF-8\">";
  html_page += "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"</head><body>";
  html_page += "<h2>Filter Wheel " + String(SMO_PRODUCT) + " " + String(SMO_VERSION) + "<h2><h3>Select Filters</h3>";
  html_page += "<form action=\"/select_filter\" method=\"POST\">";
  html_page += "<table><thead><tr><th>Filtre</th><th>Name</th><th>Select</th></thead></tr><tbody>";
  #ifdef SMO_PROTOCOL_OPTEC
  for (i = 0; i < filterWheelConfig.nbFilterSlots[filterWheelConfig.currentFilterWheel]; i++) {
    html_page += "<tr><td>" + String(i+1) + "</td>:<td>" + slotsNames[filterWheelConfig.currentFilterWheel][i];
  #else
  for (i = 0; i < filterWheelConfig.nbFilterSlots; i++) {
    html_page += "<tr><td>" + String(i+1) + "</td>:<td>" + slotsNames[i];
  #endif
    html_page += "</td><td><input type=\"radio\" name=\"filters\" value=\"filter" + String(i+1) + "\"";
    if (i == filterWheelConfig.currentSlot) {
      html_page += "checked";
    }
    html_page += "></td></tr>";
  }
  html_page += "</tbody></table><br><input type=\"submit\" value=\"Submit\"><br><br>";
  html_page += "<a href=\"/config_filters\">Config Filters</a><br>";
  html_page += "<a href=\"/config_wheel\">Config Wheel</a><br>";
  html_page += "<a href=\"/send_message\">Send Messages</a><br>";
  html_page += "<a href=\"/manual_move\">Manual Move</a><br>";
  html_page += "<a href=\"/debug\">Debug</a>";
  html_page += "</form>";
  html_page += "</body></html>";

  server.setContentLength(html_page.length());  // Permet l'affichage plus rapide après chaque clic sur les boutons
  server.send(200, "text/html", html_page);
}
#endif

// =============================================================================
#ifdef SMO_USE_WIFI
void HandleRespSelectFilter() {
  String element;
  int i;

  #ifdef SMO_DEBUG
    Serial.println(F("DF:HandleRespSelectFilter"));
  #endif
  #ifdef SMO_DEBUG
    for (i=0; i <= server.args(); i++) {
      Serial.println(server.arg(i));
    }
  #endif
  
  #ifdef SMO_PROTOCOL_OPTEC
  for (i = 1; i <= filterWheelConfig.nbFilterSlots[filterWheelConfig.currentFilterWheel]; i++) {
    element = String("filter") + String(i);
    if ( server.hasArg("filters") && server.arg("filters") == element ) {
      filterWheelConfig.targetSlot = i - 1; 
    }
  }
  if (filterWheelConfig.targetSlot != filterWheelConfig.currentSlot) {
    #ifdef SMO_BACKGROUND_MOVE
      stepper.moveTo(degsToSteps(filterWheelConfig.slotsPositions[filterWheelConfig.currentFilterWheel][filterWheelConfig.targetSlot]));
    #else
      stepper.runToNewPosition(degsToSteps(filterWheelConfig.slotsPositions[filterWheelConfig.currentFilterWheel][filterWheelConfig.targetSlot]));
    #endif
    filterWheelConfig.currentSlot = filterWheelConfig.targetSlot;
  }
  #else
  for (i = 1; i <= filterWheelConfig.nbFilterSlots; i++) {
    element = String("filter") + String(i);
    if ( server.hasArg("filters") && server.arg("filters") == element ) {
      filterWheelConfig.targetSlot = i - 1; 
    }
  }
  if (filterWheelConfig.targetSlot != filterWheelConfig.currentSlot) {
    #ifdef SMO_BACKGROUND_MOVE
      stepper.moveTo(degsToSteps(filterWheelConfig.slotsPositions[filterWheelConfig.targetSlot]));
    #else
      stepper.runToNewPosition(degsToSteps(filterWheelConfig.slotsPositions[filterWheelConfig.targetSlot]));
    #endif
    filterWheelConfig.currentSlot = filterWheelConfig.targetSlot;
  }
  #endif
  
  HandleSelectFilter();
}
#endif

// =============================================================================
#ifdef SMO_USE_WIFI
void HandleConfigFilters() {
  String html_page;
  int i;

  #ifdef SMO_DEBUG
    Serial.println(F("DF:HandleConfigFilters"));
  #endif
  html_page = "<!DOCTYPE html><html lang=\"en-us\">";
  html_page += "<head><meta charset=\"UTF-8\">";
  html_page += "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"</head><body>";
  html_page += "<h2>Filter Wheel " + String(SMO_PRODUCT) + " " + String(SMO_VERSION) + "<h2><h3>Config Filters</h3>";
  html_page += "<form action=\"/config_filters\" method=\"POST\">";
  html_page += "<table><thead><tr><th>Filtre</th><th>Name</th><th>Offset</th></thead></tr><tbody>";
  #ifdef SMO_PROTOCOL_OPTEC
  for (i = 0; i < filterWheelConfig.nbFilterSlots[filterWheelConfig.currentFilterWheel]; i++) {
    html_page += "<tr><td>" + String(i+1) + "</td>:<td><input type=\"text\" name=\"namefilter";
    html_page += String(i + 1) + "\" value=\"" + slotsNames[filterWheelConfig.currentFilterWheel][i] + "\" size=10 maxlength=8>";
    html_page += "</td><td><input type=\"text\" name=\"offsetfilter" + String(i + 1);
    html_page += "\" value=\"" + String(filterWheelConfig.slotsOffsets[filterWheelConfig.currentFilterWheel][i]) + "\"></td></tr>";
  }
  #else
  for (i = 0; i < filterWheelConfig.nbFilterSlots; i++) {
    html_page += "<tr><td>" + String(i+1) + "</td>:<td><input type=\"text\" name=\"namefilter";
    html_page += String(i + 1) + "\" value=\"" + slotsNames[i] + "\" size=10 maxlength=8>";
    html_page += "</td><td><input type=\"text\" name=\"offsetfilter" + String(i + 1);
    html_page += "\" value=\"" + String(filterWheelConfig.slotsOffsets[i]) + "\"></td></tr>";
  }
  #endif
  html_page += "</tbody></table><br><input type=\"submit\" value=\"Submit\"><br><br>";
  html_page += "</form>";
  html_page += "<a href=\"/\">Back</a>";
  html_page += "</body></html>";

  server.setContentLength(html_page.length());  // Permet l'affichage plus rapide après chaque clic sur les boutons
  server.send(200, "text/html", html_page);
}
#endif

// =============================================================================
#ifdef SMO_USE_WIFI
void HandleRespConfigFilters() {
  String element, s_number;
  int i_number;
  bool configmodif = false;
  int i;

  #ifdef SMO_DEBUG
    Serial.println(F("DF:HandleRespConfigFilters"));
  #endif
  #ifdef SMO_DEBUG
    for (i=0; i <= server.args(); i++) {
      Serial.println(server.arg(i));
    }
  #endif

  #if defined(SMO_PROTOCOL_OPTEC)
    for ( int i = 0 ; i < filterWheelConfig.nbFilterSlots[filterWheelConfig.currentFilterWheel]; i++) {
      element = String("namefilter") + String(i + 1);
      if ( server.hasArg(element) ) {
        slotsNames[filterWheelConfig.currentFilterWheel][i] = server.arg(element);
        configmodif = true;
      }
    }
  #else
    for ( int i = 0 ; i < filterWheelConfig.nbFilterSlots; i++) {
      element = String("namefilter") + String(i + 1);
      if ( server.hasArg(element) ) {
        slotsNames[i] = server.arg(element);
        configmodif = true;
      }
    }
  #endif
  #if defined(SMO_PROTOCOL_OPTEC)
    for ( int i = 0 ; i < filterWheelConfig.nbFilterSlots[filterWheelConfig.currentFilterWheel]; i++) {
      element = String("offsetfilter") + String(i + 1);
      if ( server.hasArg(element) && server.arg(element) != NULL ) {
        s_number = server.arg(element);
        i_number = s_number.toInt();
        filterWheelConfig.slotsOffsets[filterWheelConfig.currentFilterWheel][i] = i_number;
        configmodif = true;
      }
    }
  #else
    for ( int i = 0 ; i < filterWheelConfig.nbFilterSlots; i++) {
      element = String("offsetfilter") + String(i + 1);
      if ( server.hasArg(element) && server.arg(element) != NULL ) {
        s_number = server.arg(element);
        i_number = s_number.toInt();
        filterWheelConfig.slotsOffsets[i] = i_number;
        configmodif = true;
      }
    }
  #endif

  if (configmodif) {
    CopyToSlotsNamesConfig();
    SauvegardeFilterWheelEEPROM();
  }

  HandleConfigFilters();
}
#endif

// =============================================================================
#ifdef SMO_USE_WIFI
void HandleConfigWheel() {
  String html_page;

  #ifdef SMO_DEBUG
    Serial.println(F("DF:HandleConfigWheel"));
  #endif
  html_page = "<!DOCTYPE html><html lang=\"en-us\">";
  html_page += "<head><meta charset=\"UTF-8\">";
  html_page += "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"</head><body>";
  html_page += "<h2>Filter Wheel " + String(SMO_PRODUCT) + " " + String(SMO_VERSION) + "<h2><h3>Config Wheel</h3>";
  html_page += "<form action=\"/config_wheel\" method=\"POST\">";
  html_page += "<table><thead><tr><th>Name</th><th>Value</th><th>Min-Max</th></thead></tr><tbody>";
  html_page += "<tr><td>Nb Filtres</td>:<td><input type=\"text\" name=\"nbfiltres\" value=\"";
  #ifdef SMO_PROTOCOL_OPTEC
  html_page += String(filterWheelConfig.nbFilterSlots[filterWheelConfig.currentFilterWheel]) + "\"></td><td>1-9</td></tr>";
  #else
  html_page += String(filterWheelConfig.nbFilterSlots) + "\"></td><td>1-9</td></tr>";
  #endif
  html_page += "<tr><td>MaxSpeed</td>:<td><input type=\"text\" name=\"maxspeed\" value=\"";
  html_page += String(wheelConfig.stepperConfig.stepperMaxSpeed) + "\"></td><td>" + String(minStepperMaxSpeed) + "-" + String(maxStepperMaxSpeed) + "</td></tr>";
  html_page += "<tr><td>Speed</td>:<td><input type=\"text\" name=\"speed\" value=\"";
  html_page += String(wheelConfig.stepperConfig.stepperSpeed) + "\"></td><td>" + String(minStepperSpeed) + "-" + String(maxStepperSpeed) + "</td></tr>";
  html_page += "<tr><td>Accel</td>:<td><input type=\"text\" name=\"accel\" value=\"";
  html_page += String(wheelConfig.stepperConfig.stepperAccel) + "\"></td><td>" + String(minStepperAccel) + "-" + String(maxStepperAccel) + "</td></tr>";
  html_page += "<tr><td>StepsPerRevolution</td>:<td><input type=\"text\" name=\"steprevolution\" value=\"";
  html_page += String(wheelConfig.stepperConfig.stepsPerRevolution) + "\"></td><td>" + String(minStepsPerRevolution) + "-" + String(maxStepsPerRevolution) + "</td></tr>";
  html_page += "<tr><td>GearMotor</td>:<td><input type=\"text\" name=\"gearmotor\" value=\"";
  html_page += String(wheelConfig.nbDentsAxeMoteur) + "\"></td><td>" + String(minNbDentsAxeMoteur) + "-" + String(maxNbDentsAxeMoteur) + "</td></tr>";
  html_page += "<tr><td>GearWheel</td>:<td><input type=\"text\" name=\"gearwheel\" value=\"";
  html_page += String(wheelConfig.nbDentsAxeRoue) + "\"></td><td>" + String(minNbDentsAxeRoue) + "-" + String(maxNbDentsAxeRoue) + "</td></tr>";
  html_page += "</tbody></table><br><input type=\"submit\" value=\"Submit\"><br><br>";
  html_page += "</form>";
  html_page += "<a href=\"/\">Back</a>";
  html_page += "</body></html>";

  server.setContentLength(html_page.length());  // Permet l'affichage plus rapide après chaque clic sur les boutons
  server.send(200, "text/html", html_page);
}
#endif

// =============================================================================
#ifdef SMO_USE_WIFI
void HandleRespConfigWheel() {
  String s_number;
  float f_number;
  int i_number;
  bool configmodif = false;

  #ifdef SMO_DEBUG
    Serial.println(F("DF:HandleRespConfigFocuserEP"));
  #endif
  #ifdef SMO_DEBUG
    for (int i=0; i <= server.args(); i++) {
      Serial.println(server.arg(i));
    }
  #endif  
  if ( server.hasArg("nbfiltres") && server.arg("nbfiltres") != NULL ) {
    s_number = server.arg("nbfiltres");
    i_number = s_number.toInt();
    if (i_number >= 1 && i_number <= maxFilterSlots) {
      #ifdef SMO_PROTOCOL_OPTEC
        filterWheelConfig.nbFilterSlots[filterWheelConfig.currentFilterWheel] = i_number;
      #else
        filterWheelConfig.nbFilterSlots = i_number;
      #endif
      configmodif = true;
    }
  }

  if ( server.hasArg("maxspeed") && server.arg("maxspeed") != NULL ) {
    s_number = server.arg("maxspeed");
    i_number = s_number.toInt();
    if (i_number >= minStepperMaxSpeed && i_number <= maxStepperMaxSpeed) {
      wheelConfig.stepperConfig.stepperMaxSpeed = i_number;
      configmodif = true;
    }
  }

  if ( server.hasArg("speed") && server.arg("speed") != NULL ) {
    s_number = server.arg("speed");
    i_number = s_number.toInt();
    if (i_number >= minStepperSpeed && i_number <= maxStepperSpeed) {
      wheelConfig.stepperConfig.stepperSpeed = i_number;
      configmodif = true;
    }
  }

  if ( server.hasArg("accel") && server.arg("accel") != NULL ) {
    s_number = server.arg("accel");
    i_number = s_number.toInt();
    if (i_number >= minStepperAccel && i_number <= maxStepperAccel) {
      wheelConfig.stepperConfig.stepperAccel = i_number;
      configmodif = true;
    }
  }

  if ( server.hasArg("steprevolution") && server.arg("steprevolution") != NULL ) {
    String s_number = server.arg("steprevolution");
    f_number = s_number.toFloat();
    if (f_number >= minStepsPerRevolution && f_number <= maxStepsPerRevolution) {
      wheelConfig.stepperConfig.stepsPerRevolution = f_number;
      configmodif = true;
    }
  }

  if ( server.hasArg("gearmotor") && server.arg("gearmotor") != NULL ) {
    s_number = server.arg("gearmotor");
    i_number = s_number.toInt();
    if (i_number >= minNbDentsAxeMoteur && i_number <= maxNbDentsAxeMoteur ) {
      wheelConfig.nbDentsAxeMoteur = i_number;
      configmodif = true;
    }
  }

  if ( server.hasArg("gearwheel") && server.arg("gearwheel") != NULL ) {
    s_number = server.arg("gearwheel");
    i_number = s_number.toInt();
    if (i_number >= minNbDentsAxeRoue && i_number <= maxNbDentsAxeRoue) {
      wheelConfig.nbDentsAxeRoue = i_number;
      configmodif = true;
    }
  }

  if (configmodif) {
    ComputeParams();
    SauvegardeWheelEEPROM();
  }
  
  HandleConfigWheel();
}
#endif

// =============================================================================
#ifdef SMO_USE_WIFI
void HandleSendMessage() {
  String html_page;

  #ifdef SMO_DEBUG
    Serial.println(F("DF:HandleSendMessage"));
  #endif
  // When URI / is requested, send a web page with a button to get the message
  html_page = "<!DOCTYPE html><html lang=\"en-us\">";
  html_page += "<head><meta charset=\"UTF-8\">";
  html_page += "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"</head><body>";
  html_page += "<h2>Filter Wheel " + String(SMO_PRODUCT) + " " + String(SMO_VERSION) + "<h2><h3>Send Message</h3>";
  html_page += "<form name=\"send_message\" action=\"/send_message\" method=\"POST\"><input type=\"text\"";
  html_page += "name=\"message\" placeholder=\"Message\"></br><input type=\"submit\" value=\"Submit\">";
  html_page += "</form>";
  if ( lastInputMessage != NULL ) {
    html_page += "<br>Last message : " + lastInputMessage + "<br>";
    html_page += "Last result  : " + lastOutputMessage + "<br>";
    html_page += "<form name=\"refresh\" action=\"/send_message\" method=\"GET\">";
    html_page += "<input type=\"submit\" value=\"Refresh\"><br><br>";
    html_page += "</form>";
  }
  html_page += "<br><a href=\"/\">Back</a>";
  html_page += helpmessage;
  html_page += "</body></html>";

  server.setContentLength(html_page.length());  // Permet l'affichage plus rapide après chaque clic sur les boutons
  server.send(200, "text/html", html_page);
}
#endif

// =============================================================================
#ifdef SMO_USE_WIFI
void HandleRespSendMessage() {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:HandleRespSendMessage"));
  #endif
  if( ! server.hasArg("message") || server.arg("message") == NULL) {
    // If the POST request doesn't have data
    server.send(400, "text/plain", "400: Invalid Request");
    // The request is invalid, so send HTTP status 400
    return;
  }
  incomingMessage = server.arg("message");
  lastInputMessage = server.arg("message");
  lastOutputMessage = "please refresh, asynchronous message handling...";
  wifi_request = true;
  HandleSendMessage();
}
#endif

// =============================================================================
#ifdef SMO_USE_WIFI
void HandleManualMove() {
  String html_page;

  #ifdef SMO_DEBUG
    Serial.println(F("DF:HandleManualMove"));
  #endif
  // When URI / is requested, send a web page with a button to get the message
  html_page = "<!DOCTYPE html><html lang=\"en-us\">";
  html_page += "<head><meta charset=\"UTF-8\">";
  html_page += "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"</head><body>";
  html_page += "<h2>Filter Wheel " + String(SMO_PRODUCT) + " " + String(SMO_VERSION) + "<h2><h3>Manual Move</h3>";
  html_page += "Note: filter wheel reset to slot 1 position after a \"valid\" manual move<br>";
  html_page += "Note: enter a \"valid\" angle between 360.0 and -360.0 degree<br>";
  html_page += "<form action=\"/manual_move\" method=\"POST\"><input type=\"text\"";
  html_page += "name=\"angle\" placeholder=\"Angle (deg)\"></br><input type=\"submit\" value=\"Submit\"><br><br>";
  html_page += "</form>";
  html_page += "<a href=\"/\">Back</a>";
  html_page += "</body></html>";

  server.setContentLength(html_page.length());  // Permet l'affichage plus rapide après chaque clic sur les boutons
  server.send(200, "text/html", html_page);
}
#endif

// =============================================================================
#ifdef SMO_USE_WIFI
void HandleRespManualMove() {
  String s_number;
  float f_number;

  #ifdef SMO_DEBUG
    Serial.println(F("DF:HandleRespManualMove"));
  #endif
  #ifdef SMO_DEBUG
    for (int i=0; i <= server.args(); i++) {
      Serial.println(server.arg(i));
    }
  #endif
  if ( server.hasArg("angle") && server.arg("angle") != NULL ) {
    s_number = server.arg("angle");
    f_number = s_number.toFloat();
    if (f_number >= -360.0 && f_number <= 360.0) {
      if (!stepper.isRunning() && stepper.distanceToGo() == 0) {
        #ifdef SMO_BACKGROUND_MOVE
          /*stepper.moveTo(stepper.currentPosition() + degsToSteps(f_number));
          int startTime=millis();
          int currentTime;
          while (stepper.distanceToGo() != 0) {
            stepper.run();
            currentTime=millis();
            if ((currentTime-startTime)>1000) {
              startTime=currentTime;
            }
          }*/
          stepper.moveTo(stepper.currentPosition() + degsToSteps(f_number));
        #else
          stepper.runToNewPosition(stepper.currentPosition() + degsToSteps(f_number));
        #endif
        InitStepper();
        filterWheelConfig.currentSlot = 0;
        filterWheelConfig.targetSlot = 0;
      }
    }
  }

  HandleManualMove();
}
#endif

// =============================================================================
#ifdef SMO_USE_WIFI
String HandleDebugStepper(AccelStepper as) {
  String html_page;

  #ifdef SMO_DEBUG
    Serial.println(F("DF:HandleDebugStepper"));
  #endif
  html_page = "D:maxSpeed:" + String(as.maxSpeed()) + "<br>";
  html_page += "D:speed:" + String(as.speed()) + "<br>";
  html_page += "D:currentPosition:" + String(as.currentPosition()) + "<br>";
  html_page += "D:targetPosition:" + String(as.targetPosition()) + "<br>";
  html_page += "D:distanceToGo:" + String(as.distanceToGo()) + "<br><br>";

  return html_page;
}
#endif

// =============================================================================
#ifdef SMO_USE_WIFI
String HandleDebugWheel(wheelConfigStruct wc) {
  String html_page;

  #ifdef SMO_DEBUG
    Serial.println(F("DF:HandleDebugWheel"));
  #endif
  html_page = "D:stepperMaxSpeed:" + String(wc.stepperConfig.stepperMaxSpeed) + "<br>";
  html_page += "D:stepperSpeed:" + String(wc.stepperConfig.stepperSpeed) + "<br>";
  html_page += "D:stepperAccel:" + String(wc.stepperConfig.stepperAccel) + "<br>";
  html_page += "D:stepsPerRevolution:"+ String(wc.stepperConfig.stepsPerRevolution, 2) + "<br>";
  html_page += "D:maxPosition:" + String(wc.maxPosition) + "<br>";
  html_page += "D:currentPosition:" + String(wc.currentPosition) + "<br>";
  html_page += "D:nbDentsAxeMoteur:"+ String(wc.nbDentsAxeMoteur) + "<br>";
  html_page += "D:nbDentsAxeRoue:" + String(wc.nbDentsAxeRoue) + "<br>";
  html_page += "D:ratioAxe:" + String(wc.ratioAxe, 2) + "<br>";
  html_page += "D:degreePerRevolution:" + String(wc.degreePerRevolution, 2) + "<br><br>";

  return html_page;
}
#endif

// =============================================================================
#ifdef SMO_USE_WIFI
String HandleDebugFilterWheel(filterWheelConfigStruct fwc) {
  int i, j;
  String html_page;

  #ifdef SMO_DEBUG
    Serial.println(F("DF:HandleDebugFilterWheel"));
  #endif
  html_page = "";
  #ifdef SMO_PROTOCOL_OPTEC
    html_page += "D:nbFilterSlots:" + String(fwc.nbFilterSlots[fwc.currentFilterWheel]) + "<br>";
    html_page += "D:slotsPositions:";
    for (i = 0; i < fwc.nbFilterSlots[fwc.currentFilterWheel]; i++) {
      html_page += String(fwc.slotsPositions[fwc.currentFilterWheel][i]) + "|";
    }
    html_page += "<br>";
    html_page += "D:slotsOffsets:";
    for (i = 0; i < fwc.nbFilterSlots[fwc.currentFilterWheel]; i++) {
      html_page += String(fwc.slotsOffsets[fwc.currentFilterWheel][i]) + "|";
    }
    html_page += "<br>";
    html_page += "D:slotsNames:";
    for (i = 0; i < fwc.nbFilterSlots[fwc.currentFilterWheel]; i++) {
      for (j = 0; j < maxFilterNameSize; j++) {
        html_page += String(fwc.slotsNames[fwc.currentFilterWheel][i][j]) + "|";
      }
    }
    html_page += "<br>";
    html_page += "D:slotsNames2:";
    for (i = 0; i < fwc.nbFilterSlots[fwc.currentFilterWheel]; i++) {
      html_page += String(slotsNames[fwc.currentFilterWheel][i])+ "|";
    }
    html_page += "<br>";
  #else
    html_page += "D:nbFilterSlots:" + String(fwc.nbFilterSlots) + "<br>";
    html_page += "D:slotsPositions:";
    for (i = 0; i < fwc.nbFilterSlots; i++) {
      html_page += String(fwc.slotsPositions[i]) + "|";
    }
    html_page += "<br>";
    html_page += "D:slotsOffsets:";
    for (i = 0; i < fwc.nbFilterSlots; i++) {
      html_page += String(fwc.slotsOffsets[i]) + "|";
    }
    html_page += "<br>";
    html_page += "D:slotsNames:";
    for (i = 0; i < fwc.nbFilterSlots; i++) {
      for (j = 0; j < maxFilterNameSize; j++) {
        html_page += String(fwc.slotsNames[i][j]);
      }
      html_page += "|";
    }
    html_page += "<br>";
    html_page += "D:slotsNames2:";
    for (i = 0; i < fwc.nbFilterSlots; i++) {
      html_page += String(slotsNames[i]) + "|"; 
    }
    html_page += "<br>";
  #endif
  #ifdef SMO_PROTOCOL_OPTEC
    html_page += "D:currentFilterWheel:" + String(fwc.currentFilterWheel) + "<br>";
  #endif
  html_page += "D:currentSlot:" + String(fwc.currentSlot) + "<br>";
  html_page += "D:targetSlot:" + String(fwc.targetSlot) + "<br>";

  return html_page;
}
#endif

// =============================================================================
#ifdef SMO_USE_WIFI
String HandleDebugWifi() {
  int i, j;
  String html_page;

  #ifdef SMO_DEBUG
    Serial.println(F("DF:HandleDebugWifi"));
  #endif
  html_page = "";

  #ifdef SMO_USE_WIFI_ADHOC
    html_page += "D:WiFi created<br>";
    html_page += "D:MAC : " + String(WiFi.softAPmacAddress()) + "<br>";
    html_page += "D:IP address: " + WiFi.softAPIP().toString() + "<br>";
  #endif
  #ifdef SMO_USE_WIFI_CLIENT
    html_page += "D:WiFi connected<br>";
    html_page += "D:MAC : " + String(WiFi.macAddress()) + "<br>";
    html_page += "D:IP address: " + WiFi.localIP().toString() + "<br>";
    html_page += "D:SSID: " + WiFi.SSID() + "<br><br>";
    //html_page += String(WiFi.printDiag());
  #endif
  
  return html_page;
}
#endif

// =============================================================================
#ifdef SMO_USE_WIFI
void HandleDebug() {
  String html_page;

  #ifdef SMO_DEBUG
    Serial.println(F("DF:HandleDebug"));
  #endif  
  // When URI / is requested, send a web page with a button to get the message
  html_page = "<!DOCTYPE html><html lang=\"en-us\">";
  html_page += "<head><meta charset=\"UTF-8\">";
  html_page += "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"</head><body>";
  html_page += "<h2>Filter Wheel " + String(SMO_PRODUCT) + " " + String(SMO_VERSION) + "<h2><h3>Debug</h3>";
  html_page += "D:#############################<br>D:## DEBUG WIFI ##<br>D:#############################<br>";
  html_page += "D:Debug Wifi<br>";
  html_page += HandleDebugWifi();
  html_page += "D:#############################<br>D:## DEBUG VAR WHEEL ##<br>D:#############################<br>";  
  html_page += "D:Debug wheelConfig<br>";
  html_page += HandleDebugWheel(wheelConfig);
  html_page += "D:#######################<br>D:## DEBUG VAR STEPPER ##<br>D:#######################<br>";
  html_page += "D:Debug stepper<br>";
  html_page += HandleDebugStepper(stepper);
  html_page += "D:###########################<br>D:## DEBUG VAR FILTERWHEEL ##<br>D:###########################<br>";
  html_page += "D:Debug filterWheelConfig<br>";
  html_page += HandleDebugFilterWheel(filterWheelConfig);
  html_page += "D:####################<br>";
  html_page += "<br><form action=\"/debug\" method=\"POST\">";
  html_page += "<input type=\"submit\" value=\"Refresh\"><br><br>";
  html_page += "</form>";
  html_page += "<a href=\"/\">Back</a>";
  html_page += "</body></html>";

  server.setContentLength(html_page.length());  // Permet l'affichage plus rapide après chaque clic sur les boutons
  server.send(200, "text/html", html_page);
}
#endif

// =============================================================================
#ifdef SMO_USE_WIFI
void HandleRespDebug() {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:HandleRespDebug"));
  #endif
  HandleDebug();
}
#endif

// =============================================================================
#ifdef SMO_USE_WIFI
void HandleUnknownMessage() {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:HandleUnknownMessage"));
  #endif
  lastOutputMessage = "message -> " + lastInputMessage + " <- is unknown !";
 }
#endif

// =============================================================================
#ifdef SMO_USE_WIFI
void HandleCorrectMessage(String protocolResponse) {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:HandleCorrectMessage"));
  #endif
  lastOutputMessage = protocolResponse;
 }
#endif

// =============================================================================
#ifdef SMO_USE_WIFI
void HandleNotFound(){

  #ifdef SMO_DEBUG
    Serial.println(F("DF:HandleNotFound"));
  #endif
  // Send HTTP status 404 (Not Found) when there's no handler for the URI in the request
  server.send(404, "text/plain", "404: Not found");
}
#endif

// =============================================================================
#ifdef SMO_USE_WIFI
void InitWifi(void) {
  int count = 3;

  #ifdef SMO_DEBUG
    Serial.println(F("DF:InitWifi"));
  #endif

  WiFi.persistent(false);
  #if defined(SMO_USE_WIFI_CLIENT) and defined(SMO_USE_WIFI_ADHOC)
    WiFi.mode(WIFI_AP_STA);
  #elif defined(SMO_USE_WIFI_ADHOC)
    WiFi.mode(WIFI_AP);
  #elif defined(SMO_USE_WIFI_CLIENT)
    WiFi.mode(WIFI_STA);
  #endif

  #ifdef SMO_USE_WIFI_ADHOC
    #ifdef SMO_DEBUG
      Serial.println(F("D:Creating Wifi..."));
    #endif
    WiFi.softAP(SSID_AP,PASSWORD_AP);
      #ifdef SMO_DEBUG
        Serial.println(F("D:WiFi created"));
        Serial.print(F("D:MAC : "));
        Serial.println(WiFi.softAPmacAddress());
        Serial.print(F("D:IP address: "));
        Serial.println(WiFi.softAPIP());
      #endif
    connected_wifi = true;
  #endif

  #ifdef SMO_USE_WIFI_CLIENT
    // We start by connecting to a WiFi network
    wifiMulti.addAP(SSID_1, PASSWORD_1);
    #ifdef SSID_2
      wifiMulti.addAP(SSID_2, PASSWORD_2);
    #endif
    #ifdef SSID_3
      wifiMulti.addAP(SSID_3, PASSWORD_3);
    #endif
    #ifdef SMO_DEBUG
      Serial.println(F("D:Connecting Wifi..."));
    #endif
    while ( wifiMulti.run() != WL_CONNECTED && count >= 0) {
      delay ( 500 );
      count--;
    #ifdef SMO_DEBUG
      Serial.print ( "." );
    #endif
    }
    if (wifiMulti.run() == WL_CONNECTED) {
      #ifdef SMO_DEBUG
        Serial.println(F("D:WiFi connected"));
        Serial.print(F("D:MAC : "));
        Serial.println(WiFi.macAddress());
        Serial.print(F("D:IP address: "));
        Serial.println(WiFi.localIP());
        WiFi.printDiag(Serial);
      #endif
      connected_wifi = true;
    }
  #endif

    if (MDNS.begin(SMO_HOSTNAME)) {              // Start the mDNS responder for esp8266.local
      #ifdef SMO_DEBUG
        Serial.println(F("D:mDNS responder started"));
      #endif
    } else {
      #ifdef SMO_DEBUG
        Serial.println(F("D:Error setting up MDNS responder!"));
      #endif
    }

    // Start Web Server
    // Call the 'handleRoot' function when a client requests URI "/"
    server.on("/", HTTP_GET, HandleRoot);
    server.on("/select_filter", HTTP_GET, HandleSelectFilter);
    server.on("/select_filter", HTTP_POST, HandleRespSelectFilter);
    server.on("/config_filters", HTTP_GET, HandleConfigFilters);
    server.on("/config_filters", HTTP_POST, HandleRespConfigFilters);
    server.on("/config_wheel", HTTP_GET, HandleConfigWheel);
    server.on("/config_wheel", HTTP_POST, HandleRespConfigWheel);
    server.on("/send_message", HTTP_GET, HandleSendMessage);
    server.on("/send_message", HTTP_POST, HandleRespSendMessage);
    server.on("/manual_move", HTTP_GET, HandleManualMove);
    server.on("/manual_move", HTTP_POST, HandleRespManualMove);
    server.on("/debug", HTTP_GET, HandleDebug);
    server.on("/debug", HTTP_POST, HandleRespDebug);
    // When a client requests an unknown URI, call function "HandleNotFound"
    server.onNotFound(HandleNotFound);
    server.begin();
    #ifdef SMO_DEBUG
      Serial.println(F("D:HTTP server started"));
    #endif
  
  #ifdef SMO_DEBUG
    Serial.setDebugOutput(true);
  #endif
}
#endif

// =============================================================================
void SendProtocolResponse(String protocolResponse) {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:SendProtocolResponse"));
  #endif

  Serial.println(protocolResponse);
  #ifdef SMO_USE_WIFI
    if (wifi_request) {
      HandleCorrectMessage(protocolResponse);
    }
  #endif
}

// =============================================================================
void FinMessage() {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:FinMessage"));
  #endif
  incomingMessage = "";
  #ifdef SMO_USE_WIFI
    wifi_request = false;
  #endif
}

// =============================================================================
//
// Converts degrees to steps
// 
// 28BYJ-48 motor has 5.625 degrees per step
// 360 degrees / 5.625 = 64 steps per revolution
// 
// Example with degsToSteps(45):
// (64 / 5.625) * 45 = 512 steps
//
float degsToSteps(float deg) {
  return (wheelConfig.stepperConfig.stepsPerRevolution / wheelConfig.degreePerRevolution) * deg;
}

// =============================================================================
//
// Converts steps to degrees
// 
// 28BYJ-48 motor has 5.625 degrees per step
// 360 degrees / 5.625 = 64 steps per revolution
// 
// Example with stepsToDegs(512):
// (5.625 / 64) * 512 = 45 deg
//
#ifdef SMO_USE_ROTATOR
float stepsToDegs(float step) {
  return (wheelConfig.degreePerRevolution / wheelConfig.stepperConfig.stepsPerRevolution) * step;
}
#endif

// =============================================================================
void ComputeParams() {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:ComputeParams"));
  #endif
  wheelConfig.ratioAxe = (float)wheelConfig.nbDentsAxeRoue / wheelConfig.nbDentsAxeMoteur; 
  wheelConfig.degreePerRevolution = 360.0 / (wheelConfig.stepperConfig.stepsPerRevolution * wheelConfig.ratioAxe) ;
  #ifdef SMO_PROTOCOL_OPTEC
    for (int i = 0; i < filterWheelConfig.nbFilterSlots[filterWheelConfig.currentFilterWheel]; i++) {
      filterWheelConfig.slotsPositions[filterWheelConfig.currentFilterWheel][i] = ceil(i * (360.0 / filterWheelConfig.nbFilterSlots[filterWheelConfig.currentFilterWheel]));
    }
  #else
    for (int i = 0; i < filterWheelConfig.nbFilterSlots; i++) {
      filterWheelConfig.slotsPositions[i] = ceil(i * (360.0 / filterWheelConfig.nbFilterSlots));
    }
  #endif
}

// =============================================================================
void InitWheelConfig() {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:InitWheelConfig"));
  #endif
  wheelConfig.stepperConfig.stepperMaxSpeed = defStepperMaxSpeed;
  wheelConfig.stepperConfig.stepperSpeed = defStepperSpeed;
  wheelConfig.stepperConfig.stepperAccel = defStepperAccel;
  wheelConfig.stepperConfig.stepsPerRevolution = defStepsPerRevolution;
  wheelConfig.maxPosition = defStepperMaxPos;
  wheelConfig.currentPosition = 0;
  wheelConfig.maxAngle = defStepperMaxAngle;
  wheelConfig.currentAngle = 0;
  wheelConfig.nbDentsAxeMoteur = defNbDentsAxeMoteur;
  wheelConfig.nbDentsAxeRoue = defNbDentsAxeRoue;
}

// =============================================================================
void InitSlotsNamesConfig() {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:InitSlotsNamesConfig"));
  #endif
  #if defined(SMO_PROTOCOL_OPTEC)
    filterWheelConfig.nbFilterWhells = maxFilterWheels;
    for (int j = 0; j < maxFilterWheels; j++) {
      filterWheelConfig.nbFilterSlots[j] = maxFilterSlots;
      for (int i = 0 ; i < maxFilterSlots; i++) {
        for ( int k = 0 ; k < maxFilterNameSize; k++) {
          filterWheelConfig.slotsNames[j][i][k] = ' ';
        }
      }
    }
  #else
    filterWheelConfig.nbFilterSlots = maxFilterSlots;
    for ( int i = 0 ; i < maxFilterSlots; i++) {
      for ( int k = 0 ; k < maxFilterNameSize; k++) {
        filterWheelConfig.slotsNames[i][k] = ' ';
      }
    }
  #endif
}

// =============================================================================
void InitSlotsPositionsConfig() {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:InitSlotsPositionsConfig"));
  #endif
  #ifdef SMO_PROTOCOL_OPTEC
    for (int j = 0; j < maxFilterWheels; j++) {
      for (int i = 0; i < maxFilterSlots; i++) {
        filterWheelConfig.slotsPositions[j][i] = ceil(i * (360.0 / maxFilterSlots));
      }
    }
  #else
    for (int i = 0; i < maxFilterSlots; i++) {
      filterWheelConfig.slotsPositions[i] = ceil(i * (360.0 / maxFilterSlots));
    }
  #endif
}

// =============================================================================
void InitSlotsOffsetsConfig() {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:InitSlotsOffsetsConfig"));
  #endif
  #ifdef SMO_PROTOCOL_OPTEC
    for (int j = 0; j < maxFilterWheels; j++) {
      for (int i = 0; i < maxFilterSlots; i++) {
        filterWheelConfig.slotsOffsets[j][i] = 0;
      }
    }
  #else
    for (int i = 0; i < maxFilterSlots; i++) {
      filterWheelConfig.slotsOffsets[i] = 0;
    }
  #endif
}

// =============================================================================
void CopyFromSlotsNamesConfig() {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:CopyFromSlotsNamesConfig"));
  #endif
  #ifdef SMO_DEBUG
    DebugVar();
  #endif  
  #if defined(SMO_PROTOCOL_OPTEC)
    for (int j = 0; j < maxFilterWheels; j++) {
      for (int i = 0 ; i < maxFilterSlots; i++) {
        slotsNames[j][i] = "        ";
        for ( int k = 0 ; k < maxFilterNameSize; k++) {
          slotsNames[j][i].setCharAt(k, filterWheelConfig.slotsNames[j][i][k]);
        }
      slotsNames[j][i].trim();
      }
    }
  #else
    for ( int i = 0 ; i < maxFilterSlots; i++) {
      slotsNames[i] = "        ";
      for ( int k = 0 ; k < maxFilterNameSize; k++) {
        slotsNames[i].setCharAt(k, filterWheelConfig.slotsNames[i][k]);
      }
      slotsNames[i].trim();
    }
  #endif
  #ifdef SMO_DEBUG
    DebugVar();
  #endif  
}

// =============================================================================
void CopyToSlotsNamesConfig() {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:CopyToSlotsNamesConfig"));
  #endif
  #ifdef SMO_DEBUG
    DebugVar();
  #endif  
  #if defined(SMO_PROTOCOL_OPTEC)
    for (int j = 0; j < maxFilterWheels; j++) {
      for (int i = 0 ; i < maxFilterSlots; i++) {
        slotsNames[j][i].toCharArray(filterWheelConfig.slotsNames[j][i], slotsNames[j][i].length() + 1);
        for (int k = slotsNames[j][i].length(); k < maxFilterNameSize; k++) {
          filterWheelConfig.slotsNames[j][i][k] = ' ';
        }
      }
    }
  #else
    for ( int i = 0 ; i < maxFilterSlots; i++) {
      slotsNames[i].toCharArray(filterWheelConfig.slotsNames[i], slotsNames[i].length() + 1);
      for (int k = slotsNames[i].length(); k < maxFilterNameSize; k++) {
        filterWheelConfig.slotsNames[i][k] = ' ';
      }
    }  
  #endif
  #ifdef SMO_DEBUG
    DebugVar();
  #endif  
}

// =============================================================================
void InitStepper() {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:InitStepper"));
  #endif
  stepper.setMaxSpeed(wheelConfig.stepperConfig.stepperMaxSpeed);  // set the max motor speed
  stepper.setAcceleration(wheelConfig.stepperConfig.stepperAccel); // set the acceleration
  stepper.setSpeed(wheelConfig.stepperConfig.stepperSpeed);        // set the current speed
  stepper.setCurrentPosition(0);                       // set the current step
}

// =============================================================================
void UpdateStepper() {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:UpdateStepper"));
  #endif
  stepper.setMaxSpeed(wheelConfig.stepperConfig.stepperMaxSpeed);  // set the max motor speed
  stepper.setAcceleration(wheelConfig.stepperConfig.stepperAccel); // set the acceleration
  stepper.setSpeed(wheelConfig.stepperConfig.stepperSpeed);        // set the current speed
}

// =============================================================================
// Sauvegarde en mémoire EEPROM le contenu actuel de la structure wheelConfig
void SauvegardeWheelEEPROM() {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:SauvegardeWheelEEPROM"));
  #endif
  // Met à jour le nombre magic avant l'écriture
  wheelConfig.smo = SMO1;
  EEPROM.begin(eepromSize);
  EEPROM.put(eepromStepperAddress, wheelConfig);
  EEPROM.commit();
  EEPROM.end();
}

// =============================================================================
// Sauvegarde en mémoire EEPROM le contenu actuel de la structure filterWheelConfig
void SauvegardeFilterWheelEEPROM() {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:SauvegardeFilterWheelEEPROM"));
  #endif
  // Met à jour le nombre magic avant l'écriture
  filterWheelConfig.smo = SMO2;
  EEPROM.begin(eepromSize);
  EEPROM.put(eepromWheelAddress, filterWheelConfig);
  EEPROM.commit();
  EEPROM.end();
}

// =============================================================================
// Init les structures Config et ecrit le contenu de la mémoire EEPROM
void InitWheelEEPROM() {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:InitWheelEEPROM"));
  #endif
  // Valeurs par défaut pour les variables de config
  InitWheelConfig();
  // Sauvegarde les nouvelles données
  SauvegardeWheelEEPROM();
}

// =============================================================================
// Init les structures Config et ecrit le contenu de la mémoire EEPROM
void InitFilterWheelEEPROM() {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:InitConfigEEPROM"));
  #endif
  // Valeurs par défaut pour les variables de config
  InitSlotsPositionsConfig();
  InitSlotsOffsetsConfig();
  InitSlotsNamesConfig();
  // Sauvegarde les nouvelles données
  SauvegardeFilterWheelEEPROM();
}

// =============================================================================
// Charge le contenu de la mémoire EEPROM dans la structure wheelConfig
void ChargeWheelEEPROM() {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:chargeWheelEEPROM"));
  #endif
  // charge eepromSize octet memoire eeprom en RAM
  EEPROM.begin(eepromSize);
  #ifdef SMO_DEBUG
    Serial.println(String(F("D:Taille eeprom ")) + String(EEPROM.length()));
  #endif
  // Lit la mémoire EEPROM
  EEPROM.get(eepromStepperAddress,wheelConfig);
  EEPROM.end();
  #ifdef SMO_DEBUG
    Serial.println(String(F("D:Valeur lu de magic : ")) + String(wheelConfig.smo));
  #endif
  // Détection d'une mémoire non initialisée
  bool erreur = wheelConfig.smo != SMO1;
  if (erreur) {
    #ifdef SMO_DEBUG
      Serial.println(F("D:Erreur fichier config non initialisé"));
    #endif
    InitWheelEEPROM();
  }
  #ifdef SMO_DEBUG
    else Serial.println(F("D:Config ok"));
  #endif  
}

// =============================================================================
// Charge le contenu de la mémoire EEPROM dans la structure filterWheelConfig
void ChargeFilterWheelEEPROM() {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:chargeFilterWheelEEPROM"));
  #endif
  // charge eepromSize octet memoire eeprom en RAM
  EEPROM.begin(eepromSize);
  #ifdef SMO_DEBUG
    Serial.println(String(F("D:Taille eeprom ")) + String(EEPROM.length()));
  #endif
  // Lit la mémoire EEPROM
  EEPROM.get(eepromWheelAddress, filterWheelConfig);    
  EEPROM.end();
  #ifdef SMO_DEBUG
    Serial.println(String(F("D:Valeur lu de magic : ")) + String(filterWheelConfig.smo));
  #endif
  // Détection d'une mémoire non initialisée
  bool erreur = filterWheelConfig.smo != SMO2;
  if (erreur) {
    #ifdef SMO_DEBUG
      Serial.println(F("D:Erreur fichier config non initialisé"));
    #endif
    InitFilterWheelEEPROM();
  }
  CopyFromSlotsNamesConfig();
  #ifdef SMO_DEBUG
    Serial.println(F("D:Config ok"));
  #endif  
}

//
// =============================================================================
// FUNCTIONS (specific SMO_PROTOCOL_PERSO)
// =============================================================================
//

// =============================================================================
//
//  SMO_PROTOCOL pour Roue a Filtre Perso
//
//  WE     --> IE      Wheel Filter Entering Serial Mode
//  WX     --> IX      Wheel Filter eXiting Serial Mode
//  WI     --> II      Wheel Init, set slot 1 and step 0
//  WR     --> IR      Wheel Reset, move to 1 and set step 0
//  WD     --> ID      Wheel Dump, Debug Dump
//  WFSG   --> IFSx    Wheel Filter Slots Get, get slots amount  1-9 (int)
//  WFSSx  --> IFSx    Wheel Filter Slots Set, set slots amount  1-9 (int)
//  WFPG   --> IFPx    Wheel Filter Pos Get, get slot position   1-9 (int)
//  WFPSx  --> IFPx    Wheel Filter Pos Set, slot number to go   1-9 (int)
//  WFPS+  --> IFPx    Wheel Filter Pos Set Next slot            1-9 (int)
//  WFPS-  --> IFPx    Wheel Filter Pos Set Previous slot        1-9 (int)
//  WFNGx  --> IFNxy   Wheel Filter Name Get, x=1-9, y=name    (max 8 char)
//  WFNSxy --> IFNxy   Wheel Filter Name Set, x=1-9, y=newname (max 8 char)
//  WFOGx  --> IFOxy   Wheel Filter Offset Get, x=1-9, y=value    1-1000 (int)
//  WFOSxy --> IFOxy   Wheel Filter Offset Set, x=1-9, y=newvalue 1-1000 (int)
//  WMSG   --> IMSxxxx Wheel Motor Step Get, get motor step position
//  WMSS+  --> IMSxxxx Wheel Motor Step Set +10 step (calibration move)
//  WMSS-  --> IMSxxxx Wheel Motor Step Set -10 step (calibration move)
//  WMSS>  --> IMSxxxx Wheel Motor Step Set +100 step (calibration move)
//  WMSS<  --> IMSxxxx Wheel Motor Step Set -100 step (calibration move)
//  WPGx  --> IPxy     Wheel Param Get, param=x value=y
//  WPSxy --> IPxy     Wheel Param Set, param=x value=y,  Modif params
//      param=MS   MaxSpeed           100-2000    (int)   default=1000
//      param=CS   CurrentSpeed       100-2000    (int)   default=500
//      param=CA   CurrentAccel       100-1000    (int)   default=200
//      param=SR   StepPerRevolutiion 1.00-100.00 (float) default=64
//      param=GM   GearMotor tooth    10-100      (int)   default=20
//      param=GR   GearWheel tooth    50-500      (int)   default=100
//

// =============================================================================
#ifdef SMO_PROTOCOL_PERSO
void Perso_ConnexionWheel(String msg) {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Perso_ConnexionWheel"));
  #endif
  if (msg.startsWith("WE")) {
    connected = true;
    SendProtocolResponse("IE");
  } 
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_PERSO
void Perso_DeconnexionWheel(String msg) {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Perso_DeconnexionWheel"));
  #endif
  if (msg.startsWith("WX")) {
    connected = false;
    SendProtocolResponse("IX");
  } 
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_PERSO
void Perso_GetNbFilters (String msg) {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Perso_GetNbFilters"));
  #endif
  if (msg.startsWith("WFSG")) {
    SendProtocolResponse("IFS" + String(filterWheelConfig.nbFilterSlots));
  } 
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_PERSO
void Perso_SetNbFilters (String msg) {
  String s_number;
  int number;

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Perso_SetNbFilters"));
  #endif
  if (msg.startsWith("WFSS")) {
    s_number = msg.substring(4, 5);
    number = s_number.toInt();
    if (number >= 1 && number <= maxFilterSlots) {
      filterWheelConfig.nbFilterSlots = number;
      ComputeParams();
      SauvegardeFilterWheelEEPROM();
      SendProtocolResponse("IFS" + String(filterWheelConfig.nbFilterSlots));
    }
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_PERSO
void Perso_GetFilterPosition(String msg) {
  String s_number;
  int number;

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Perso_GetFilterPosition"));
  #endif
  if (msg.startsWith("WFPG")) {
    SendProtocolResponse("IFP" + String(filterWheelConfig.currentSlot + 1));
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_PERSO
void Perso_GoToFilterPosition(String msg) {
  int number = 0;
  
  #ifdef SMO_DEBUG
    Serial.println(F("DF:Perso_GoToFilterPosition"));
  #endif
  if (msg.startsWith("WFPS")) {
    if (msg.charAt(4) == '+') {
      number = (filterWheelConfig.currentSlot + 1 + filterWheelConfig.nbFilterSlots) % (filterWheelConfig.nbFilterSlots);
    }
    else if (msg.charAt(4) == '-') {
      number = (filterWheelConfig.currentSlot - 1 + filterWheelConfig.nbFilterSlots) % (filterWheelConfig.nbFilterSlots);
    }
    else {
      msg.remove(0, 4);
      // on recoit une valeur entre 1 et 9, on veut un indice tableau entre 0 et 8 
      number = msg.toInt() - 1;
    }
    if (number >= 0 && number <= (filterWheelConfig.nbFilterSlots - 1) && number != filterWheelConfig.currentSlot && !stepper.isRunning() && stepper.distanceToGo() == 0) {
      filterWheelConfig.targetSlot = number;
      #ifdef SMO_BACKGROUND_MOVE
        stepper.moveTo(degsToSteps(filterWheelConfig.slotsPositions[filterWheelConfig.targetSlot]));
        int startTime=millis();
        int currentTime;
        while (stepper.distanceToGo() != 0) {
          stepper.run();
          currentTime=millis();
          if ((currentTime-startTime)>1000) {
            startTime=currentTime;
            SendProtocolResponse(".");
          }
        }
      #else
        stepper.runToNewPosition(degsToSteps(filterWheelConfig.slotsPositions[filterWheelConfig.targetSlot]));
      #endif
      filterWheelConfig.currentSlot = filterWheelConfig.targetSlot;
      SendProtocolResponse("IFP" + String(filterWheelConfig.currentSlot + 1));
    }
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_PERSO
void Perso_GetFilterName(String msg) {
  int number;

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Perso_GetFilterName"));
  #endif
  if (msg.startsWith("WFNG")) {
    msg.remove(0, 4);
    number = msg.toInt() - 1;
    if (number >= 0 && number <= (filterWheelConfig.nbFilterSlots - 1)) {
      SendProtocolResponse("IFN" + String(number + 1) + String(slotsNames[number]));
    }
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_PERSO
void Perso_SetFilterName(String msg) {
  String s_number;
  int number;
  
  #ifdef SMO_DEBUG
    Serial.println(F("DF:Perso_SetFilterName"));
  #endif
  if (msg.startsWith("WFNS")) {
    s_number = msg.substring(4, 5);
    number = s_number.toInt() - 1;
    if (number >= 0 && number <= (filterWheelConfig.nbFilterSlots - 1)) {
      msg.remove(0, 5);
      slotsNames[number] = msg;
      CopyToSlotsNamesConfig();
      SauvegardeFilterWheelEEPROM();
      SendProtocolResponse("IFN" + String(number + 1) + String(slotsNames[number]));
    }
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_PERSO
void Perso_GetFilterOffset(String msg) {
  String s_number;
  int number;

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Perso_GetFilterOffset"));
  #endif
  if (msg.startsWith("WFOG")) {
    s_number = msg.substring(4, 5);
    number = s_number.toInt() - 1;
    if (number >= 0 && number <= (filterWheelConfig.nbFilterSlots - 1)) {
      SendProtocolResponse("IFO" + String(number + 1) + String(filterWheelConfig.slotsOffsets[number]));
    }
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_PERSO
void Perso_SetFilterOffset(String msg) {
  String s_number;
  int number;

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Perso_SetFilterOffset"));
  #endif
  if (msg.startsWith("WFOS")) {
    s_number = msg.substring(4, 5);
    number = s_number.toInt() - 1;
    if (number >= 0 && number <= (filterWheelConfig.nbFilterSlots - 1)) {
      msg.remove(0, 5);
      filterWheelConfig.slotsOffsets[number] = msg.toInt();
      SauvegardeFilterWheelEEPROM();
      SendProtocolResponse("IFO" + String(number + 1) + String(filterWheelConfig.slotsOffsets[number]));
    }
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_PERSO
void Perso_GetMotorStep(String msg) {
  int number;

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Perso_GetMotorStep"));
  #endif
  if (msg.startsWith("WMSG")) {
    SendProtocolResponse("IMS" + String(stepper.currentPosition()));
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_PERSO
void Perso_SetMotorStep(String msg) {
  String s_number;
  int number;

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Perso_SetMotorStep"));
  #endif
  if (msg.startsWith("WMSS")) {
    if (msg.charAt(4) == '+') {
      if (!stepper.isRunning() && stepper.distanceToGo() == 0) {
        #ifdef SMO_BACKGROUND_MOVE
          stepper.move(10);
        #else
          stepper.runToNewPosition(stepper.currentPosition() + 10);
        #endif
      }
    }
    else if (msg.charAt(4) == '-') {
      if (!stepper.isRunning() && stepper.distanceToGo() == 0) {
        #ifdef SMO_BACKGROUND_MOVE
          stepper.move(-10);
        #else
          stepper.runToNewPosition(stepper.currentPosition() - 10);
        #endif
      }
    }
    else if (msg.charAt(4) == '>') {
      if (!stepper.isRunning() && stepper.distanceToGo() == 0) {
        #ifdef SMO_BACKGROUND_MOVE
          stepper.move(100);
        #else
          stepper.runToNewPosition(stepper.currentPosition() + 100);
        #endif
      }
    }
    else if (msg.charAt(4) == '<') {
      if (!stepper.isRunning() && stepper.distanceToGo() == 0) {
        #ifdef SMO_BACKGROUND_MOVE
          stepper.move(-100);
        #else
          stepper.runToNewPosition(stepper.currentPosition() - 100);
        #endif
      }
    }
    SendProtocolResponse("IMS" + String(stepper.currentPosition()));
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_PERSO
void Perso_InitWheel(String msg) {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Perso_InitWheel"));
  #endif
  if (msg.startsWith("WI")) {
    InitStepper();
    filterWheelConfig.currentSlot = 0;
    SendProtocolResponse("II");
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_PERSO
void Perso_ResetWheel(String msg) {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Perso_ResetWheel"));
  #endif
  if (msg.startsWith("WR")) {
    if (!stepper.isRunning() && stepper.distanceToGo() == 0) {
      #ifdef SMO_BACKGROUND_MOVE
        stepper.moveTo(0);
        int startTime=millis();
        int currentTime;
        while (stepper.distanceToGo() != 0) {
          stepper.run();
          currentTime=millis();
          if ((currentTime-startTime)>1000) {
            startTime=currentTime;
            SendProtocolResponse(".");
          }
        }
      #else
        stepper.runToNewPosition(0);
      #endif
    }
    InitStepper();
    filterWheelConfig.currentSlot = 0;
    SendProtocolResponse("IR");
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_PERSO
void Perso_DumpWheel(String msg) {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Perso_DumpWheel"));
  #endif
  if (msg.startsWith("WD")) {
    #ifdef SMO_DEBUG
      DebugWifi();
      DebugVar();
    #endif
    SendProtocolResponse("ID");
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_PERSO
void Perso_GetParamWheel(String msg) {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Perso_GetParamWheel"));
  #endif
  if (msg.startsWith("WPG") && msg.length() >= 5) {
    if (msg.substring(3, 5) == "MS") {
      SendProtocolResponse("IPMS" + String(wheelConfig.stepperConfig.stepperMaxSpeed));
    }
    else if (msg.substring(3, 5) == "CS") {
      SendProtocolResponse("IPCS" + String(wheelConfig.stepperConfig.stepperSpeed));      
    }
    else if (msg.substring(3, 5) == "CA") {
      SendProtocolResponse("IPCA" + String(wheelConfig.stepperConfig.stepperAccel));      
    }
    else if (msg.substring(3, 5) == "SR") {
      SendProtocolResponse("IPSR" + String(wheelConfig.stepperConfig.stepsPerRevolution));      
    }
    else if (msg.substring(3, 5) == "GM") {
      SendProtocolResponse("IPGM" + String(wheelConfig.nbDentsAxeMoteur));      
    }
    else if (msg.substring(3, 5) == "GR") {
      SendProtocolResponse("IPGR" + String(wheelConfig.nbDentsAxeRoue));      
    }
    else if (msg.substring(3, 5) == "SN") {
      SendProtocolResponse("IPSN" + String(filterWheelConfig.nbFilterSlots));      
    }
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_PERSO
void Perso_SetParamWheel(String msg) {
  String s_number;
  float f_number;
  int i_number;
  
  #ifdef SMO_DEBUG
    Serial.println(F("DF:Perso_SetParamWheel"));
  #endif
  if (msg.startsWith("WPS") && msg.length() >= 6) {
    s_number = msg.substring(5,msg.length());
    f_number = s_number.toFloat();
    if (msg.substring(3, 5) == "MS" && f_number >= minStepperMaxSpeed && f_number <= maxStepperMaxSpeed) {
      wheelConfig.stepperConfig.stepperMaxSpeed = (int)f_number;
      SendProtocolResponse("IPMS" + String(wheelConfig.stepperConfig.stepperMaxSpeed));
    }
    else if (msg.substring(3, 5) == "CS" && f_number >= minStepperSpeed && f_number <= maxStepperSpeed) {
      wheelConfig.stepperConfig.stepperSpeed = (int)f_number;
      SendProtocolResponse("IPCS" + String(wheelConfig.stepperConfig.stepperSpeed));      
    }
    else if (msg.substring(3, 5) == "CA" && f_number >= minStepperAccel && f_number <= maxStepperAccel) {
      wheelConfig.stepperConfig.stepperAccel = (int)f_number;
      SendProtocolResponse("IPCA" + String(wheelConfig.stepperConfig.stepperAccel));      
    }
    else if (msg.substring(3, 5) == "SR" && f_number >= minStepsPerRevolution && f_number <= maxStepsPerRevolution) {
      wheelConfig.stepperConfig.stepsPerRevolution = f_number;
      SendProtocolResponse("IPSR" + String(wheelConfig.stepperConfig.stepsPerRevolution));      
    }
    else if (msg.substring(3, 5) == "GM"&& f_number >= minNbDentsAxeMoteur && f_number <= maxNbDentsAxeMoteur ) {
      wheelConfig.nbDentsAxeMoteur = (int)f_number;
      SendProtocolResponse("IPGM" + String(wheelConfig.nbDentsAxeMoteur));      
    }
    else if (msg.substring(3, 5) == "GR" && f_number >= minNbDentsAxeRoue && f_number <= maxNbDentsAxeRoue) {
      wheelConfig.nbDentsAxeRoue = (int)f_number;
      SendProtocolResponse("IPGR" + String(wheelConfig.nbDentsAxeRoue));      
    }
    ComputeParams();
    UpdateStepper();
    SauvegardeWheelEEPROM();
    #ifdef SMO_DEBUG
      DebugVar();
    #endif
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_PERSO
void setup() {

  Serial.begin(SERIAL_BAUD);               // initialise the serial monitor
  Serial.flush();
  delay(1000);
  ChargeWheelEEPROM();
  ChargeFilterWheelEEPROM();
  filterWheelConfig.currentSlot = 0;
  ComputeParams();
  Perso_InitWheel("WI");
  #ifdef SMO_USE_WIFI
    InitWifi();
  #endif
  #if defined(SMO_USE_WIFI) and defined(SMO_USE_WIFI_OTA)
    InitOTA();
  #endif
  while (!Serial) {
    // wait for serial port to connect. Needed for native USB
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_PERSO
void loop() {

  #if defined(SMO_USE_WIFI) and defined(SMO_USE_WIFI_OTA)
    ArduinoOTA.handle();
  #endif
  
  #ifdef SMO_USE_WIFI
    server.handleClient();
    if (wifi_request) {
      #ifdef SMO_DEBUG
        Serial.print(F("D:incomingMessage(wifi): "));
        Serial.println(incomingMessage);
      #endif
    }
  #endif
  
  if (Serial.available() > 0) {
    Serial.setTimeout(SERIAL_TIMEOUT);
    incomingMessage = Serial.readStringUntil('\n');
    #ifdef SMO_DEBUG
      Serial.print(F("D:incomingMessage: "));
      Serial.println(incomingMessage);
    #endif
  } 
  else {
    if (incomingMessage.startsWith("WE")) {
      Perso_ConnexionWheel(incomingMessage);
      FinMessage();
    }
    else if (connected && incomingMessage.startsWith("WX")) {
      Perso_DeconnexionWheel(incomingMessage);
      FinMessage();
    }
    else if (connected && incomingMessage.startsWith("WI")) {
      Perso_InitWheel(incomingMessage);
      FinMessage();
    } 
    else if (connected && incomingMessage.startsWith("WR")) {
      Perso_ResetWheel(incomingMessage);
      FinMessage();
    } 
    else if (connected && incomingMessage.startsWith("WD")) {
      Perso_DumpWheel(incomingMessage);
      FinMessage();
    } 
    else if (connected && incomingMessage.startsWith("WFSG")) {
      Perso_GetNbFilters(incomingMessage);
      FinMessage();
    }
    else if (connected && incomingMessage.startsWith("WFSS")) {
      Perso_SetNbFilters(incomingMessage);
      FinMessage();
    } 
    else if (connected && incomingMessage.startsWith("WFPG")) {
      Perso_GetFilterPosition(incomingMessage);
      FinMessage();
    } 
    else if (connected && incomingMessage.startsWith("WFPS")) {
      Perso_GoToFilterPosition(incomingMessage);
      FinMessage();
    } 
    else if (connected && incomingMessage.startsWith("WFNG")) {
      Perso_GetFilterName(incomingMessage);
      FinMessage();
    } 
    else if (connected && incomingMessage.startsWith("WFNS")) {
      Perso_SetFilterName(incomingMessage);
      FinMessage();
    } 
    else if (connected && incomingMessage.startsWith("WFOG")) {
      Perso_GetFilterOffset(incomingMessage);
      FinMessage();
    } 
    else if (connected && incomingMessage.startsWith("WFOS")) {
      Perso_SetFilterOffset(incomingMessage);
      FinMessage();
    } 
    else if (connected && incomingMessage.startsWith("WMSG")) {
      Perso_GetMotorStep(incomingMessage);
      FinMessage();
    } 
    else if (connected && incomingMessage.startsWith("WMSS")) {
      Perso_SetMotorStep(incomingMessage);
      FinMessage();
    } 
    else if (connected && incomingMessage.startsWith("WPG")) {
      Perso_GetParamWheel(incomingMessage);
      FinMessage();
    } 
    else if (connected && incomingMessage.startsWith("WPS")) {
      Perso_SetParamWheel(incomingMessage);
      FinMessage();
    }
    #ifdef SMO_USE_WIFI
      else if (wifi_request) {
        HandleUnknownMessage();
        FinMessage();     
    }
    #endif
    else if (incomingMessage.length() != 0) {
      FinMessage();
    }
  }
  stepper.run();
}
#endif

//
// =============================================================================
// FUNCTIONS (specific SMO_PROTOCOL_QUANTUM)
// =============================================================================
//

// =============================================================================
//
// Quantum SMO_PROTOCOL
//
// Get number of filter positions   EN           N<number>
// Disconnect                       DS
// Go to filter position            G<number>
// position is returned when the move finishes  P<number>
// get filter name                  F<number>   F<number><name>
// set filter name                  f<number><name>
// Get filter offset                O<number>   O<number><offset>
// set filter offset                o<number><offset>
// Get description                  SN          SN<details>
// get version                      VR          VR<details>
//

// =============================================================================
#ifdef SMO_PROTOCOL_QUANTUM
void Quantum_GetNbFilters (String msg) {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Quantum_GetNbFilters"));
  #endif
  if (msg.startsWith("EN")) {
    SendProtocolResponse("N" + String(filterWheelConfig.nbFilterSlots));
  } 
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_QUANTUM
void Quantum_Disconnect (String msg) {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Quantum_Disconnect"));
  #endif
  if (msg.startsWith("DS")) {
    connected = false;
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_QUANTUM
void Quantum_GoToFilterPosition(String msg) {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Quantum_GoToFilterPosition"));
  #endif
  if (msg.startsWith("G")) {
    msg.remove(0, 1);
    filterWheelConfig.targetSlot = msg.toInt();
    if (!stepper.isRunning() && stepper.distanceToGo() == 0) {
      //SendProtocolResponse("P" + String(filterWheelConfig.targetSlot));
      #ifdef SMO_BACKGROUND_MOVE
        stepper.moveTo(degsToSteps(filterWheelConfig.slotsPositions[filterWheelConfig.targetSlot]));
        int startTime=millis();
        int currentTime;
        while (stepper.distanceToGo() != 0) {
          stepper.run();
          currentTime=millis();
          if ((currentTime-startTime)>1000) {
            startTime=currentTime;
            SendProtocolResponse("M" + String(filterWheelConfig.targetSlot));
          }
        }
      #else
        stepper.runToNewPosition(degsToSteps(filterWheelConfig.slotsPositions[filterWheelConfig.targetSlot]));
      #endif
      SendProtocolResponse("P" + String(filterWheelConfig.targetSlot));
      filterWheelConfig.currentSlot = filterWheelConfig.targetSlot;
    }
  } 
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_QUANTUM
void Quantum_GetFilterName(String msg) {
  int number;

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Quantum_GetFilterName"));
  #endif
  if (msg.startsWith("F")) {
    msg.remove(0, 1);
    number = msg.toInt();
    SendProtocolResponse("F" + String(number) + String(slotsNames[number]));
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_QUANTUM
void Quantum_SetFilterName(String msg) {
  String s_number;
  int number;

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Quantum_SetFilterName"));
  #endif
  if (msg.startsWith("f")) {
    s_number = msg.substring(1, 2);
    number = s_number.toInt();
    msg.remove(0, 2);
    slotsNames[number] = msg;
    CopyToSlotsNamesConfig();
    SauvegardeFilterWheelEEPROM();
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_QUANTUM
void Quantum_GetFilterOffset(String msg) {
  int number;

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Quantum_GetFilterOffset"));
  #endif
  if (msg.startsWith("O")) {
    msg.remove(0, 1);
    number = msg.toInt();
    SendProtocolResponse("O" + String(number) + String(filterWheelConfig.slotsOffsets[number]));
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_QUANTUM
void Quantum_SetFilterOffset(String msg) {
  String s_number;
  int number;

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Quantum_SetFilterOffset"));
  #endif
  if (msg.startsWith("o")) {
    s_number = msg.substring(1, 2);
    number = s_number.toInt();
    msg.remove(0, 2);
    filterWheelConfig.slotsOffsets[number] = msg.toInt();
    SauvegardeFilterWheelEEPROM();
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_QUANTUM
void Quantum_GetDescription(String msg) {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Quantum_GetDescription"));
  #endif
  if (msg.startsWith("SN")) {
    SendProtocolResponse(msg + String(SMO_PRODUCT));
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_QUANTUM
void Quantum_GetVersion(String msg) {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Quantum_GetVersion"));
  #endif
  if (msg.startsWith("VR")) {
    SendProtocolResponse(msg + String(SMO_VERSION));
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_QUANTUM
void setup() {

  Serial.begin(SERIAL_BAUD);               // initialise the serial monitor
  Serial.flush();
  delay(1000);
  ChargeWheelEEPROM();
  ChargeFilterWheelEEPROM();
  filterWheelConfig.currentSlot = 0;
  ComputeParams();
  InitStepper();
  incomingMessage = "";
  #ifdef SMO_USE_WIFI
    InitWifi();
  #endif
  #if defined(SMO_USE_WIFI) and defined(SMO_USE_WIFI_OTA)
    InitOTA();
  #endif
  while (!Serial) {
    // wait for serial port to connect. Needed for native USB
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_QUANTUM
void loop() {

  #if defined(SMO_USE_WIFI) and defined(SMO_USE_WIFI_OTA)
    ArduinoOTA.handle();
  #endif

  #ifdef SMO_USE_WIFI
    server.handleClient();
    if (wifi_request) {
      #ifdef SMO_DEBUG
        Serial.print(F("D:incomingMessage(wifi): "));
        Serial.println(incomingMessage);
      #endif
    }
  #endif

  if (Serial.available() > 0) {
    Serial.setTimeout(SERIAL_TIMEOUT);
    incomingMessage = Serial.readStringUntil('\n');
    #ifdef SMO_DEBUG
      Serial.print(F("D:incomingMessage: "));
      Serial.println(incomingMessage);
    #endif
  }
  else {
    if (incomingMessage.startsWith("EN")) {
      Quantum_GetNbFilters(incomingMessage);
      FinMessage();
    }
    else if (incomingMessage.startsWith("DS")) {
      Quantum_Disconnect(incomingMessage);
      FinMessage();
    } 
    else if (incomingMessage.startsWith("G")) {
      Quantum_GoToFilterPosition(incomingMessage);
      FinMessage();
    } 
    else if (incomingMessage.startsWith("F")) {
      Quantum_GetFilterName(incomingMessage);
      FinMessage();
    } 
    else if (incomingMessage.startsWith("f")) {
      Quantum_SetFilterName(incomingMessage);
      FinMessage();
    } 
    else if (incomingMessage.startsWith("O")) {
      Quantum_GetFilterOffset(incomingMessage);
      FinMessage();
    } 
    else if (incomingMessage.startsWith("o")) {
      Quantum_SetFilterOffset(incomingMessage);
      FinMessage();
    } 
    else if (incomingMessage.startsWith("SN")) {
      Quantum_GetDescription(incomingMessage);
      FinMessage();
    } 
    else if (incomingMessage.startsWith("VR")) {
      Quantum_GetVersion(incomingMessage);
      FinMessage();
    }
    #ifdef SMO_USE_WIFI
      else if (wifi_request) {
        HandleUnknownMessage();
        FinMessage();     
    }
    #endif
    else if (incomingMessage.length() != 0) {
      FinMessage();
    }
  }
  stepper.run();
}
#endif

//
// =============================================================================
// FUNCTIONS (specific SMO_PROTOCOL_OPTEC)
// =============================================================================
//

// =============================================================================
//  SMO_PROTOCOLE pour Roue a Filtre Optec
//
//    WSMODE  --> !
//    WVAAAA  --> V= 2.04
//    WHOME   --> A, B, C, D, E ou F, G, H
//    WIDENT  --> A, B, C, D, E ou F, G, H
//    WFILTR  --> x (1 à 5 ou 1 à 8)
//    WGOTOx  --> *
//    WREAD   --> n1..40 ou n64  (chaine de 40 ou 64 caracteres)
//    WLOADy*n1..n40 ou n64 --> !
//    WEXITS  --> END
//

// =============================================================================
#ifdef SMO_PROTOCOL_OPTEC
void Optec_StartSerialMode(String msg) {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Optec_StartSerialMode"));
  #endif
 if (msg.startsWith("WSMODE")) {
    connected = true;
    SendProtocolResponse("!");
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_OPTEC
void Optec_GetFirmware(String msg) {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Optec_StartSerialMode"));
  #endif
  if (msg.startsWith("WVAAAA")) {
    SendProtocolResponse("V= 2.04");
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_OPTEC
void Optec_WheelHome(String msg) {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Optec_StartSerialMode"));
  #endif
  if (msg.startsWith("WHOME")) {
    filterWheelConfig.currentSlot = 0;
    //todo: identify filterwheel, and load filternames for current wheel from eeprom
    SendProtocolResponse(String(filterWheelsNames[filterWheelConfig.currentFilterWheel]));
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_OPTEC
void Optec_WheelIdent(String msg) {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Optec_StartSerialMode"));
  #endif
  if (msg.startsWith("WIDENT")) {
    SendProtocolResponse(String(filterWheelsNames[filterWheelConfig.currentFilterWheel]));
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_OPTEC
void Optec_GetFilterPos(String msg) {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Optec_GetFilterPos"));
  #endif
  if (msg.startsWith("WFILTR")) {
    SendProtocolResponse(String(filterWheelConfig.currentSlot + 1));
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_OPTEC
void Optec_GoToFilterPosition(String msg) {
  int number;

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Optec_GoToFilterPosition"));
  #endif
  if (msg.startsWith("WGOTO")) {
    msg.remove(0, 5);
    filterWheelConfig.targetSlot = msg.toInt() - 1;
    if (!stepper.isRunning() && stepper.distanceToGo() == 0) {
      #ifdef SMO_BACKGROUND_MOVE
        stepper.moveTo(degsToSteps(filterWheelConfig.slotsPositions[filterWheelConfig.currentFilterWheel][filterWheelConfig.targetSlot]));
        int startTime=millis();
        int currentTime;
        while (stepper.distanceToGo() != 0) {
          stepper.run();
          currentTime=millis();
          if ((currentTime-startTime)>1000) {
            startTime=currentTime;
            SendProtocolResponse(".");
          }
        }
      #else
        stepper.runToNewPosition(degsToSteps(filterWheelConfig.slotsPositions[filterWheelConfig.currentFilterWheel][filterWheelConfig.targetSlot]));
      #endif
      filterWheelConfig.currentSlot = filterWheelConfig.targetSlot;
    }
    SendProtocolResponse("*");
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_OPTEC
void Optec_GetFilterNames(String msg) {
  String resp = "";
  
  #ifdef SMO_DEBUG
    Serial.println(F("DF:Optec_GetFilterNames"));
  #endif
  if (msg.startsWith("WREAD")) {
    for (int i = 0; i < filterWheelConfig.nbFilterSlots[filterWheelConfig.currentFilterWheel]; i++) {
      resp.concat(slotsNames[filterWheelConfig.currentFilterWheel][i]);
      for (int j = slotsNames[filterWheelConfig.currentFilterWheel][i].length(); j < maxFilterNameSize; j++) {
        resp.concat(" ");
      }
    }
    SendProtocolResponse(resp);
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_OPTEC
void Optec_SetFilterNames(String msg) {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Optec_SetFilterNames"));
  #endif
  int indice = 0;
  if (msg.startsWith("WLOAD")) {
    indice = msg.charAt(5) - 'A';
    for (int i = 0; i < filterWheelConfig.nbFilterSlots[indice]; i++) {
      slotsNames[indice][i] = msg.substring(7 + i * maxFilterNameSize, 7 + i * maxFilterNameSize + maxFilterNameSize);
    }
    SendProtocolResponse("!");
    CopyToSlotsNamesConfig();
    SauvegardeFilterWheelEEPROM();
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_OPTEC
void Optec_ExitSerialMode(String msg) {

  #ifdef SMO_DEBUG
    Serial.println(F("DF:Optec_ExitSerialMode"));
  #endif
  if (msg.startsWith("WEXITS")) {
    connected = false;
    SendProtocolResponse("END");
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_OPTEC
void setup() {

  Serial.begin(SERIAL_BAUD, SERIAL_8N1);      // initialise the serial monitor
  Serial.flush();
  delay(1000);
  ChargeWheelEEPROM();
  ChargeFilterWheelEEPROM();
  filterWheelConfig.currentFilterWheel = 0;
  filterWheelConfig.currentSlot = 0;
  ComputeParams();
  InitStepper();
  incomingMessage = "";
  #ifdef SMO_USE_WIFI
    InitWifi();
  #endif
  #if defined(SMO_USE_WIFI) and defined(SMO_USE_WIFI_OTA)
    InitOTA();
  #endif
  while (!Serial) {
    // wait for serial port to connect. Needed for native USB
  }
}
#endif

// =============================================================================
#ifdef SMO_PROTOCOL_OPTEC
void loop() {

  #if defined(SMO_USE_WIFI) and defined(SMO_USE_WIFI_OTA)
    ArduinoOTA.handle();
  #endif

  #ifdef SMO_USE_WIFI
    server.handleClient();
    if (wifi_request) {
      #ifdef SMO_DEBUG
        Serial.print(F("D:incomingMessage(wifi): "));
        Serial.println(incomingMessage);
      #endif
    }
  #endif

  if (Serial.available() > 0) {
    Serial.setTimeout(SERIAL_TIMEOUT);
    incomingMessage = Serial.readStringUntil('\n');
    #ifdef SMO_DEBUG
      Serial.print(F("D:incomingMessage: "));
      Serial.println(incomingMessage);
    #endif
  }
  else {
    if (incomingMessage.startsWith("WSMODE")) {
      Optec_StartSerialMode(incomingMessage);
      FinMessage();
    }
    else if (connected && incomingMessage.startsWith("WVAAAA")) {
      Optec_GetFirmware(incomingMessage);
      FinMessage();
    } 
    else if (connected && incomingMessage.startsWith("WHOME")) {
      Optec_WheelHome(incomingMessage);
      FinMessage();
    } 
    else if (connected && incomingMessage.startsWith("WIDENT")) {
      Optec_WheelIdent(incomingMessage);
      FinMessage();
    } 
    else if (connected && incomingMessage.startsWith("WFILTR")) {
      Optec_GetFilterPos(incomingMessage);
      FinMessage();
    } 
    else if (connected && incomingMessage.startsWith("WGOTO")) {
      Optec_GoToFilterPosition(incomingMessage);
      FinMessage();
    } 
    else if (connected && incomingMessage.startsWith("WREAD")) {
      Optec_GetFilterNames(incomingMessage);
      FinMessage();
    } 
    else if (connected && incomingMessage.startsWith("WLOAD")) {
      Optec_SetFilterNames(incomingMessage);
      FinMessage();
    } 
    else if (connected && incomingMessage.startsWith("WEXITS")) {
      Optec_ExitSerialMode(incomingMessage);
      FinMessage();
    } 
    #ifdef SMO_USE_WIFI
      else if (wifi_request) {
        HandleUnknownMessage();
        FinMessage();     
    }
    #endif
    else if (incomingMessage.length() != 0) {
      FinMessage();
    }
  }
  stepper.run();
}
#endif

// =============================================================================
// END
// =============================================================================

voilà :grinning:
merci
cdt

Bonjour astronomy

Sur le schéma il y a
image
Pin moteur D0, D1, D2, D3

Dans le programme, il y a

// Caracteristiques Moteur
#define motorPin1  5                // IN1 pin on the ULN2003A driver to pin D1 on NodeMCU board
#define motorPin2  4                // IN2 pin on the ULN2003A driver to pin D2 on NodeMCU board
#define motorPin3  0                // IN3 pin on the ULN2003A driver to pin D3 on NodeMCU board
#define motorPin4  2                // IN4 pin on the ULN2003A driver to pin D4 on NodeMCU board

Cordialement
jpbbricole

1 Like

Mon opinion : avec un programme aussi long les sources de non fonctionnement sont trop nombreuses pour que quelqu'un comme moi s'y retrouve.

Je serai à ta place je ferais en priorité un programme extrêmement simple qui ne fait que faire tourner le moteur puisqu'il y a un problème sur le moteur, et je n'irai pas chercher une bibliothèque comme accelsteper mais quelque chose de très simple. Certes acccelstepper est puissante mais qui dit puissance dit aussi risque d'incompréhension vu les documentations assez succintes . Autant se placer à raz des paquerettes.

Tant que cet essai n'est pas transformé je n'irais pas plus loin.
Une fois réussi, tu pourra essayer de prendre en main accelstepper si tu le désires.

Puis ajoutes les fonctions une par une jusqu'à obtenir le programme que tu souhaites.
C'est ma méthode de travail, elle vaut ce qu'elle vaut, mais en général elle permet d'atteindre un résultat. Surtout que je ne pense pas que ce soit un choix personnel d'écrire les commentaires en anglais, c'est donc un programme récupéré quelque part.