This ESP32 script works only when board is plugged to PC

This is a split flap display project which I didn't write, I built and upload the code on a esp32 board using platformio. whenever I power the ESP32 from PSU it will run the motors but it won't trigger the hall sensors. When I upload the ESP32 to the PC it works correctly.

Config.h

#pragma once

#define WIFI_SSID "xxxxxx"
#define WIFI_PWD "xxxxx"

#define MY_NTP_SERVER "au.pool.ntp.org" // Set the best fitting NTP server (pool) for your location
#define MY_TZ "AEST-10AEDT,M10.1.0,M4.1.0/3" // Set your time zone from https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv

#define WORDNIKAPIKEY "" // Insert your private key from https://developer.wordnik.com/
#define MINWORDLEN 9 // Specify minimum word length to fetch from Wordnik
#define WORDUPDATESPERHOUR 0 // Set number of word updates from https://wordnik.com per hour. 0 will disable, else use an integer that results in an exact number of minutes btw updates eg. 1,2,3,4,5,6,10,12...

debug.h

#pragma once

// Set debug to 1 to allow debugging info on serial monitor
#define DEBUG 1

#if DEBUG == 1
#define debug(x) Serial.print(x)
#define debugln(x) Serial.println(x)
#define debugf(...) Serial.printf(__VA_ARGS__)
#define TXT_RST "\e[0m"
#define TXT_BLUE "\e[0;34m"
#define TXT_YELLOW "\e[0;33m"
#define TXT_RED "\e[1;31m"
#define TXT_GREEN "\e[0;32m"
#else
#define debug(x)
#define debugln(x)
#define debugf(...)
#endif

system.h

#pragma once

#include <Arduino.h>
#include <ArduinoJson.h>
#include <WebServer.h>
#include "debug.h"
#if __has_include(<config-private.h>)
    #include "config-private.h"
#else
    #include "config.h"
#endif
#include <WiFiClientSecure.h>
#include <ESPmDNS.h>

// Specify number of Units (characters) in the display (4 - 12)
#define UNITCOUNT 12

// Specify the network name of the display (useful if you have more than one display)
#define NETWORKNAME "splitflap"

#define STR_HELPER(x) #x
#define STR(x) STR_HELPER(x)
#define WORDNIKURL "https://api.wordnik.com/v4/words.json/randomWord?hasDictionaryDef=true&excludePartOfSpeech=family-name%2Cgiven-name%2Cproper-noun%2Cproper-noun-plural&minCorpusCount=100&maxCorpusCount=-1&minDictionaryCount=1&maxDictionaryCount=-1&minLength=" STR(MINWORDLEN) "&maxLength=" STR(UNITCOUNT) "&api_key=" WORDNIKAPIKEY
#define NTP_MIN_VALID_EPOCH 1577836800  //2020-1-1
#define RTC_MAGIC 0x76b78ec4

void disableCertificates();
boolean synchroniseWith_NTP_Time(time_t &now, tm &timeinfo);
boolean getNTP(time_t &now, tm &timeinfo);
String wordOfTheDay();
void setup_routing();
void sendwebpage();
void receiveAPI();
void receiveInput();
void randomWord ();
void handle_NotFound();
String padToFullWidth (const char* word);

extern void displayString(String display);

unit.h

#pragma once

#include <Arduino.h>
#include "FastAccelStepper.h"
#include "debug.h"

// Customise below for each unit for your build. (Units are numbered left to right 0 - 11)
const uint8_t calOffsetUnit[] = {87, 62, 77, 65, 89, 104, 107, 82, 95, 97, 90, 55};
const float FlapStep[] = {2038.0/45, 2038.0/45, 2038.0/45, 2050.0/45, 2049.0/45, 2049.0/45, 2049.0/45, 2038.0/45, 2051.0/45, 2051.2/45, 2049.0/45, 2038.0/45}; // stepper motor steps per rotation per flap, for each unit motor

