pkerspe / ESP-StepperMotor-Server

Turn your ESP32 into a complete stepper motor control server with web UI, REST API and serial control interface
MIT License
225 stars 39 forks source link

Initiate Homing via RestAPI #28

Closed BenSpex closed 3 years ago

BenSpex commented 3 years ago

Is your feature request related to a problem? Please describe. Currently, the system only homes on boot up of the server. In my application, the stepper motor drives a wheel that sometimes loses grip. So before each run, I have to ensure that the 0 Position is correctly set. Rebooting the server via cmd line is not the best solution here :)

Describe the solution you'd like A Rest API Endpoint, something like SET Homing potentially including pin and direction of homing as well as max speed.

And again thanks a lot for the great Repo

pkerspe commented 3 years ago

Hi @BenSpex, thanks for the input. I understand the requirement and will investigate it. Just for the sake of completeness, could you please also paste your setup() and loop() function code?

Thanks

pkerspe commented 3 years ago

@BenSpex I just pushed an update to git but did not yet create a new release since I do not have a hardware test setup at the moment so the code is untested. I updated the readme, there is a new endpoint POST | /api/steppers/returnhome It requires one post parameter called "id" which is the config id of the stepper to start the homing run for. I hope it works, please let me know if you had the time to test it

BenSpex commented 3 years ago

Hi @pkerspe That was super quick.

I have not hardware here at the moment but will test everything on Monday.

For the completion this is my setup and loop function:

#include <ESPStepperMotorServer.h>
#include <ESPStepperMotorServer_PositionSwitch.h>
#include <ESPStepperMotorServer_StepperConfiguration.h>
#include <ESP_FlexyStepper.h>

ESPStepperMotorServer *stepperMotorServer;
ESPStepperMotorServer_StepperConfiguration *stepperConfiguration;

// Gear Ratio 15.5
// Steps per Rotaiton = 800
// Total steps per Whell rotation = 15.5 x 800
// Wheel Diameter = 100
// Steps per mm = Total steps per whell rotation / pi * diameter
// (15.5 * 800) / (2 * pi * 100/2)
#define PIl 3.141592653589793238462643383279502884L 
#define GEAR_RATIO 15.5
#define WHEEL_DIAMETER 100
#define STEP_PIN 16
#define DIRECTION_PIN 17
#define LIMIT_SWITCH_PIN 23
#define MICROSTEPS_PER_STEP 1
#define STEPS_PER_REV 3200

const char *wifiName= ""; // enter the SSID of the wifi network to connect to
const char *wifiSecret = ""; // enter the password of the the existing wifi network here
#define LSWITCH SWITCHTYPE_LIMITSWITCH_POS_BEGIN_BIT&SWITCHTYPE_STATE_ACTIVE_LOW_BIT
#define HOME_SPEED_MM_S 5.0f
#define MAX_DISTANCE_TO_HOME_MM 20000l

void setup()
{
  Serial.begin(115200);
  // now create a new ESPStepperMotorServer instance (this must be done AFTER the Serial interface has been started)
  // In this example We create the server instance with only the serial command line interface enabled
  stepperMotorServer = new ESPStepperMotorServer(ESPServerRestApiEnabled | ESPServerWebserverEnabled | ESPServerSerialEnabled | ESPServerLogLevel_DEBUG);
  stepperMotorServer->setWifiCredentials(wifiName, wifiSecret);
  stepperMotorServer->setWifiMode(ESPServerWifiModeClient); //start the server as a wifi client (DHCP client of an existing wifi network)

  //create a new configuration for a stepper
  stepperConfiguration = new ESPStepperMotorServer_StepperConfiguration(STEP_PIN, DIRECTION_PIN);
  stepperConfiguration->setDisplayName("X-Axis");
  //configure the step size and microstepping setup of the drive/motor
  stepperConfiguration->setMicrostepsPerStep(1);
  stepperConfiguration->setStepsPerMM(100);
  stepperConfiguration->setStepsPerRev(int((GEAR_RATIO*STEPS_PER_REV)/(2*PIl*(WHEEL_DIAMETER/2))));

  // now add the configuration to the server
  unsigned int stepperId = stepperMotorServer->addOrUpdateStepper(stepperConfiguration);

  //you can now also add switch and rotary encoder configurations to the server and link them to the steppers id if needed
  //here an example for a limit switch connected to the previously created stepper motor configuration (make sure the pin is not floating (use pull up or pull down resistor if needed))
  ESPStepperMotorServer_PositionSwitch *positionSwitch = new ESPStepperMotorServer_PositionSwitch(LIMIT_SWITCH_PIN, stepperId, LSWITCH, "Limit Switch", 0L);
  stepperMotorServer->addOrUpdatePositionSwitch(positionSwitch);

    ESP_FlexyStepper *flexyStepper = stepperConfiguration->getFlexyStepper();
  if (flexyStepper->moveToHomeInMillimeters(-1, HOME_SPEED_MM_S, MAX_DISTANCE_TO_HOME_MM, LIMIT_SWITCH_PIN)){
    Serial.println("Stepper Reached Home Position");
  }
  else{
    Serial.println("Stepper Could not Reach Home Position!");
  }

  //start the server
  stepperMotorServer->start();

}

