# doll's house elevator

Hi everybody,

Santa’s is coming and so we are now building a great doll’s house with lights and elevator. And my doubts are on the elevator.

I have come up with the solution in the picture:
1-one arduino uno
2-one Ultrasonic Sensor
3-a continuos rotation servo
4-a pushbutton.

The house has two floors, 35cm (14 inches) each.
The ultrasonic sensor is on the top. So: when it measures the elevator being less than 5 cms far and the pushbutton is pressed, than the servo should rotate clockwise UNTIL the elevator is 35 cms far. If instead is more than 34 cms, the servo should go counterclockwise UNTIL the elevator is on the top.

How can I measure while I am rotating the servo? The code I have until now is the one below. The servo rotates in the right direction, but I do not know how to measure the distance in the meanwhile

``````#include <Servo.h>
Servo myservo;
int buttonInput = 2;
int duration;                                                          //Stores duration of pulse in
int distance;
int sensorpin = 7;                                                 // Pin for SRF05
int buttonState=0;
void setup()
{
Serial.begin(9600);
}

void loop()
{

buttonState = digitalRead(buttonInput);
if (buttonState==HIGH) {
myservo.attach(9);
myservo.writeMicroseconds(1500);
pinMode(sensorpin, OUTPUT);
digitalWrite(sensorpin, LOW);                          // Make sure pin is low before sending a short high to trigger ranging
delayMicroseconds(2);
digitalWrite(sensorpin, HIGH);                         // Send a short 10 microsecond high burst on pin to start ranging
delayMicroseconds(10);
digitalWrite(sensorpin, LOW);                                  // Send pin low again before waiting for pulse back in
pinMode(sensorpin, INPUT);
duration = pulseIn(sensorpin, HIGH);                        // Reads echo pulse in from SRF05 in micro seconds
distance = duration/58;                                      // Dividing this by 58 gives us a distance in cm
Serial.println(distance);                                              // Wait before looping to do it again
delay(100);
if (distance <35)
{
myservo.writeMicroseconds(1600);}
else {   myservo.writeMicroseconds(1400);
Serial.println(distance);
}
} else  {Serial.println("bottone not pressed");}
}
``````

Thank you for the help

The ultrasonic sensor is on the top. So: when it measures the elevator being less than 5 cms far and the pushbutton is pressed, than the servo should rotate clockwise UNTIL the elevator is 35 cms far. If instead is more than 34 cms, the servo should go counterclockwise UNTIL the elevator is on the top.

You really only need to sense two positions - Top Floor and Bottom Floor.

Check the resolution of your distance sensor.

I'd go with a [u]slotted optical switch[/u] or a [u]reflective optical switch[/u] but if you want to use an ultrasonic sensor that's fine too.

One issue with an optical switch is that you might not know if you've gone past the limits, and of course with your distance sensor that's not a problem.

Or you could use a regular-old [u]microswitch[/u] at the top & bottom.

How can I measure while I am rotating the servo?

Your servo only runs when your software tells it to run so the software always "knows" if it's running and the direction.

I would have used a geared-down DC motor, but one advantage of servos is that they have a built-in driver circuit.

Go here, learn this.How to do multiple things at once.

Code that does thing #1 and when that's done, thing #2, etc, can't do anything else but that.

But you need to sense the position all the time, not just after it's about right.

I would just use contacts and a DC motor but still have to watch pins while moving the box.

First lesson is to write code that does not block other code from running. That link will get you started using simple common-sense example to get new ideas across. Learn that and this convo can move forward much quicker.

Hi, as soon as I could I read anything about millis and multitasking in arduino. To be honest…seems easy when you read, but is quite confusing to me when it comes time to apply…
So: if I push the button it all starts. The servo turns in clockwise if the distance is high, counterclockwise if it’s low. In the serial monitor the distance is continuosly displayed.
Now I have some new problems:

1. It works only while button is pressed: how can I just click the button and record that state ?
2. If the distance change while button is pressed, the servo goes clock/counterclockwise accordingly but never stops. In my code I commented some lines in the end which were supposed to stop the motor. But if I use those lines, the servo just stays.