// The following are hardware related and won't change unless PCB is changed. Note: Units are numbered from left to right 0 - 11.
const uint8_t unitStepPin[] =  {14,13,5,4,18,17,16,15,26,25,23,19};
const uint8_t UnitEnablePin[] = {3,2,1,0,7,6,5,4,11,10,9,8};
const char sensorPort[] = {'A','A','A','A','B','B','A','A','B','B','B','B'}; //Maps unit sensors 0 - 11 to the MCP23017 ports A or B
const uint8_t sensorPortBit[] = {0b00001000,0b00000100,0b00000010,0b00000001,0b00000010,0b00000001,0b00100000,0b00010000,0b00100000,0b00010000,0b00001000,0b00000100}; //Maps unit sensors 0 - 11 to the bit of the MCP23017 port
const uint8_t interruptPin = 27;
const uint8_t button1Pin = 14;
const uint8_t button2Pin = 6;
const char letters[] = {' ', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '$', '&', '#', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', '.', '-', '?', '!'};
const uint16_t rotationSpeeduS = 2000;

////////////////////////////////////////
// For JTAG debugging, cannot use pins 13,14,15, so free these up when debugging
// const uint8_t UNITCOUNT = 9;
// const uint8_t unitStepPin[] =  {5,4,18,17,16,26,25,23,19};
// const uint8_t UnitEnablePin[] = {1,0,7,6,5,11,10,9,8};
// const char sensorPort[] = {'A','A','B','B','A','B','B','B','B'}; //Maps unit sensors 0 - 11 to the MCP23017 ports A or B
// const uint8_t sensorPortBit[] = {0b00000010,0b00000001,0b00000010,0b00000001,0b00100000,0b00100000,0b00010000,0b00001000,0b00000100}; //Maps unit sensors 0 - 11 to the bit of the above MCP23017 port
// const uint8_t calOffsetUnit[] = {77, 65, 89, 104, 107, 95, 97, 90, 85};
////////////////////////////////////////

class Unit {
  public:
    boolean calibrationStarted;
    boolean calibrationComplete;
    char pendingLetter;
    uint8_t destinationLetter;

    // Constructor for each Unit object
    Unit(FastAccelStepperEngine& engine, uint8_t unitNum);

    void moveStepperbyStep(int16_t steps);
    void moveSteppertoLetter(char toLetter);
    void moveStepperbyFlap(uint16_t flaps);
    void calibrateStart();
    int8_t calibrate();
    boolean checkIfRunning();
    boolean updateHallValue(uint8_t updatedHallValue);

  private:
    FastAccelStepper* stepper;
    float missedSteps;
    uint8_t unitNum;
    bool preInitialise;
    uint8_t currentLetterPosition;
    uint32_t calibrationStartTime;
    uint8_t currentHallValue;
    uint32_t lastHallUpdateTime;

    int16_t stepsToRotate (float steps);
    uint16_t stepsToRotateFlaps(uint16_t flaps);
    uint8_t flapsToRotateToLetter(char letterchar, boolean *recalibrate);
    uint8_t translateLettertoInt(char letterchar);
  };

main.cpp

/* Firmware for Mechanical Split-Flap Display
 *
 * This is the Arduino firmware for esp32Core_board_v2 (ESP32 DevKitC)
 *
 * Malcolm Yeoman (2024)
 *
 * Blog post: https://tinkerwithtech.net/split-flap-display-brings-back-memories
 * Github:
 *
 * Note1: Implemented so that on the MCP23017 GPA7 and GPB7 are not used as inputs
 *        https://microchipsupport.force.com/s/article/GPA7---GPB7-Cannot-Be-Used-as-Inputs-In-MCP23017
 *
*/

#include <Arduino.h>
#include <Wire.h>
#include <MCP23017.h>
#include <time.h>
#include "system.h"
#include "unit.h"
#if __has_include(<config-private.h>)
    #include "config-private.h"
#else
    #include "config.h"
#endif

// Function headers
void print_test_menu ();
bool setExternalPin(uint8_t pin, uint8_t value);
// boolean calibrate_all_units();
void recalibrate_units();
void IRAM_ATTR sensor_ISR();
void updateHallSensors();
void displayString(String display);
boolean diplayStillMoving();
// void intToBinary(int num, char* binaryStr);
// void debugUnitFlags(String prefix);

// Global vars
uint32_t previousMillis = 0;
uint32_t displayLastStoppedMillis;
uint32_t nextWordAPIMillis = 0;
MCP23017 mcp_en_steppers = MCP23017(0x20);
MCP23017 mcp_sensor = MCP23017(0x21);
Unit *splitFlap[UNITCOUNT];
volatile bool sensortriggered = false;
uint16_t counter = 0;
uint8_t active_menu_unit = 0;
boolean getting_first_word = true;
const char* ssid = WIFI_SSID;
const char* password = WIFI_PWD;
time_t now; // this is the epoch
tm timeinfo;  // structure tm holds time information in a more convenient way
String test_command_previous = "";
uint8_t charSeq = 40;
char save_display[13];
char previous_display[13];
uint8_t reboot_count;
String localIP;
FastAccelStepperEngine engine;
extern WebServer server;
uint8_t word_updates_per_hour = WORDUPDATESPERHOUR; //store config value in variable to prevent div by zero compiler warnings

// RTC memory structure - for persisting data between reboots
typedef struct {
  uint32_t magic;
  char previous_display[13];
  uint8_t reboot_count;
} RTC;
RTC_NOINIT_ATTR RTC nvmem;


// SETUP
void setup() {

#if DEBUG == 1
  Serial.begin(115200);
#endif
  
  WiFi.begin(ssid, password);
  debugln(TXT_BLUE "Starting" TXT_RST);

  // Future expansion possibility: Set up external I2C to chain multiple controller boards
  // Wire2.begin(32,33); //(SDA=32, SCL=33);

  // Configure I2C for MCP23017 port expanders
  Wire.begin(21, 22, 800000); // SDA=21, SCL=22, 800kHz

  mcp_en_steppers.init();
  mcp_en_steppers.portMode(MCP23017Port::A, 0);          //Port A as output
  mcp_en_steppers.portMode(MCP23017Port::B, 0);          //Port B as output
  mcp_en_steppers.writeRegister(MCP23017Register::GPIO_A, 0x00);  //Reset port A 
  mcp_en_steppers.writeRegister(MCP23017Register::GPIO_B, 0x00);  //Reset port B

  mcp_sensor.init();
  mcp_sensor.portMode(MCP23017Port::A, 0b01111111); //Port A 7 bits as input
  mcp_sensor.portMode(MCP23017Port::B, 0b01111111); //Port B 7 bits as input
  mcp_sensor.writeRegister(MCP23017Register::IPOL_A, 0x00);
  mcp_sensor.writeRegister(MCP23017Register::IPOL_B, 0x00);
  mcp_sensor.writeRegister(MCP23017Register::GPIO_A, 0xFF);
  mcp_sensor.writeRegister(MCP23017Register::GPIO_B, 0xFF);

  // Set up fast stepper engine
  engine = FastAccelStepperEngine();
  engine.init();
  engine.setExternalCallForPin(setExternalPin);

  // Initialise split-flap display units
  for (uint8_t unit = 0; unit < UNITCOUNT; unit++) {
    splitFlap[unit] = new Unit(engine, unit);
  }

  // Enable Sensor interrupts
  mcp_sensor.interruptMode(MCP23017InterruptMode::Or); //Both ports logically ORed to same interrupt pin     
  mcp_sensor.interrupt(MCP23017Port::A, CHANGE);
  mcp_sensor.interrupt(MCP23017Port::B, CHANGE);
  mcp_sensor.clearInterrupts();
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), sensor_ISR, FALLING);

  // attempt to connect to Wifi network
  debug("Attempting to connect to SSID: ");
  debugln(ssid);
  while (WiFi.status() != WL_CONNECTED) {
    debug(".");
    // wait 1 second between retries
    delay(1000);
  }

  debug("\nConnected to " TXT_BLUE);
  debugln(ssid);
  localIP = WiFi.localIP().toString();
  debug(localIP);
  debugln(TXT_RST);

  disableCertificates(); 

  // Lookup NTP time
  configTzTime(MY_TZ, MY_NTP_SERVER);
  now = time(nullptr); //start time sync in background

  // Set up REST API
  setup_routing();

  // Read hall sensors to get current values
  updateHallSensors();

  // Read any previous display before last reboot
  if (nvmem.magic == RTC_MAGIC) {
    strncpy(previous_display, nvmem.previous_display, 13);
    previous_display[12]='\0';
    reboot_count = nvmem.reboot_count;
    debugf(TXT_YELLOW "previous_display: [%s], reboots: %d\n" TXT_RST, previous_display, reboot_count);
  }
  else {
    previous_display[0] = '\0';
    reboot_count = 0;
  }

  delay(101); // Delay to avoid initially false triggering the glitch detection

  // Try up to 3 times to get NTP
  uint8_t getntpcount = 0;
  while (getntpcount++ < 3 && !getNTP(now, timeinfo)) {
    delay(1000);
    debugln("Retrying NTP...");
  }

  displayLastStoppedMillis = 0;