void loop()
{
}
BenSpex commented 3 years ago

Hi @pkerspe so I had a spare ESP32 which I programmed with the current Version on git. I have no Stepper Motor here, but will have one at my disposal from Monday on. The new Endpoint works and it actually shows that the motor is moving in the negative direction (probably left) on the GUI. However when I trigger the PIN, the movement just continues.

On the init using the blocking flexyStepper->moveToHomeInMillimeters(-1, HOME_SPEED_MM_S, MAX_DISTANCE_TO_HOME_MM, LIMIT_SWITCH_PIN) it actually works just fine with the same PIN being used.

So the PIN is connected, but maybe just not correctly configured? I tried changing the various Begin | End | Serial Versions for the Limit Switch Setting as well as Active High / Low. Or does the non blocking MoveToHome function require a different configuration?

On another note, how would I change speed value for the returnHome endpoint? I would obviously like to choose a reduced speed. The main.cpp used is the same as above.

Thanks Ben

pkerspe commented 3 years ago

Thanks for testing @BenSpex I guess your wiring and code is correct just an internal problem with the handling of the IO state change that is not handed to the flexy stepper instance correctly. According to you code above you configure the position switch correctly as long at is indeed an active low configuration and you use a pin of the ESP32 that has the internal pull up function available. According to your code example you use pin 23 which should be just fine.

I will also implement a blocking call to moveToHomeInMillimeters as rest endpoint so you can test that as well. Might be able to get something done by end of day today.

BenSpex commented 3 years ago

Hi @pkerspe great thanks.

For testing purposes I just configured the PIN 23 as an Emergency Switch with Active Low, basically the same as the limit switch. This worked like a charm every time.

Having a Rest API for the blocking function would be great. flexyStepper->moveToHomeInMillimeters(-1, HOME_SPEED_MM_S, MAX_DISTANCE_TO_HOME_MM, LIMIT_SWITCH_PIN) Maybe even include the param for Speed, PIN and Direction? Of course direction and Pin should eventually be taken from the config, but as a hot fix and for testing purposes this would already be great.

BenSpex commented 3 years ago

Hi @pkerspe

I tried adding the blocking request in there using:

void ESPStepperMotorServer_RestAPI::handleBlockingHomingRequest(AsyncWebServerRequest *request)
{
    this->logDebugRequestUrl(request);

    if (request->hasParam("id"))
    {
        int stepperIndex = request->getParam("id")->value().toInt();
        ESPStepperMotorServer_StepperConfiguration *stepper = this->_stepperMotorServer->getCurrentServerConfiguration()->getStepperConfiguration(stepperIndex);
        if (stepper == NULL)
        {
            request->send(404, "application/json", "{\"error\": \"No stepper configuration found for given stepper id\"}");
            return;
        }

        int direction = request->getParam("dir")->value().toInt();
        int pin = request->getParam("pin")->value().toInt();
        float speed = request->getParam("speed")->value().toFloat();
        long max_dist = request->getParam("max")->value().toInt();

        stepper->getFlexyStepper()->moveToHomeInMillimeters(direction, speed, max_dist, pin);

        request->send(204);
        return;
    }
    request->send(400, "application/json", "{\"error\": \"Missing stepperid paramter\"}");
}

    httpServer->on("/api/steppers/returnhomeblocking", HTTP_POST, [this](AsyncWebServerRequest *request)
                   {
                       this->logDebugRequestUrl(request);
                       this->handleBlockingHomingRequest(request);
                   });

The homing now works. But the downside is the blocking nature of the call. Meaning that the server actually stops working until the homing procedure is completed.

For a work around it is okay, but I don't think it should find it's way in the Repository here.

If you want a way to get the non blocking call working, that would be great. Ideally with a max speed parameter.

Thanks

pkerspe commented 3 years ago

Thanks for your input. Interesting you are not experiencing any issues with the watchdog timer with you function. I implemented an updated rest api endpoint today utilising the blocking call for homing, but I get kernel panics on my esp32 due to the blocking nature of the esp flexy stepper call. So far I did not push the changes. I am still checking in the documentation of the async webserver to check how to handle auch a long running request more synchronously. At the end I feel the rest api should be asynchronous for all movement related calls to avoid performance issues / timeouts etc.

Can you try if your approach works even if the stepper has to travel for more than 1-2 seconds before the limit switch is triggered?

pkerspe commented 3 years ago