I think my interpretation of how to use millis in my example lacks of something…

Thak you for any more suggestions!

``````#include <Servo.h>
Servo myservo;
int buttonInput = 2;
int duration;                                                          //Stores duration of pulse in
int duration2;
int distance;
int distancedown;
int distanceup;
int goingdown =0;
int goingup =0;
int sensorpin = 7;                                                 // Pin for SRF05
int buttonState=0;
const unsigned long distInterval = 500;//period to check distance
unsigned long distTimer;

void setup()
{
Serial.begin(9600);
myservo.attach(9);
myservo.writeMicroseconds(1500);   //keep the servo stopped
distTimer = millis();
}

void loop()
{
buttonState = digitalRead(buttonInput);
if (buttonState==HIGH) {
if ((millis()-distTimer)>=distInterval);
digitalWrite(sensorpin, LOW);                          // Make sure pin is low before sending a short high to trigger ranging
delayMicroseconds(2);
digitalWrite(sensorpin, HIGH);                         // Send a short 10 microsecond high burst on pin to start ranging
delayMicroseconds(10);
digitalWrite(sensorpin, LOW);                                  // Send pin low again before waiting for pulse back in
pinMode(sensorpin, INPUT);
duration = pulseIn(sensorpin, HIGH);                        // Reads echo pulse in from SRF05 in micro seconds
distance = duration/58;                                      // Dividing this by 58 gives us a distance in cm
Serial.println(distance);                                              // Wait before looping to do it again
delay(100);
if (distance>34) { //that means the elevator is on the first floor and should go up
myservo.writeMicroseconds(1600);
goingup=1;}
if (distance<7) { //that means the elevator is on the second floor and should go down
myservo.writeMicroseconds(1400);
goingdown=1;}
/*
if ((goingup=1) && (distance <=6)){
myservo.writeMicroseconds(1500); }
if ((goingdown=1) && (distance >=34)){
myservo.writeMicroseconds(1500);
}
*/
}
}
``````

Don't just read. You HAVE to type it yourself to learn.
In my time, taking notes by hand forced stronger retention of lectures. The act requires intent, it grows neural connections clear down to your fingers. Don't just look at the blocks, play with them.
You need to mess with and learn the code. What you don't DO, you don't LEARN.
You start with what an example gives you and once you understand how it works, you go from there when you need that basic functionality again. Adding tasks is easy.

Go here, learn this.How to do multiple things at once.

At least ask good questions about parts you don't get!

When you learn the many things together lesson, you will take a different view of how to code your project.

Sensors will be handled by code that is inside of loop() and no other logical structure.
Motors, ditto.

The decision code should be simple, state-based. That's a smaller lesson, a key lesson.

Your code makes a virtual machine of parts.
A sensor part maintains output. The ultrasonic sensor code should only pulse the sensor 100x a second at most and update a variable with the new distance each time. That is all.
A button part maintains a status that other parts may trigger on.
A process part that uses button and sensor data to decide what the motor should do and set a variable to control the motor part.
A motor part that does what the control variable tells it to. Nothing more.

Each part should be non-blocking, learn to not block!

Have a look at Several Things at a Time

When the Arduino gives a servo command the servo will continue operating in the background without further input from your program. That leaves the Arduino free to check the sensors - whichever sort you choose.

...R

Thank you both for your suggestions: GoForSmoke, I appreciate your words, I try to convert them to more motivation to learn. Robin2, I downloaded and studied the SeveralThingsAtatime code and found it extremely understandable, so I tried to rewrite my code separating everything in blocks as is done in yours.

As a result, I studied again and again and rebuilt the whole code.
Now the logic is
-I created three voids, one for measuring the distance one for controlling the servo move and the last for stopping the motor when the right distance is reached.
-I put the button control in the main loop, so to check when the button is pressed, if it’s pressed it executes the other voids
-Now I used millis to check the distance while I am moving the servo and more thing are being done together.
-I simplified the code to check just one position of the elevator: I will add the remaining later, when the first part is ok