#if DEBUG == 1
  print_test_menu();
#endif
}

void loop() {
  String test_command;
  uint16_t test_num;

  ////////////////////////
  // HIGH LEVEL EVENT LOOP
  ////////////////////////

  // Read hall sensors if change detected via interrupt
  if (sensortriggered) {
    sensortriggered = false;
    updateHallSensors();
  }

  // Calibrate any units that require it
  recalibrate_units();

  // Move any units pending due to drum passing origin (after they have recalibrated)
  for (uint8_t unit = 0; unit < UNITCOUNT; unit++) {
    if (splitFlap[unit]->pendingLetter > 0 && splitFlap[unit]->calibrationComplete) {
      splitFlap[unit]->moveSteppertoLetter(splitFlap[unit]->pendingLetter);
    }
  }

  // Lower priority events below
  if ((uint32_t)millis() - previousMillis >= 500) {
    previousMillis = millis();

    // Rest API server
    server.handleClient();

    //If display not moving, check if anything new to display
    if (!diplayStillMoving()) {
      displayLastStoppedMillis = millis();

      // Check if need to redisplay after reboot
      if (previous_display[0] != '\0') {
        // don't allow more than 3 reboots per period - to avoid continous running in the event of fault
        if (reboot_count++ > 3) {
          debugln(TXT_RED "HALTING due to reboot count." TXT_RST);
          while (true);
        }
        debugf("Display previous string: [%s], reboots: %d\n", previous_display, reboot_count);
        displayString(String(previous_display));
        previous_display[0] = '\0';
        getting_first_word = false;
      }

      else if (getting_first_word) {
        if (word_updates_per_hour > 0) {
          String word = wordOfTheDay();
          displayLastStoppedMillis = millis();
          debugf("Word, %02d:%02d, [%s]\n", timeinfo.tm_hour, timeinfo.tm_min, word);
          displayString(word);    
          nextWordAPIMillis = millis() + 60000; //dont check again until this minute passed
        }
        getting_first_word = false;
      }

      // Display random word according to WORDUPDATESPERHOUR
      else if (word_updates_per_hour > 0 && ((uint32_t)millis() > nextWordAPIMillis)) {
        // Only update during daytime hours
        if (getNTP(now, timeinfo)) {
          if ((timeinfo.tm_min % (60 / word_updates_per_hour) == 0) && (timeinfo.tm_hour >= 8 && timeinfo.tm_hour <= 19)) {
            reboot_count = 0;
            nextWordAPIMillis = (millis() + (3600 / word_updates_per_hour) * 1000) - 3000; //dont check again until nearly next word update time
            String word = wordOfTheDay();
            displayLastStoppedMillis = millis();
            debugf("Word, %02d:%02d, [%s]\n", timeinfo.tm_hour, timeinfo.tm_min, word);
            displayString(word);

            // For testing only (changes all characters and requires a drum rotation + calibration each time)
            // nextWordAPIMillis = (millis() + (3600 / word_updates_per_hour) * 1000) - 3000; //dont check again until nearly next word update time
            // String thisSeq = "@@@@@@@@@@@@";
            // charSeq--;
            // if (charSeq == 0) {
            //   charSeq = 39;
            // }
            // thisSeq.replace("@",String(letters[charSeq]));
            // displayString(thisSeq);

          }
        }
      }

      // Handle Button Press Code Here
      // else if (digitalRead(button1Pin) == 0) {
      //   do something;
      // }
    }
    //If display has been moving for more than 20 seconds, must be an error condition
    else if (millis() - displayLastStoppedMillis > 20000) {
      debugln(TXT_RED "Moving > 20 secs - RESTARTING!!!" TXT_RST);
      nvmem.magic = RTC_MAGIC;
      strncpy(nvmem.previous_display,save_display,13);
      nvmem.reboot_count = reboot_count;
      ESP.restart();
    }

    // Handle interactive serial commands over USB (used for debugging)
#if DEBUG == 1
    if (Serial.available()) {
      test_command = Serial.readStringUntil('\n');
      test_command.replace("\r","");
      test_command.toUpperCase();

      // if only return was pressed then repeat last command
      if (test_command.length() == 0) { 
        test_command = test_command_previous;
      }

      if (test_command.charAt(0) == char(92)) { //backslash
        print_test_menu();
      }
      else if (test_command.charAt(0) == ']') {
        test_num = test_command.substring(1,3).toInt();
        if (test_num >= 0) {
          active_menu_unit = test_num;
          debugf("Active unit set to %d\n", active_menu_unit);
        }
      }
      else if (test_command.charAt(0) == '>') {
        test_num = test_command.substring(1,5).toInt();
        if (test_num > 0) {
          debugf("Move %d flaps\n", test_num);
          splitFlap[active_menu_unit]->moveStepperbyFlap(test_num);
        }
      }
      else if (test_command.charAt(0) == '}') {
        test_num = test_command.substring(1,5).toInt();
        if (test_num > 0) {
            debugf("Move all flaps by %d\n", test_num);
            for (uint8_t unit = 0; unit < UNITCOUNT; unit++) {
              splitFlap[unit]->moveStepperbyFlap(test_num);
            }
        }
      }
      // Move stepper by a raw number of steps
      else if (test_command.charAt(0) == '~') {
        test_num = test_command.substring(1,5).toInt();
        if (test_num > 0) {
          debugf("Move %d steps\n", test_num);
          splitFlap[active_menu_unit]->moveStepperbyStep(test_num);
        }
      }
      else if (test_command.charAt(0) == '|') {
        ESP.restart();
      }
      else if (test_command.charAt(0) == '%') {
        String word = wordOfTheDay();
        debugf("Word, %02d:%02d, [%s]\n", timeinfo.tm_hour, timeinfo.tm_min, word);
        displayString(word);
      }   
      else if (test_command.charAt(0) == '+') {
        String thisSeq = "@@@@@@@@@@@@";
        // String thisSeq = "       @ ";
        charSeq--;
        if (charSeq == 0) {
          charSeq = 39;
        }
        thisSeq.replace("@",String(letters[charSeq]));
        displayString(thisSeq);
      }  
      else if (test_command.charAt(0) == '<') {
        debugln("Put ESP to sleep until power reset");
        esp_deep_sleep_start();
      }      
      else {
        test_command.toUpperCase();
        debugf("Display %s\n", test_command);
        displayString(test_command);
      }
      test_command_previous = test_command;
    }
#endif
  }

}