So after reading and thinking a bit about the requirement I decided that blocking functions (or basically anything that uses delay or yield functions, since it is strongly recommended in the async webserver library doc NOT to do that) must not be used. So a two stop approach needs to be implemented to first trigger the homing procedure in a rest api endpoint that only validates the parameter values and then returns immediately. A second endpoint could then be polled for the status / result of the homing request. Alternatively the client could be informed via a socket connection that the homing request completed. A long running request that only returns once the homing has completed will not suit the overall architecture of this library and specifically of its dependencies (namely esp async webserver). I will further investigate what can be cleanly implemented in order to allow for ad-hoc configuration of the limit switch ip pin vs using a previously configured Limit switch. Most likely this will require an update of the esp flexystepper library itself.

pkerspe commented 3 years ago

@BenSpex I updated the REST Endpoint, it is an asynchronous call to only trigger execution of the homing procedure. So far an endpoint to check if homing has been completed is still missing. I updated the documentation of the endpoint in the README.md and published a new Version 0.4.6

I did only basic testing on an ESP without connected hardware, but at least I could see the motion stopped and position was set as home once I connected the defined limit switch PGPIO pin to ground. Please test and let me know if it works for you.

BenSpex commented 3 years ago

Hi @pkerspe Of course the stepper froze when the homing is a bit longer. But it continued to work again afterwards with the zero position correctly established.

Thanks a lot for the super fast turn around. I see that you also changed the ESP-Flexy-Stepper Lib. Right now I'm a bit lazy and will wait the 24h until PlatformIO has crawled everything and included the new versions.

Tomorrow I will be able to test everything with actual hardware and will let you know afterwards.

BenSpex commented 3 years ago

I had a quick test without the hardware. It seems that the home direction is always positive regardless of the type of Limit Switch Begin|Left or End|Right when the switchID Param is not given.

I think updating this part will do the trick, in the homehandling function.

    ESPStepperMotorServer_PositionSwitch *switchConfig;
    signed char directionTowardHome = 1;
    byte gpioPinForSwitch = 0;
    //TOOD: implement proper ISR handling for custom switch parameter if not linked to existing stepper
    if (request->hasParam("switchid"))
    {
        int switchIndex = request->getParam("switchid")->value().toInt();
        switchConfig = this->_stepperMotorServer->getCurrentServerConfiguration()->getSwitch(switchIndex);
        if (switchConfig == NULL)
        {
            request->send(400, "application/json", "{\"error\": \"No switch configuration found for given switch id\"}");
            return;
        }
        gpioPinForSwitch = switchConfig->getIoPinNumber();
        //TODO: this detection is not exactely precise, since the switch could also be a combined Begin / End switch
        if (switchConfig->isTypeBitSet(SWITCHTYPE_LIMITSWITCH_POS_BEGIN_BIT))
        {
            directionTowardHome = -1;
        }
    }
    else
    {
        //Try to find pin by looking for configured limit switches of type homing switch for selected stepper motor config
        switchConfig = this->_stepperMotorServer->getCurrentServerConfiguration()->getFirstConfiguredLimitSwitchForStepper(stepperIndex);
        if (switchConfig == NULL)
        {
            request->send(400, "application/json", "{\"error\": \"No existing limit switch configuration found the stepper with the given id\"}");
            return;
        }
        gpioPinForSwitch = switchConfig->getIoPinNumber();
        // Added direction section for the case no ID was provided
        if (switchConfig->isTypeBitSet(SWITCHTYPE_LIMITSWITCH_POS_BEGIN_BIT))
        {
            directionTowardHome = -1;
        }
    }

Or rather than trying to guess the homing direction, add home direction into the params. This would resolve the ambiguity with the Serial Pin, as well as the setup where one would configure two limit switches for begin and end.

pkerspe commented 3 years ago

@BenSpex thanks for the input, I will add your code and also an optional parameter for the direction as suggested

pkerspe commented 3 years ago

@BenSpex new release 0.4.7. is released with your change and a new parameter "direction"

BenSpex commented 3 years ago

@pkerspe I did not experience any issues with the command and it works as intended. However I do loose WiFi connection quite often and if I use an external Pull-Up Resistor the Web server crashes and reboots the ESP32. But I think these are hardware issues rather than software.

pkerspe commented 3 years ago

@BenSpex thanks for the confirmation. As for the other problems, can you describe a bit more in detail?

So when you loose WiFi connection, does that go along with a reboot of the ESP32? Are there any kernel panics for example any crash reports / stack traces in the ESP32s serial console?

And for the external Pull-Up Resistors: Can you specify where you connected those? Which value for the resistor did you use and what configuration in the Stepper Motor Server you used for the pin you connected the Pull Up Resistor to? It might be hardware issues but could just as well be a software issue. You should see more details in the serial console when the ESP reboots. Usually it prints out some kernel panic message followed by an "encoded" strack trace that needs to be decoded to get some useful information (plugins for Arduino IDE as well as for PlaformIO exists for this decoding to be done automatically).