It’s beginning to work, as - if I keep the button pressed and the measured distance is the expected, the servo moves and the distance keeps being updated in the serial monitor. Plus, when the distance reach the desired level, the servo stops as expected.

My remaining problems are now:
-how to record the pushbutton state and let it wait until the motor as stopped (in my code, I have to keep it pressed, but I’d like to push and release and watch the elevatore move)
-after the first stop, the motor stays stopped. Is there something in my code to being done to reset it to the starting behaviour?

``````#include <Servo.h>
Servo myservo;
int buttonInput = 2;
int duration;
int distance;
int sensorpin = 7;                                                 // Pin for SRF05
int buttonState=0;
int buttonHIGH=0;
int stop=0; // variable to store the value that indicates the elevator is going up
const unsigned long  buttonInterval = 500; // number of millisecs between button readings
const unsigned long distInterval = 1000;//period to check distance
const unsigned long logInterval = 1000;//period to check distance
const unsigned long servoInterval = 1000;//period to check distance
const unsigned long stopupInterval = 1000;//period to check distance
unsigned long currentMillis = 0;
unsigned long previousDistanceMillis=0;
unsigned long previousButtonMillis=0;
unsigned long previousLogMillis=0;
unsigned long previousServoMillis=0;
unsigned long previousStopupMillis=0;

void setup()
{
Serial.begin(9600);
myservo.attach(9);
myservo.writeMicroseconds(1500);   //keep the servo stopped
}

void loop()
{
buttonState = digitalRead(buttonInput);
currentMillis = millis();
if (buttonState==HIGH){
readDistance();
moveServo();
stopServo();
}
}
void readDistance() {
if (currentMillis - previousDistanceMillis >= distInterval) {
digitalWrite(sensorpin, LOW);
delayMicroseconds(2);
digitalWrite(sensorpin, HIGH);
delayMicroseconds(10);
digitalWrite(sensorpin, LOW);
pinMode(sensorpin, INPUT);
duration = pulseIn(sensorpin, HIGH);
distance = duration/58;
Serial.println(distance);
previousDistanceMillis += distInterval;}
}
void moveServo(){
if (currentMillis - previousServoMillis >= servoInterval) {
if (distance >35) {
//mentre sono qui dentro devo leggere la distanza e muovere il servo
if (currentMillis - previousDistanceMillis >= distInterval) {
digitalWrite(sensorpin, LOW);
delayMicroseconds(2);
digitalWrite(sensorpin, HIGH);
delayMicroseconds(10);
digitalWrite(sensorpin, LOW);
pinMode(sensorpin, INPUT);
duration = pulseIn(sensorpin, HIGH);
distance = duration/58;
Serial.println(distance);
stop=1;
previousDistanceMillis += distInterval;
}
if (currentMillis - previousDistanceMillis < distInterval) {
myservo.writeMicroseconds(1600);
previousDistanceMillis += distInterval;
}

}
previousServoMillis += servoInterval;}
}
void   stopServo(){
if (currentMillis - previousStopupMillis >= stopupInterval) {
if ((stop=1)&&(distance<7)){
myservo.writeMicroseconds(1500);
stop=0;
}
previousStopupMillis += stopupInterval;
}
}
``````