void print_test_menu() {
  getNTP(now, timeinfo);

  debugln(TXT_GREEN "----------------------------------");
  debugf ("     %s", asctime(&timeinfo));
  debugln("Enter a command followed by return");
  debugln("----------------------------------");
  debugln("\\   : Show this menu");
  debugln("}   : Move ALL forward number of flaps");
  debugf ("]   : Set active unit for this menu [%d]\n", active_menu_unit);
  debugln(">   : Move forward number of flaps");
  debugln("~   : Move forward number steps");
  debugln("|   : Reset Display");
  debugln("%   : Display a random word");
  debugln("+   : Test: countdown of all flaps");
  debugln("<   : Idle");
  debugln("any : display given text");
  debugln("----------------------------------" TXT_RST);
}

// Callback routine that actions the enable on or off triggered by setAutoEnable
bool setExternalPin(uint8_t pin, uint8_t value) {
  pin = pin & ~PIN_EXTERNAL_FLAG;

  // When using SLEEP instead of /ENABLE on the A4988 to save idle power, need to invert
  // debugf("mcp en pin %d set to %d\n", pin, value ^ 0x01);
  mcp_en_steppers.digitalWrite(pin, value ^ 0x01);

  return value;
}

void recalibrate_units() {
  uint8_t unitsCalibrating;
  int8_t calibrationResult;

  unitsCalibrating = 0;
  for (uint8_t unit = 0; unit < UNITCOUNT; unit++) {
    if (!splitFlap[unit]->calibrationComplete) {
      if (!splitFlap[unit]->calibrationStarted) {
        splitFlap[unit]->calibrateStart();
        unitsCalibrating++;
      }
      else {
        calibrationResult = splitFlap[unit]->calibrate();
        if (calibrationResult == 0) {
          unitsCalibrating++;
        }
        else if (calibrationResult < 0) {
          debugf("Calibration failed for unit %d\n", unit);
          debugln(TXT_RED "RESTARTING!!!" TXT_RST);
          nvmem.magic = RTC_MAGIC;
          strncpy(nvmem.previous_display,save_display,13);
          nvmem.reboot_count = reboot_count;
          ESP.restart();
        }
      }
    }
  }
}