In your code you do everything if (buttonState==HIGH){

What you need to do is to set a variable - let's call it goUp - to true when the button is pressed. Then the rest of your code works if (goUp = true) {

At the place in your code where the lift stops you can add a line goUp = false;

...R

You have the parts but as Robin pointed out, they are -inside- of the button press code so they only work when the button is pressed.

The parts have to run each on its own, none inside of the other.

Loop() is what makes then able to do it, ... each part is ready to run and at least runs a check to see if it should.

Timed parts usually check the millis or micros clock to see if their time to "do siomething" has arrived:

loop()
{
if ( now - start >= wait ) // a repeating timer for blinking or running a sensor every 10ms, etc.
{
do the thing that needs to be done when time is up and reset the start time
start = start + wait; // if this got to wait late, the next time is still set correct
}

if ( a_different_wait > 0 ) // a one-shot timer, some other task sets wait and this runs later on.
{
if ( now - a_different_start >= a_different_wait )
{
do the thing that needs to be done when time is up and set the wait time to zero
a_different_wait = 0;
}
}

................. and more code parts, none inside of any other

}

The button code should set/update a variable that other code parts will use to decide to run or not.

The button part itself can be tricky if it's not going to block the other parts from acting smoothly and on-time. If you put a 0.1F capacitor across the button itself, the button gets a LOT simpler to code for.

The sensor part should have a repeating timer to over and over (20 to 100 times a second, long waits to Arduino, once every 10 to 50 millis) and do nothing but update a distance variable.
The sensor part always runs even if only to see that its wait is not over, if (start-now>=wait)....

The motor part checks a variable to see if it should go up, down or stop. It can't do that if it's stuck inside of the button part.

The decision part should read the button state variable and the distance variable then set the motor variable (up/down/stop) to suit. It will need to keep track of what it was doing, use a variable to do that.

It is the variables that control the flow of execution in this approach, the motor is controlled by a variable value and runs freely otherwise outside of the braces in the decision part. Even as the motor runs, the decision part keeps watching the variables it does to signal the motor part when to change.

Both parts are separate but you see that the variables control what both do.

All parts "get close attention" when loop() runs fast, they get run just as fast. That is the heart of smoothly running many thing together smoothly, it make fast responses. The variables that are used to pass information between parts makes the coordination. You can get coordinated, responsive automation this way which is how it's been done for decades, nothing new.

Tip: Arduino IDE has in the Tool Menu, Autoformat. It makes the indent levels right and let you know if an error stops it. When you write Arduino code it is good to use Autoformat every few changes.

``````void loop()
{
buttonState = digitalRead(buttonInput);
currentMillis = millis();

if (buttonState==HIGH)
{
readDistance();
moveServo();
stopServo();
}
}
``````

What this tells me is that you did not understand how fast that loop() should repeat. It should repeat at least once per millisecond, more like 5 times a millisecond.

So the servo doesn’t have to stop until it is time to stop and time goes by as loop() runs 1000’s of times per second.

I warn you, when your code gets that fast, a contact button with no hardware debounce will read many press and release for every one actual press or release. Did Robin’s thread make that clear, about button bounce and the need to deal with it? Simply reading the pin is not enough.

Do you think that with cooperation that you could take one of Robin’s button examples and then, again with cooperation/help, move your sensor and motor code into that framework? If you do then make sure whatever example you start with works as it should on your setup so from the start all is known?

Hi all,

thank you for your advices, now my code seems more working, even if it has a strange behaviour.
The button is working ok, but it’s not “smooth” (some clicks are missed): I think the capacitor suggested by GoForSmoke could do the job.
The motor now turns in the right direction and the distance is read every 50ms.
But, when the distance changes (that is, the elevator goes up for example) the motor, at the right moment, start turning in the opposite direction (instead of stopping).
I tried to put some Serial.print to log a little in the code, and that seems to never enter the “stopServo()” procedure. Again, I think I should improve the use of the timers.
I will work again on it…but wanted to share with you my progress.

thank you

``````#include <Servo.h>
#include <NewPing.h>
Servo myservo;
#define TRIGGER_PIN  7  // Arduino pin tied to trigger pin on the ultrasonic sensor.
#define ECHO_PIN     7  // Arduino pin tied to echo pin on the ultrasonic sensor.
#define MAX_DISTANCE 300 // Maximum distance we want to ping for (in centimeters). Maximum sensor distance is rated at 400-500cm.
NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE);
int distance = 0;
int buttonInput = 2;
int sensorpin = 7;                                                 // Pin for SRF05
int buttonState = 0;
int buttonHIGH = 0;
boolean runningUp = false;
boolean runningDown = false;
boolean bottonePremuto = false;
boolean stopping = false;
unsigned long currentMillis = 0;
const unsigned long decideInterval = 100;//period to check distance
const unsigned long stopInterval = 100;//period to check distance
const unsigned long distInterval = 100;//period to check distance
unsigned long previousDecideMillis = 0;
unsigned long previousStopMillis = 0;
unsigned long previousDistanceMillis = 0;

void setup()
{
currentMillis = millis();
Serial.begin(9600);
myservo.attach(9);    //keep the servo stopped
}

void loop()
{
readDistance();
buttonState = digitalRead(buttonInput);
currentMillis = millis();
if (buttonState == HIGH) {
bottonePremuto = true;
}
if (bottonePremuto == true) {
decideDirection();
stopServo();
}
}
void readDistance() {
if (currentMillis - previousDistanceMillis >= distInterval) {
delay(50);                     // Wait 50ms between pings (about 20 pings/sec). 29ms should be the shortest delay between pings.
Serial.print("Ping: ");
Serial.print(sonar.ping_cm()); // Send ping, get distance in cm and print result (0 = outside set distance range)
currentMillis = millis();
Serial.println("cm");
distance = sonar.ping_cm();
previousDistanceMillis += distInterval;
}
}

void decideDirection() {

if ((currentMillis - previousDecideMillis >= decideInterval) && (stopping == false)) {
if (distance >= 28) {
Serial.println("DECIDED TO GO DOWN");
myservo.writeMicroseconds(1400);
runningUp = true;
runningDown = false;
}
else {
Serial.println("DECIDED TO GO UP");
runningDown = true;
runningUp = false;
myservo.writeMicroseconds(1600);
}
previousDecideMillis += decideInterval;
}
}
void stopServo() {
if (currentMillis - previousStopMillis >= stopInterval) {
if ((distance <= 10) && (runningUp == true)) {
Serial.println("stopping");
myservo.writeMicroseconds(1500);
runningUp = false;
runningDown = false;
bottonePremuto = false;
stopping = true;
}
if ((distance >= 29) && (runningDown == true)) {
Serial.println("stopping");
myservo.writeMicroseconds(1500);
runningUp = false;
runningDown = false;
bottonePremuto = false;
stopping = true;
}
previousStopMillis += stopInterval;
}
}
``````

The button is working ok, but it’s not “smooth” (some clicks are missed): I think the capacitor suggested by GoForSmoke could do the job.

Your code is not unblocked enough for button bounce to make a difference. Bad part is, that is why it doesn’t run smooth and misses button presses.

With the button, you don’t operate on HIGH or LOW states but instead on change of state, LOW to HIGH for example. That way if the button is held, it is not seen as press, press, press, etc, many times a second. Button output is your bottonePremuto.

Detect change is simple, is it the same state now as last time I looked? No? Then it changed.

So your button task reads the HIGH or LOW and compares to last time and sets a variable (bottonePremuto) to tell if the change you want has been detected or not. 1 press, 1 time set button output variable (bottonePremuto) to yes and every other time to no.

So your decide task reads the button output variable and sets its own variable to up or down.
The direction decide should not have a timer. It should only watch for the button output to be yes and then it should use the distance to set the direction variable, nothing else.

Your distance sensor (I would use limit switches, there is a better use for that sensor ) should run on a timer once every 50 millis. Then you can get rid of that delay(50) that blocks the rest of the code. You have a timer for direction decide that you don’t need there, give it to the distance sensor!
Distance sensor code should run every X millis and only update a variable that tells distance. Nothing else should touch it, it should always run if only to see that it is not yet time to sense just so when the time comes it WILL run regardless of what the rest of the sketch is doing.

If the motor code then reads the direction (up or down) and the distance, it can know when to stop and set the direction to stop so next loop() it doesn’t try to run again.
Here I have violated the IPO principle. Ideally there should be another task/part that reads the direction and distance tells the motor up/down/stop but look, I merged both functions. Please don’t tell and I won’t either.

I'd make a state nachine with five states:

Startup
At_bottom
Going_up
At_top
Going_down

In each state read the buttons/sensors and do the appropriate thing....