void IRAM_ATTR sensor_ISR() {
    sensortriggered = true;
}

void updateHallSensors() {
  uint8_t newvalue;

  mcp_sensor.clearInterrupts();
  uint8_t sensor_port_current_a = mcp_sensor.readPort(MCP23017Port::A);
  uint8_t sensor_port_current_b = mcp_sensor.readPort(MCP23017Port::B);

  for (uint8_t unit = 0; unit < UNITCOUNT; unit++) {
    if (sensorPort[unit] == 'A') {
        newvalue = ((~sensor_port_current_a & sensorPortBit[unit]) == 0);
        if (!splitFlap[unit]->updateHallValue(newvalue)) {
          splitFlap[unit]->moveStepperbyFlap(1);
          splitFlap[unit]->calibrationComplete = false;
          splitFlap[unit]->calibrationStarted = false;
          splitFlap[unit]->pendingLetter = splitFlap[unit]->destinationLetter;
        }
    }
    else {
        newvalue = ((~sensor_port_current_b & sensorPortBit[unit]) == 0);
        if (!splitFlap[unit]->updateHallValue(newvalue)) {
          splitFlap[unit]->moveStepperbyFlap(1);
          splitFlap[unit]->calibrationComplete = false;
          splitFlap[unit]->calibrationStarted = false;
          splitFlap[unit]->pendingLetter = splitFlap[unit]->destinationLetter;
        }
    }
  }
}