The reason for the Startup state is that at startup the cage can be anywhere - bottom, top, or in between. So if in between run the cage cage either up or down till you know where it is, and change state to At_top or At_bottom.

regards Allan

The distance sensor tells where it is.

Have you gone through the Many Things At Once lesson?

allanhurst:
I'd make a state nachine with five states:

Startup
At_bottom
Going_up
At_top
Going_down

In each state read the buttons/sensors and do the appropriate thing....

The reason for the Startup state is that at startup the cage can be anywhere - bottom, top, or in between. So if in between run the cage cage either up or down till you know where it is, and change state to At_top or At_bottom.

regards Allan

I've been trying to think of the best way to put this. Please, if I seem rough it's because I'm being real and not being personal. I had to learn all this myself the hard way during the 80's.

What do you need to do with that approach to always detect and respond to inputs? Every state that doesn't run quickly has to read the pins, which in the case of bouncing contact switches takes many 10's of 1000's of cpu cycles worth of time.

YES you CAN do that always watch and timely react to inputs without blocking INSIDE of the state machine but please please please show me how to do that without creating a lot of logic structure that you would not when writing each input handler OUTSIDE of the state machine.

I have trouble remembering words at times. I describe the usual learn-to-code path as top-down or IT code or as turn-based code.

A better but not perfect description is synchronous code approach. That approach has the code handle events when the code is ready for them usually as it moves from one stage to another but with enough structure it can get very flexible and responsive.
What I found over years is that it usually leads to spaghetti code whether small or large bowl. When you see deep indent levels and/or the same code/function calls repeated in case after case (or worse, if-elseif-elseif-etc), there it is. When you need to change one thing and that requires tracing though many different sets of braces to get them all and checking that each place the change does not violate the existing logic (make bugs), when input. process, and output code are closely weaved on a case by case basis then that is spaghetti code, when the exception code starts to get to a significant amount, that a solid bane of a programmers existence yet to so many it is a fact of life once the program grows and gets flexed too hard.
Back in the day I went through stages of identify, alleviate and then attack the problem while writing fixed-price contracts as my way to pay the bills. That's a lot of incentive. I will try and save anyone from going through as much of that as possible.

Enter the asynchronous code approach. It has the code handle events as they come, always ready for the next and doing processing in the cycles between. A better name may be event driven code.

Contact switches (buttons) not debounced in hardware take time to resolve when switched. Cap sensing takes time as does ultrasonic distance detection and good old serial input often plods by comparison. Motors, serial out and lots of outputs require attention over time.
Dedicated tasks for all of those puts the code to handle each in one place. Change? One place unless you change the whole meaning of what it does to something the rest of sketch can't handle. There is no rule that has no exceptions, anyone can screw things up, I can too.

By handling a button over time in a task that outputs status you change the real into an abstract that the other code, like a state machine, can handle immediately without all the extra structure to handle the over-time reality of all events weaved in. The process tasks including state machines stay simpler when all they do is process.

I love state machines. Wrote my first in 80 or 81 to fill a need and was told what it was later.
State machines make a lot of things possible in the event driven approach but they should not lock inputs (especially) inside of logic structure braces unless you like to write lots of extra code and debug for fun.

Yes - I smplified things a lot.

You need a seperate event handler to detect such things as button presses with debounce. This can either be invoked inside each state or by setting flags in the main loop... Either works.

And of course a state can ignore irrelevant inputs.

regards

Allan

My button library objects return a 3 bit status showing states current, previous and bounce. Sketches that use them don't need to keep previous state and compare to current, they just look for a value.

Ultrasonic sensor returns a distance. With piezo touch you can get a value for how hard it was touched. Some kinds of cap sense can return a very loose value for closeness. Gear tooth counter returns count or rate.

But IR block, IR reflect and PIR can work fine returning a flag as can cap sense and piezo as button. The IR sensor handlers could return status that reflects time in ways too.

If I use a dedicated handler for a contact button and decide to change to cap sense, I should be able to change that one handler as long as the status means the same basic thing and not need to find and change any lines in the rest of the sketch.
If I want to add other kinds of button-type inputs, I add those to the sketch without needing to see where calls have to fit in existing logic though I may need to add status checks for the new inputs into one or more other tasks.