void displayString(String display) {
  uint8_t test_length;
  char display_char;

  display.toUpperCase();
  strncpy(save_display, display.c_str(), 13); // save display in case of reboot

  test_length = display.length();
  if (test_length > UNITCOUNT) {
    test_length = UNITCOUNT;
  }
  for (uint8_t char_pos = 0; char_pos < test_length; char_pos++) {
    display_char = display.charAt(char_pos);
    splitFlap[char_pos]->moveSteppertoLetter(display_char);
  }
}

boolean diplayStillMoving () {
  boolean display_busy = true;
  display_busy = false;
  for (uint8_t unit = 0; unit < UNITCOUNT; unit++) {
    if (splitFlap[unit]->checkIfRunning()) {
      display_busy = true;
    }
  }
  return display_busy;
}

// void intToBinary(int num, char* binaryStr) {
//     for (int i = 7; i >= 0; i--) {
//         int bit = (num >> i) & 1;
//         binaryStr[7 - i] = bit + '0'; // Convert the bit to '0' or '1'
//     }
//     binaryStr[8] = '\0'; // Null-terminate the string
// }

// void debugUnitFlags(String prefix) {
//   for (uint8_t unit = 0; unit < UNITCOUNT; unit++) {
//     debugf("%s,%02d,%c,%d,%d\n", prefix, unit, (splitFlap[unit]->pendingLetter == 0) ? 95 : splitFlap[unit]->pendingLetter, splitFlap[unit]->calibrationStarted, splitFlap[unit]->calibrationComplete);
//   }
// }

system.cpp

#include "system.h"

const char* word_server = "api.wordnik.com";  // word server
WiFiClientSecure client;
WebServer server(80);

// HTML web page to handle input of text to display
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html><head>
  <title>Split-Flap Display</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  </head><body>
  <form action="receiveInput" method="POST">
    <p style="font-weight: bold; margin-bottom:12px;">Split-Flap Display</p>
    <input type="text" id="displaytext" name="displaytext">
    <input type="submit" value="Update" id="submitbtn" onclick="this.hidden=true; document.getElementById('submitrnd').hidden = true"><br/><br/>
  </form>
  <form action="randomWord" method="POST">
    <input type="submit" value="Random Word" id="submitrnd" onclick="document.getElementById('displaytext').value = '***RANDOM***'; this.hidden=true; document.getElementById('submitbtn').hidden = true">
  </form>
  </body></html>)rawliteral";

void disableCertificates() {
    client.setInsecure();
}

boolean synchroniseWith_NTP_Time(time_t &now, tm &timeinfo){
  uint8_t timeout = 0;
  
  // debug("Setting time using SNTP");
  while (now < NTP_MIN_VALID_EPOCH && ++timeout < 50) {
    delay(100);
    // debug(".");
    now = time(nullptr);
  }

  if (timeout >= 50) {
    return false;
  }

  localtime_r(&now, &timeinfo); // update the structure tm (timeinfo) with the current time
  
  return true;
}

boolean getNTP(time_t &now, tm &timeinfo) {
  // If cannot get NTP time, return error
  if (!synchroniseWith_NTP_Time(now, timeinfo)) {
    debugln("Error getting time");
    return false;
  }
  else {
    time(&now); // read the current time
    localtime_r(&now, &timeinfo); // update the structure tm with the local current time
  }
  return true;
}

String wordOfTheDay() {
    JsonDocument jsonBufferData;
    String json_word;

    if (WORDNIKAPIKEY != "") {
      // debugln("\nStarting connection to word server...");
      if (!client.connect(word_server, 443)) {
          debugln("Connection failed!");
          return "";
      }
      else {
          // debugln("Connected to word_server!");
          // Make a HTTP request:
          client.println("GET " WORDNIKURL " HTTP/1.0");
          client.println("Host: api.wordnik.com");
          client.println("Connection: close");
          client.println();

          while (client.connected()) {
          String line = client.readStringUntil('\n');
          if (line == "\r") {
              // debugln("headers received");
              break;
          }
          }
          // if there are incoming bytes available
          // from the word_server, read them and print them:
          while (client.available()) {
          char c = client.read();
          json_word += c;
          }
          
          client.stop();
      }

      //Parse JSON to get word
      DeserializationError jsonError = deserializeJson(jsonBufferData, json_word);

      // Test if parsing succeeds
      if (jsonError) {
          debug(F("deserializeJson() failed: "));
          debugln(jsonError.f_str());
          return "";
      }

      const char* word = jsonBufferData["word"];
      return padToFullWidth(word);
    }
    else {
      debugln("WORDNIKAPIKEY not specified in config.h");
      return("");
    }

}

void setup_routing() {     
  // Send web page with input fields to client
  server.on("/", HTTP_GET, sendwebpage);  
  server.on("/display", HTTP_POST, receiveAPI);    
  server.on("/receiveInput", HTTP_POST, receiveInput);    
  server.on("/randomWord", HTTP_POST, randomWord);    
  
  server.onNotFound(handle_NotFound);

  server.begin();    

  // Setup mDNS to allow connection via hostname
  if (!MDNS.begin(NETWORKNAME)) {   // Set the hostname NETWORKNAME defined in system.h: by default "splitflap.local"
    debugln("Error setting up MDNS responder!");
  }

  MDNS.addService("http", "tcp", 80);
  MDNS.addServiceTxt("http", "tcp", NETWORKNAME, "1");
}

void sendwebpage() {
  server.send(200, "text/html", index_html);
}

void receiveAPI() {
  JsonDocument jsonBufferData;
  const char* displaytext;
  String body = server.arg("plain");
  
  DeserializationError jsonError = deserializeJson(jsonBufferData, body);

  // Test if parsing succeeds
  if (jsonError) {
      debug(F("deserializeJson() failed: "));
      debugln(jsonError.f_str());
      return;
  }

  displaytext = jsonBufferData["displaytext"];

  debugf("Text to display from API: %s\n", padToFullWidth (displaytext));
  displayString(padToFullWidth (displaytext));

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

void receiveInput() {
  JsonDocument jsonBufferData;
  const char* displaytext;
  String inputText = server.arg("displaytext");

  displaytext = inputText.c_str();

  // server.send(200, "text/plain", "{}");
  server.sendHeader("Location", "/",true);  
  server.send(302, "text/plain", "");  

  delay(500);
  displayString(padToFullWidth (displaytext));
}


void randomWord () {
  String word = wordOfTheDay();
  debugf("Word, [%s]\n", word);

  server.sendHeader("Location", "/",true);  
  server.send(302, "text/plain", "");

  delay(500);
  displayString(word);
}

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

String padToFullWidth (const char* word) {
  String word_fullwidth = word;

  if (word_fullwidth.length() <= UNITCOUNT - 2) {
      word_fullwidth = " " + word_fullwidth;
  }

  while (word_fullwidth.length() < UNITCOUNT) {
      word_fullwidth += " ";
  }

  return word_fullwidth;
}

unit.cpp

#include "unit.h"

Unit::Unit(FastAccelStepperEngine& engine, uint8_t unit) {
  unitNum = unit;
  stepper = engine.stepperConnectToPin(unitStepPin[unitNum]);

  stepper->setEnablePin(UnitEnablePin[unitNum] | PIN_EXTERNAL_FLAG);
  stepper->setAutoEnable(true);
  stepper->setDelayToEnable(1000); // microseconds
  stepper->setDelayToDisable(2); // milliseconds
  // debugf("Unit %d Step pin set to %d\n", unitNum, unitStepPin[unitNum]);

  stepper->setSpeedInUs(rotationSpeeduS);  // the parameter is us/step
  stepper->setAcceleration(4000);

  missedSteps = 0; 
  currentLetterPosition = 0;
  destinationLetter = 0;
  pendingLetter = 0;
  calibrationStarted = false;
  calibrationComplete = false;
  currentHallValue = 1;
  lastHallUpdateTime = 0;
  }

// calc number of steps to rotate based on cumulative step error
int16_t Unit::stepsToRotate (float steps) {
    int16_t roundedStep = (int16_t)steps;
    missedSteps = missedSteps + ((float)steps - (float)roundedStep);
    if (missedSteps > 1) {
        roundedStep = roundedStep + 1;
        missedSteps--;
    }
  return roundedStep;
}

// translates char to letter position
uint8_t Unit::translateLettertoInt(char letterchar) {
  for (int i = 0; i < 45; i++) {
    if (letterchar == letters[i]) {
      return i;
    }
  }
  return 0;
}

// calc flaps to rotate to get to a specified letter
uint8_t Unit::flapsToRotateToLetter(char letterchar, boolean *recalibrate) {
    int8_t newLetterPosition;
    int8_t deltaFlapPosition;

    newLetterPosition = translateLettertoInt(letterchar);
    deltaFlapPosition = newLetterPosition - currentLetterPosition;

    if (deltaFlapPosition < 0) {
      deltaFlapPosition = 45 + deltaFlapPosition;
      *recalibrate = true;
    }
    else {
      currentLetterPosition = newLetterPosition;
    }

    return deltaFlapPosition;
}

// calc steps to rotate forward a specified number of flaps
uint16_t Unit::stepsToRotateFlaps(uint16_t flaps) {
  float preciseStep = (float)flaps * FlapStep[unitNum];
  return stepsToRotate (preciseStep);
}

// only for testing: Move stepper by a raw number of steps
void Unit::moveStepperbyStep(int16_t steps) {
    stepper->move(steps);
}

void Unit::moveStepperbyFlap(uint16_t flaps) {
    stepper->move(stepsToRotateFlaps(flaps));
}

void Unit::moveSteppertoLetter(char toLetter) {
  boolean recalibrate = false;

  destinationLetter = toLetter;

  uint8_t flapsToMove = flapsToRotateToLetter(toLetter, &recalibrate);
  debugf("Unit %02d flapsToMove %d\n", unitNum, flapsToMove);

  if (recalibrate) {
    calibrationComplete = false;
    pendingLetter = toLetter;
    debugf("Unit %02d pendingLetter '%c'\n", unitNum, pendingLetter);
    debugf("Pending,%02d,'%c'\n", unitNum, pendingLetter);
  }
  else {
    debugf("Unit %02d move to '%c'\n", unitNum, toLetter);
    moveStepperbyFlap(flapsToMove);
    pendingLetter = 0;
  }
}

// start calibration of the unit using the hall sensor
void Unit::calibrateStart() {
  calibrationComplete = false;
  calibrationStarted = true;
  calibrationStartTime = millis();

  stepper->runForward();

  // if starting within range of the sensor, need to move outside range before doing calibration
  if (currentHallValue == 0) {
      debugf("preInitialise started for Unit %d\n", unitNum);
      preInitialise = true;
  }
  else {
      preInitialise = false;
  }

  debugf("Calibration started for Unit %d\n", unitNum);
  
  stepper->runForward();
}

// continue calibration of the unit using the hall sensor
int8_t Unit::calibrate() {
  if (!calibrationComplete) {

    // If taking too long, there must be a problem
    if (millis() - calibrationStartTime > 12000) {
      currentLetterPosition = 0;
      calibrationComplete = true;
      calibrationStarted= false;
      debugf("calibration for Unit %d failed\n", unitNum);
      stepper->forceStop();
      return -1;
    }

    // if reached end of preinitialisation phase
    if (preInitialise == true && currentHallValue == 1) {
      preInitialise = false;
      debugf("preInitialise completed for Unit %d\n", unitNum);
    }
    //if still in preinitialising phase, keep moving
    else if (preInitialise == true) {
      return 0;
    }
    // if sensor reached, do calibration
    else if (currentHallValue == 0) {
      // reached marker, go to calibrated offset position
      debugf("Calb,%02d\n", unitNum);
      stepper->forceStopAndNewPosition(0);
      delay(1); // attempt to fix rare hangup
      stepper->move(calOffsetUnit[unitNum]);
      currentLetterPosition = 0;
      missedSteps = 0;
      calibrationComplete = true;
      calibrationStarted = false;
      // Reset speed
      // stepper->setSpeedInUs(rotationSpeeduS);  // the parameter is us/step
      debugf("Unit %d calibrated\n", unitNum);
      return 1;
      }

    return 0;
  }

  // just return if calibration already completed
  return 1;
}

boolean Unit::checkIfRunning() {
  return stepper->isRunning();
}

boolean Unit::updateHallValue(uint8_t updatedHallValue) {
  uint32_t timedelta;
  if (updatedHallValue != currentHallValue) {
    timedelta = millis() - lastHallUpdateTime;

    currentHallValue = updatedHallValue;
    lastHallUpdateTime = millis();
    // debugf("Unit: %d, Hall: %d, Delta: %d\n", unitNum, currentHallValue, timedelta);
    // debugf("Hall,%02d,%d,%lu,%d,%d,%d,'%c'\n", unitNum, currentHallValue, timedelta, preInitialise, calibrationStarted, calibrationComplete, pendingLetter);

    // If occasional glitch occurrs, start calibration again
    if (timedelta <= 100) {
      debugf(TXT_RED "GLITCH,%02d,%d,%d,'%c'\n" TXT_RST, unitNum, updatedHallValue, timedelta, destinationLetter);
      return false;
    }
  }

  return true;
}

git hub link: split-flap/src/unit.cpp at main · tinkermax/split-flap · GitHub

I used a different type of ESP32 and everything worked fine.

Any reason why you started a new topic? For reference: ESP32 code works only in debug mode

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.