Overall I can use and reuse Input and Output code with minimal fitting in to any sketch.

You begin to need moar code the moment you put these things inside of braces of logic to do other things. More elements being logicked at once, the increase in complexity goes up by something like elements times conditions, only worse.

Hi all,

I simplified the code a little and tried to apply the suggestions provided.
I also reviewed the way I calculate the timers for the code.
I am stuck in a problem: I put some new log text to be read in the monitor and understand where the code is entering.
It seems that, once the code enters in the moveServo() it just stays there.
It should recall the stopServo(), but never enters there.

Well, other things seem to be better now: the distance is read constantly, the movement part is a lot easier (I use a variable to decide the direction) but am stuck in a running servo…
bye!

``````#include <Servo.h>
#include <NewPing.h>
Servo myservo;
#define TRIGGER_PIN  7  // Arduino pin tied to trigger pin on the ultrasonic sensor.
#define ECHO_PIN     7  // Arduino pin tied to echo pin on the ultrasonic sensor.
#define MAX_DISTANCE 300 // Maximum distance we want to ping for (in centimeters). Maximum sensor distance is rated at 400-500cm.
NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE);
int distance = 0;
int buttonInput = 2;
int sensorpin = 7;                                                 // Pin for SRF05
int buttonState = 0;
int buttonHIGH = 0;
int whatDirection = 1500;
boolean startRunning = false;
boolean runningUp = false;
boolean runningDown = false;
boolean runs = false;
boolean bottonePremuto = false;
unsigned long currentMillis = 0;
const unsigned long moveInterval = 100;
const unsigned long distInterval = 100;
const unsigned long stopInterval = 500;
unsigned long previousMoveMillis = 0;
unsigned long previousDistanceMillis = 0;
unsigned long previousStopMillis = 0;

void setup()
{
Serial.begin(9600);
myservo.attach(9);    //keep the servo stopped
if (millis() - previousMoveMillis >= moveInterval) {
previousMoveMillis = millis();
previousDistanceMillis = millis();
previousStopMillis = millis();
}
}

void loop()
{
readDistance();
buttonState = digitalRead(buttonInput);
currentMillis = millis();
if ((buttonState == HIGH) && (bottonePremuto == false)) {
bottonePremuto = true;
}
if ((bottonePremuto == true) && (startRunning == false)) {
if ((distance >= 28) && (bottonePremuto == true)) {
Serial.println("DECIDED TO GO UP");
whatDirection = 1200;
runningUp = true;
startRunning = true;
}
else {
Serial.println("DECIDED TO GO DOWN");
whatDirection = 1800;
runningDown = true;
startRunning = true;
}
moveServo();
}
}
void readDistance() {
if (millis() - previousDistanceMillis >= distInterval) {
Serial.print("Ping: ");
Serial.print(sonar.ping_cm()); // Send ping, get distance in cm and print result (0 = outside set distance range)
currentMillis = millis();
Serial.println("cm");
distance = sonar.ping_cm();
previousDistanceMillis += distInterval;
}
}

void moveServo() {
if ((millis() - previousMoveMillis >= moveInterval) && (startRunning == true)) {
myservo.writeMicroseconds(whatDirection);
}
previousMoveMillis += moveInterval;
if (millis() - previousStopMillis >= stopInterval) {
stopServo();
}
}
void stopServo() {
if (millis() - previousStopMillis >= stopInterval) {
if ((startRunning == true) && (runningUp == true) && (distance < 7)) {
myservo.writeMicroseconds(1500);
Serial.println("Stop UP");
whatDirection = 1500;
runningUp = false;
}
Serial.println("SHOULDSTOPPPPP");
previousStopMillis += stopInterval;
}
}
``````

Keep your design open-ended.
Allow room for a door opener/closer (servo) and lights / floor indicators.
They don't have to be 'real' but they will add an incredible amount of realism for the kids playing!
The mechanicals are pretty simple if you design it well.