Board index Roller Coaster Games NoLimits Coaster 2 Building a control panel for NoLimits 2

Building a control panel for NoLimits 2

All discussion relation to NoLimits Coaster 2 should be posted here. Purchase NoLimits Coaster from this page: nolimits-coaster-2-purchase-and-upgrade-links-t32524.html

Post August 20th, 2021, 9:21 pm

Posts: 197
Points on hand: 4,311.00 Points
I've recently started building a physical control panel for NoLimits 2, using the same Allen-Bradley 800T buttons that are used on real coaster control panels. I'm buying them used from eBay, as they're VERY expensive new. I decided to go with 8 controls, this is more than enough for all the station controls, including two Dispatch buttons that must be pressed simultaneously. The last of the controls and the enclosure just arrived today, this is what it looks like so far:

Image

The Control Power switch came with its legend plate (got lucky there!) and the legend plates for the rest are on their way. These are all the controls and how I plan for them to work, from left to right and top to bottom:

Top row:

CONTROL POWER OFF/ON: 2-position selector switch. On switches the current coaster in NL2 to Manual dispatch, and enables all the green and amber buttons. Off switches the current coaster to Auto dispatch, and disables all the green and amber buttons. Should really be a keyswitch, but I didn't want to bother with a key.

TROUBLE/RESET: Red guarded illuminated pushbutton. Flashes when any yellow messages appear in NL2's messages panel, illuminates when any red messages appear (i.e. coaster crash). Pressing it activates the Reset function on NL2's ingame control panel.

SEATS/FLOOR RAISE/LOWER: Amber guarded illuminated pushbutton. Active only for Flying, Floorless, and Floorless Dive coasters. Flashes when seats/floor are in dispatch position and can be moved to loading position, illuminates when seats/floor are in loading position.

EMERGENCY STOP: Red mushroom-head push-pull illuminated button. Push to activate NL2's E-stop function, pull to disable it. Illuminates when activated.

Bottom row:

DISPATCH: Green guarded illuminated pushbutton. Push both Dispatch buttons to dispatch train. Flashes when train can be dispatched, illuminates when a train is moving in the station.

GATES OPEN/CLOSE
: Amber guarded illuminated pushbutton. Press to open and close gates. Flashes when gates are closed and can be opened, illuminates when gates are open.

RESTRAINTS LOCK/UNLOCK: Amber guarded illuminated pushbutton. Press to open and close restraints. Flashes when restraints are closed and can be opened, illuminates when restraints are open.

DISPATCH: Same as other Dispatch button. They'll be wired in series so that both need to be pressed together to dispatch the train.

Now, the hard part is the wiring and programming. This part I haven't really started yet, I'm still in the research stage. I have very little experience with this, but from what I've read from other people who have made these, an Arduino Leonardo is the way to go. I'm hoping to use NL2's Telemetry Server to keep the state of the panel synced to NL2, but if that turns out to be unfeasible, I can always just make it send keystrokes. The most problematic control to implement will likely be the Trouble/Reset button, as there seems to be no way to activate the Reset function through either a keybind or telemetry. I'm definitely looking for any suggestions or recommendations from anyone experienced in Arduino programming, particularly in how to make it send and receive TCP/IP messages over USB to the computer to which it's connected.

Oh, and as for the button lighting, I'm using these in place of the 24v incandescent bulbs included with the buttons. The 6v versions will be able to run off the 5v power provided by an Arduino.

Post August 22nd, 2021, 4:10 pm

Posts: 197
Points on hand: 4,311.00 Points
Update:

Preliminary wiring is done. This is all the wires that won't actually connect to the Arduino, it and the jumper wires that connect to it are arriving tomorrow. Most of it is the ground wiring, which simply connects one terminal for each light (light terminals are facing the center) and one terminal for each button ( button terminals are facing the top and bottom) all together. Then I just need to connect one of these connected terminals to a ground pin on the Arduino. Will probably do two, so a ground wire breaking won't break everything. I've also connected the Dispatch buttons (bottom left and right) and their lights so that they work together, and connected the light for the E-stop (top left) to its NO (normally open) terminal so that it'll turn on when the E-stop is pressed. For the buttons, I'm using the NC (normally closed) terminal for the E-stop and the NO (normally open) terminals for everything else to actually send data to the Arduino. All the other lights will be controlled by the Arduino.

Image

And here's the code. This just uses keystrokes to control NL2 and internally keeps track of the state of the station, I have no idea (yet!) how to make an Arduino use NL2's telemetry server. The Control Power switch enables and disables the Arduino's "keyboard", and all the controls except Reset and E-stop are disabled if the Control Power is off or the E-stop is pressed. It starts out in "normal" mode, pressing the Seats/Floor button switches it to Flying/Floorless mode, enabling control of the seats or floor. The reset button (red guarded) sets the station state back to default, including disabling Flying/Floorless mode, and will give you a trouble light if you press it with the E-stop pressed as it sets the E-stop to off. There's a 6 second delay after moving the seats/floor to allow them to move, and a 15 second delay after dispatching the train to allow it to move out and the next train to move in. This is necessary without access to the telemetry server to determine the actual state of the station. The code compiles successfully, but I can't yet test it on an Arduino because it's not here yet!

#include <Keyboard.h>

const byte KEYPAD_1 = 225;
const byte KEYPAD_2 = 226;
const byte KEYPAD_3 = 227;
const byte KEYPAD_4 = 228;
const byte KEYPAD_6 = 230;
const byte KEYPAD_7 = 231;
const byte KEYPAD_8 = 232;
const byte KEYPAD_9 = 233;
const byte KEYPAD_ENTER = 224;
const int PowerSwitchPin = 1;
const int ResetButtonPin = 2;
const int SeatsFloorButtonPin = 3;
const int EmergencyStopPin = 4;
const int GatesButtonPin = 5;
const int RestraintsButtonPin = 6;
const int DispatchButtonPin = 7;
const int TroubleLightPin = 8;
const int SeatsFloorLightPin = 9;
const int GatesLightPin = 10;
const int RestraintsLightPin = 11;
const int DispatchLightPin = 12;
int eStopState;
bool keyboardState = false;
bool isFloorlessOrFlying = false;
bool seatsFloorDispatch = false;
bool restraintsClosed = true;
bool gatesClosed = true;
unsigned long previousMillis = 0;
const long interval = 500;
int ledState = LOW;

void setup() {
  // put your setup code here, to run once:
  pinMode(PowerSwitchPin, INPUT_PULLUP);
  pinMode(ResetButtonPin, INPUT_PULLUP);
  pinMode(SeatsFloorButtonPin, INPUT_PULLUP);
  pinMode(EmergencyStopPin, INPUT_PULLUP);
  pinMode(GatesButtonPin, INPUT_PULLUP);
  pinMode(RestraintsButtonPin, INPUT_PULLUP);
  pinMode(DispatchButtonPin, INPUT_PULLUP);
  pinMode(TroubleLightPin, OUTPUT);
  pinMode(SeatsFloorLightPin, OUTPUT);
  pinMode(GatesLightPin, OUTPUT);
  pinMode(RestraintsLightPin, OUTPUT);
  pinMode(DispatchLightPin, OUTPUT);
  eStopState = digitalRead(EmergencyStopPin);
}

void loop() {
  // put your main code here, to run repeatedly:
  unsigned long currentMillis = millis();
  if (digitalRead(PowerSwitchPin) == LOW && keyboardState == false) { //enable keyboard if power on
    Keyboard.begin();
    keyboardState = true;
  } 
  if (digitalRead(PowerSwitchPin) == LOW && digitalRead(EmergencyStopPin == LOW)) { // only process controls if power is on and E-stop is off
    if (digitalRead(SeatsFloorButtonPin) == LOW && (isFloorlessOrFlying = false)) { //enable floorless or flying if seats/floor button pressed
      isFloorlessOrFlying = true;
      if (canMoveSeatsFloor && (seatsFloorDispatch == false)) {
        digitalWrite(SeatsFloorLightPin, HIGH);
      }
    }
    if (digitalRead(SeatsFloorButtonPin) == LOW && isFloorlessOrFlying && canMoveSeatsFloor) { //read seats/floor button and check if usable
      if (seatsFloorDispatch) { //move seats/floor to loading position
         Keyboard.press(KEYPAD_4); //lower flyer seats
         Keyboard.releaseAll();
         Keyboard.press(KEYPAD_7); //raise floorless floor
         Keyboard.releaseAll();
         seatsFloorDispatch = false;
         digitalWrite(SeatsFloorLightPin, HIGH);
         delay(6000); //wait 6 seconds while seats/floor move
      }
      else { //move seats/floor to dispatch position
         Keyboard.press(KEYPAD_6); //raise flyer seats
         Keyboard.releaseAll();
         Keyboard.press(KEYPAD_1); //lower floorless floor
         Keyboard.releaseAll();
         digitalWrite(SeatsFloorLightPin, HIGH);
         delay(6000); //wait 6 seconds while seats/floor move
         seatsFloorDispatch = true;
         digitalWrite(SeatsFloorLightPin, LOW);
      }
    }
    if (digitalRead(GatesButtonPin) == LOW && canMoveGatesRestraints) //read gates button and check if usable
    {
      if (gatesClosed) { //open gates
        Keyboard.press(KEYPAD_9);
        Keyboard.releaseAll();
        gatesClosed = false;
        digitalWrite(GatesLightPin, HIGH);
      }
      else { //close gates
        Keyboard.press(KEYPAD_3);
        Keyboard.releaseAll();
        gatesClosed = true;
        digitalWrite(GatesLightPin, LOW);
      }
    }
    if (digitalRead(RestraintsButtonPin) == LOW && canMoveGatesRestraints) //read restraints button and check if usable
    {
      if (restraintsClosed) { //open restraints
        Keyboard.press(KEYPAD_8);
        Keyboard.releaseAll();
        restraintsClosed = false;
        digitalWrite(RestraintsLightPin, HIGH);
      }
      else { //close restraints
        Keyboard.press(KEYPAD_2);
        Keyboard.releaseAll();
        restraintsClosed = true;
        digitalWrite(RestraintsLightPin, LOW);
      }
    }
    if (digitalRead(DispatchButtonPin) == LOW && canDispatch){ //read dispatch button and check if usable
      Keyboard.press(KEYPAD_ENTER); //dispatch train
      Keyboard.releaseAll();
      digitalWrite(SeatsFloorLightPin, LOW);
      digitalWrite(GatesLightPin, LOW);
      digitalWrite(RestraintsLightPin, LOW);
      digitalWrite(DispatchLightPin, HIGH); //dispatch lights on, all others off
      delay(15000); //wait 15 seconds for trains to move
      digitalWrite(DispatchLightPin, LOW); //dispatch lights off
    }
    if (currentMillis - previousMillis >= interval) { // every half second, check if lights should blink
      previousMillis = currentMillis;
      if (ledState == LOW) {
        ledState = HIGH;
      } else {
        ledState = LOW;
      }
      if (canMoveSeatsFloor && isFloorlessOrFlying && (seatsFloorDispatch == false)) { //blink seats/floor light
        digitalWrite(SeatsFloorLightPin, ledState);
      }
      if (canMoveGatesRestraints && gatesClosed) { //blink gates light
        digitalWrite(GatesLightPin, ledState);
      }
      if (canMoveGatesRestraints && restraintsClosed) { //blink restraints light
        digitalWrite(RestraintsLightPin, ledState);
      }
      if (canDispatch) { //blink dispatch light
        digitalWrite(DispatchLightPin, ledState);
      }
    }
  }
  else {  // turn off all lights if power is off or E-stop is on
    digitalWrite(SeatsFloorLightPin, LOW);
    digitalWrite(GatesLightPin, LOW);
    digitalWrite(RestraintsLightPin, LOW);
    digitalWrite(DispatchLightPin, LOW);
  }
  if (digitalRead(EmergencyStopPin) != eStopState) { //Toggle E-stop
    Keyboard.press(201); //Press F8
    Keyboard.releaseAll();
    eStopState = digitalRead(EmergencyStopPin);
  }
  if (digitalRead(ResetButtonPin) == LOW) { //reset station state
    if (digitalRead(EmergencyStopPin) == HIGH) { //check if E-stop is pressed
      digitalWrite(TroubleLightPin, HIGH); // Trouble light if reset pressed with E-stop pressed
    }
    else {
      isFloorlessOrFlying = false;
      seatsFloorDispatch = false;
      restraintsClosed = true;
      gatesClosed = true;
      eStopState = LOW;
      digitalWrite(TroubleLightPin, LOW);
    }
  }
  if (digitalRead(PowerSwitchPin) == HIGH && keyboardState == true) { //disable keyboard if power off
    Keyboard.end();
    keyboardState = false;
  }
}

bool canMoveSeatsFloor() { //determine if flyer seats or floorless floor can be moved
  if (gatesClosed && restraintsClosed) {
    return true;
  }
  else {
    return false;
  }
}

bool canMoveGatesRestraints() { //determine if gates and restraints can be moved
  if (isFloorlessOrFlying == false || seatsFloorDispatch == false) {
    return true;   
  }
  else {
    return false;
  }
}

bool canDispatch() { //determine if train can be dispatched
  if ((isFloorlessOrFlying == false || seatsFloorDispatch == true) && gatesClosed && restraintsClosed) {
    return true;
  }
  else {
    return false;
  }
}

Post August 23rd, 2021, 3:04 am
gouldy User avatar
Premium Member
Premium Member

Posts: 7816
Points on hand: 860.00 Points
Bank: 25,088.00 Points
Location: WOLVERHAMPTON, England.

This is a very cool thing to have made! You'll have to do a video of it working.

Post August 23rd, 2021, 1:12 pm

Posts: 197
Points on hand: 4,311.00 Points
gouldy wrote:
This is a very cool thing to have made! You'll have to do a video of it working.

Yep, that's the plan. It actually won't be 100% complete until October, as the green LED bulbs for the dispatch buttons are backordered until then. The arduino, the wires for it, and the legend plates should all be arriving today or tomorrow.

Post August 24th, 2021, 2:02 pm

Posts: 197
Points on hand: 4,311.00 Points
Legend plates are here and installed! The panel looks fully LEGIT now, and is finished on the outside except for the LED bulbs, which are on their way. Here, I removed the E-stop's head so you can see all of them clearly:

Image

The legend plates are all custom, and I got them from here. This was the cheapest place I found that does custom ones.

The interior isn't any farther along yet, the Arduino and wires were supposed to get here yesterday but got mis-routed and should be here today or tomorrow. The LED bulbs should be here Saturday, not sure if the green ones will be in this shipment or if they're still backordered.

Post August 25th, 2021, 11:24 am

Posts: 197
Points on hand: 4,311.00 Points
The Arduino's here, and the wiring is finished.

Image

In addition to adding the LEDs, the only thing I need to do is install a panel-mount USB port into the enclosure. This will allow the cover to be closed while the Arduino is connected to USB. I'm going with this one. After that, all that's left to do is refine the code. It works, but not too well, and it's hard to see what's going on without the LEDs. A demo video will happen once it's all done with the LEDs installed, and the code refined and working to my satisfaction.

Post August 28th, 2021, 10:49 pm
Oscar User avatar
Founding Member
Founding Member

Posts: 14392
Points on hand: 9,519.60 Points
Bank: 187,052.60 Points
Location: California, USA

That looks really good! Thanks for posting this! :D
Support Us! - Click Here To Donate $5 Monthly!
Paradox wrote:
No need to tell Oscar about the problems. He is magic.

Post August 30th, 2021, 2:11 pm

Posts: 197
Points on hand: 4,311.00 Points
Let there be LIGHT!
Image
The LED bulbs just arrived today, all but the green ones for the Dispatch buttons, which are on backorder until October 4th. They look amazing and work perfectly. Here, it's running a simple test program to turn all of them on when the power switch is on.

All that's left hardware-wise (aside from installing the green LEDs when they arrive) is installing the USB pasthrough port (waiting on screws now, it didn't include them) and securing the Arduino to the inside of the enclosure. The rest is all software, the code still needs a lot of work.

Post September 1st, 2021, 9:03 pm

Posts: 197
Points on hand: 4,311.00 Points
Major update, the whole project is almost complete!

Installed the USB passthrough port! I can finally close the enclosure with the Arduino connected to USB!
Image

Extended the Arduino wiring, this will allow it to be secured to the inside of the enclosure.
Image

Securing the Arduino and installing the green LED bulbs for the Dispatch buttons are the final steps for completing it, in terms of hardware. The green LED bulbs were back in stock as of yesterday and should ship tomorrow.

And last but certainly not least, the code is done. It's not necessarily final, but everything works. It doesn't use NL2's telemetry server, that would basically be a whole separate project on its own.

#include <Keyboard.h>

const byte KEYPAD_1 = 225; //ASCII codes for numpad keys, to control gates, restraints, seats, floor, and dispatch.
const byte KEYPAD_2 = 226;
const byte KEYPAD_3 = 227;
const byte KEYPAD_4 = 228;
const byte KEYPAD_6 = 230;
const byte KEYPAD_7 = 231;
const byte KEYPAD_8 = 232;
const byte KEYPAD_9 = 233;
const byte KEYPAD_ENTER = 224;
const int PowerSwitchPin = 1;
const int ResetButtonPin = 2;
const int SeatsFloorButtonPin = 3;
const int EmergencyStopPin = 4;
const int GatesButtonPin = 5;
const int RestraintsButtonPin = 6;
const int DispatchButtonPin = 7;
const int TroubleLightPin = 8;
const int SeatsFloorLightPin = 9;
const int GatesLightPin = 10;
const int RestraintsLightPin = 11;
const int DispatchLightPin = 12;
const long DebounceDelay = 150;
const long GatesRestraintsDelay = 500; // Time needed for gates and restraints to move
const long SeatsFloorDelay = 6000; //Time needed for floorless floor and flyer seats to move
const long DispatchDelay = 15000; //Time needed for trains to move in and out of station
unsigned long GatesStartTime = 0;
unsigned long RestraintsStartTime = 0;
unsigned long DispatchStartTime = 0;
unsigned long SeatsFloorStartTime = 0; //Time left for things to move
unsigned long currentMillis;
int eStopState;
bool keyboardState = false;
bool isFloorlessOrFlying = false;
bool seatsFloorLoading = false;
bool seatsFloorDispatch = false;
bool restraintsOpen = false;
bool restraintsClosed = true;
bool gatesOpen = false;
bool gatesClosed = true;
bool canDispatch = true;
bool canMoveGatesRestraints = true;
bool canMoveSeatsFloor = false;
int seatsFloorMotionDirection = 0;
int gatesMotionDirection = 0;
int restraintsMotionDirection = 0; //These indicate which way gates/restraints/seats/floor are moving
bool trainParked = true;
unsigned long previousBlinkMillis = 0;
const long blinkInterval = 500; //Time between blinks of button lights
int ledState = LOW;

void setup() {
  // put your setup code here, to run once:
  pinMode(PowerSwitchPin, INPUT_PULLUP);
  pinMode(ResetButtonPin, INPUT_PULLUP);
  pinMode(SeatsFloorButtonPin, INPUT_PULLUP);
  pinMode(EmergencyStopPin, INPUT_PULLUP);
  pinMode(GatesButtonPin, INPUT_PULLUP);
  pinMode(RestraintsButtonPin, INPUT_PULLUP);
  pinMode(DispatchButtonPin, INPUT_PULLUP);
  pinMode(TroubleLightPin, OUTPUT);
  pinMode(SeatsFloorLightPin, OUTPUT);
  pinMode(GatesLightPin, OUTPUT);
  pinMode(RestraintsLightPin, OUTPUT);
  pinMode(DispatchLightPin, OUTPUT);
  eStopState = digitalRead(EmergencyStopPin);
}

void loop() {
  // put your main code here, to run repeatedly:
  currentMillis = millis();
  if (digitalRead(PowerSwitchPin) == LOW && keyboardState == false) { //enable keyboard if power on
    Keyboard.begin();
    keyboardState = true;
  }
  if (digitalRead(PowerSwitchPin) == LOW && digitalRead(EmergencyStopPin) == LOW) { // only process controls if power is on, E-stop is off
    if (trainParked) {
      processControls();
    }
    updateLights();
  }
  else {  // turn off all lights if power is off or E-stop is on
    lightsOut();
  }
  updateStates();
  if (digitalRead(EmergencyStopPin) != eStopState) { //Toggle E-stop
    Keyboard.press(201); //Press F8
    Keyboard.releaseAll();
    eStopState = digitalRead(EmergencyStopPin);
    delay(DebounceDelay);
  }
  if (digitalRead(ResetButtonPin) == LOW) { //reset station state
    reset();
  }
  if (digitalRead(PowerSwitchPin) == HIGH && keyboardState == true) { //disable keyboard if power off
    Keyboard.end();
    keyboardState = false;
  }
}

void processControls() {
  if (digitalRead(SeatsFloorButtonPin) == LOW && (isFloorlessOrFlying == false)) { //enable floorless or flying if seats/floor button pressed
    isFloorlessOrFlying = true;
    seatsFloorLoading = true;
    seatsFloorDispatch = false;
    delay(DebounceDelay);
  }
  if (digitalRead(SeatsFloorButtonPin) == LOW && canMoveSeatsFloor) { //read seats/floor button and check if usable
    moveSeatsFloor();
  }
  if (digitalRead(GatesButtonPin) == LOW && canMoveGatesRestraints) { //read gates button and check if usable
    moveGates();
  }
  if (digitalRead(RestraintsButtonPin) == LOW && canMoveGatesRestraints) { //read restraints button and check if usable
    moveRestraints();
  }
  if (digitalRead(DispatchButtonPin) == LOW && canDispatch) { //read dispatch button and check if usable
    dispatchTrain();
  }
}

void reset() { //reset station state
  if (digitalRead(EmergencyStopPin) == HIGH) { //check if E-stop is pressed
    digitalWrite(TroubleLightPin, HIGH); // Trouble light if reset pressed with E-stop pressed
  }
  else {
    isFloorlessOrFlying = false; //set all station state variables to default
    restraintsOpen = false;
    restraintsClosed = true;
    gatesOpen = false;
    gatesClosed = true;
    seatsFloorLoading = false;
    seatsFloorDispatch = false;
    eStopState = LOW;
    DispatchStartTime = 0;
    SeatsFloorStartTime = 0;
    GatesStartTime = 0;
    RestraintsStartTime = 0;
    seatsFloorMotionDirection = 0;
    gatesMotionDirection = 0;
    restraintsMotionDirection = 0;
    canDispatch = true;
    canMoveGatesRestraints = true;
    canMoveSeatsFloor = false;
    trainParked = true;
    digitalWrite(TroubleLightPin, LOW); //turn off trouble light if on
  }
  delay(DebounceDelay);
}

void lightsOut() { //turn off all lights
  digitalWrite(SeatsFloorLightPin, LOW);
  digitalWrite(GatesLightPin, LOW);
  digitalWrite(RestraintsLightPin, LOW);
  digitalWrite(DispatchLightPin, LOW);
}

void moveSeatsFloor() {
  if (seatsFloorDispatch) { //move seats/floor to loading position
    Keyboard.press(KEYPAD_4); //lower flyer seats
    Keyboard.press(KEYPAD_7); //raise floorless floor
    Keyboard.releaseAll();
    seatsFloorDispatch = false;
    seatsFloorMotionDirection = -1; //-1 = moving from dispatch position to loading position
    SeatsFloorStartTime = currentMillis; //start motion timer
  }
  if (seatsFloorLoading) { //move seats/floor to dispatch position
    Keyboard.press(KEYPAD_6); //raise flyer seats
    Keyboard.press(KEYPAD_1); //lower floorless floor
    Keyboard.releaseAll();
    seatsFloorLoading = false;
    seatsFloorMotionDirection = 1; //1 = moving from loading position to dispatch position
    SeatsFloorStartTime = currentMillis; //start motion timer
  }

}

void moveGates() {
  if (gatesClosed) { //open gates
    Keyboard.press(KEYPAD_9);
    Keyboard.releaseAll();
    gatesClosed = false;
    gatesMotionDirection = -1; //-1 = moving from dispatch position to loading position
    GatesStartTime = currentMillis; //start motion timer
  }
  if (gatesOpen) { //close gates
    Keyboard.press(KEYPAD_3);
    Keyboard.releaseAll();
    gatesOpen = false;
    gatesMotionDirection = 1; //1 = moving from loading position to dispatch position
    GatesStartTime = currentMillis; //start motion timer
  }
}

void moveRestraints() {
  if (restraintsClosed) { //open restraints
    Keyboard.press(KEYPAD_8);
    Keyboard.releaseAll();
    restraintsClosed = false;
    restraintsMotionDirection = -1; //-1 = moving from dispatch position to loading position
    RestraintsStartTime = currentMillis; //start motion timer
  }
  if (restraintsOpen) { //close restraints
    Keyboard.press(KEYPAD_2);
    Keyboard.releaseAll();
    restraintsOpen = false;
    restraintsMotionDirection = 1; //1 = moving from loading position to dispatch position
    RestraintsStartTime = currentMillis; //start motion timer
  }
}

void dispatchTrain() { //dispatch train and start timer
  Keyboard.press(KEYPAD_ENTER); //dispatch train
  Keyboard.releaseAll();
  trainParked = false;
  DispatchStartTime = currentMillis; //start motion timer
}

void updateLights() {
  if (currentMillis - previousBlinkMillis >= blinkInterval) { // every half second, check if lights should blink
    previousBlinkMillis = currentMillis;
    if (ledState == LOW) {
      ledState = HIGH;
    } else {
      ledState = LOW;
    }
    if (trainParked) { // only update lights if train parked in station
      if (isFloorlessOrFlying) {
        if (seatsFloorDispatch && canMoveSeatsFloor) {
          digitalWrite(SeatsFloorLightPin, ledState); //blink if in dispatch position and movable
        }
        if (seatsFloorMotionDirection != 0 || (canMoveSeatsFloor && seatsFloorLoading)) {
          digitalWrite(SeatsFloorLightPin, HIGH); //seats/floor light on if moving or in loading position and movable
        }
        if (seatsFloorMotionDirection == 0 && canMoveSeatsFloor == false) {
          digitalWrite(SeatsFloorLightPin, LOW); //seats/floor light off if inactive
        }
      } else {
        digitalWrite(SeatsFloorLightPin, LOW); //off if coaster not floorless or flying
      }
      if (canMoveGatesRestraints) { //check gates and restrtaints state
        if (gatesClosed) {
          digitalWrite(GatesLightPin, ledState); //blink if gates closed
        } else {
          digitalWrite(GatesLightPin, HIGH); //on if gates open
        }
        if (restraintsClosed) {
          digitalWrite(RestraintsLightPin, ledState); //blink if restraints closed
        } else {
          digitalWrite(RestraintsLightPin, HIGH); //on if restraints open
        }

      } else {
        digitalWrite(GatesLightPin, LOW); // gates light off if unavailable
        digitalWrite(RestraintsLightPin, LOW); //restraints light off if unavailable
      }
      if (canDispatch) { //blink dispatch light
        digitalWrite(DispatchLightPin, ledState);
      } else {
        digitalWrite(DispatchLightPin, LOW);
      }
    }
    else { // only dispatch lights on if train moving
      digitalWrite(SeatsFloorLightPin, LOW);
      digitalWrite(GatesLightPin, LOW);
      digitalWrite(RestraintsLightPin, LOW);
      digitalWrite(DispatchLightPin, HIGH);
    }
  }
}

void updateStates() {
  if ((isFloorlessOrFlying == false || seatsFloorDispatch) && gatesClosed && restraintsClosed && trainParked) {
    canDispatch = true;
  } else { //determine if train can be dispatched
    canDispatch = false;
  }
  if ((isFloorlessOrFlying == false || seatsFloorLoading) && trainParked) {
    canMoveGatesRestraints = true;
  } else { //determine if gates and restraints can be moved
    canMoveGatesRestraints = false;
  }
  if (isFloorlessOrFlying && gatesClosed && restraintsClosed && trainParked && (seatsFloorLoading || seatsFloorDispatch)) {
    canMoveSeatsFloor = true;
  } else { //determine if seats/floor can be moved
    canMoveSeatsFloor = false;
  }
  if ((currentMillis - GatesStartTime >= GatesRestraintsDelay) || GatesStartTime == 0) {
    if (gatesMotionDirection == -1) { // Check if gates are opening
      gatesMotionDirection = 0;
      gatesOpen = true;
    }
    if (gatesMotionDirection == 1) { //check if gates are closing
      gatesMotionDirection = 0;
      gatesClosed = true;
    }
  }
  if ((currentMillis - RestraintsStartTime >= GatesRestraintsDelay) || RestraintsStartTime == 0) {
    if (restraintsMotionDirection == -1) { // Check if restraints are opening
      restraintsMotionDirection = 0;
      restraintsOpen = true;
    }
    if (restraintsMotionDirection == 1) { //check if restraints are closing
      restraintsMotionDirection = 0;
      restraintsClosed = true;
    }
  }
  if ((currentMillis - SeatsFloorStartTime >= SeatsFloorDelay) || SeatsFloorStartTime == 0) {
    if (seatsFloorMotionDirection == -1) { //check if seats/floor moving to loading position
      seatsFloorMotionDirection = 0;
      seatsFloorLoading = true;
    }
    if (seatsFloorMotionDirection == 1) { //check if seats/floor moving to dispatch position
      seatsFloorMotionDirection = 0;
      seatsFloorDispatch = true;
    }
  }
  if ((currentMillis - DispatchStartTime >= DispatchDelay) || DispatchStartTime == 0) {
    trainParked = true;
  }
}

Post September 9th, 2021, 9:15 pm

Posts: 197
Points on hand: 4,311.00 Points
IT'S DONE! The bulbs for the Dispatch buttons just arrived today! They're WAY brighter than the others!
Image

I also have made a few small changes to the code and wiring, in order to control the E-stop button's light in software. It's now connected to pin 0, and this is the current code:

#include <Keyboard.h>

const byte KEYPAD_1 = 225; //ASCII codes for numpad keys, to control gates, restraints, seats, floor, and dispatch.
const byte KEYPAD_2 = 226;
const byte KEYPAD_3 = 227;
const byte KEYPAD_4 = 228;
const byte KEYPAD_6 = 230;
const byte KEYPAD_7 = 231;
const byte KEYPAD_8 = 232;
const byte KEYPAD_9 = 233;
const byte KEYPAD_ENTER = 224;
const int EStopLightPin = 0;
const int PowerSwitchPin = 1;
const int ResetButtonPin = 2;
const int SeatsFloorButtonPin = 3;
const int EmergencyStopPin = 4;
const int GatesButtonPin = 5;
const int RestraintsButtonPin = 6;
const int DispatchButtonPin = 7;
const int TroubleLightPin = 8;
const int SeatsFloorLightPin = 9;
const int GatesLightPin = 10;
const int RestraintsLightPin = 11;
const int DispatchLightPin = 12;
const long DebounceDelay = 150;
const long GatesRestraintsDelay = 500; // Time needed for gates and restraints to move
const long SeatsFloorDelay = 6000; //Time needed for floorless floor and flyer seats to move
const long DispatchDelay = 15000; //Time needed for trains to move in and out of station
unsigned long GatesStartTime = 0;
unsigned long RestraintsStartTime = 0;
unsigned long DispatchStartTime = 0;
unsigned long SeatsFloorStartTime = 0; //Time left for things to move
unsigned long currentMillis;
int eStopState;
bool keyboardState = false;
bool isFloorlessOrFlying = false;
bool seatsFloorLoading = false;
bool seatsFloorDispatch = false;
bool restraintsOpen = false;
bool restraintsClosed = true;
bool gatesOpen = false;
bool gatesClosed = true;
bool canDispatch = true;
bool canMoveGatesRestraints = true;
bool canMoveSeatsFloor = false;
int seatsFloorMotionDirection = 0;
int gatesMotionDirection = 0;
int restraintsMotionDirection = 0; //These indicate which way gates/restraints/seats/floor are moving
bool trainParked = true;
unsigned long previousBlinkMillis = 0;
const long blinkInterval = 500; //Time between blinks of button lights
int ledState = LOW;

void setup() {
  // put your setup code here, to run once:
  pinMode(EStopLightPin, OUTPUT);
  pinMode(PowerSwitchPin, INPUT_PULLUP);
  pinMode(ResetButtonPin, INPUT_PULLUP);
  pinMode(SeatsFloorButtonPin, INPUT_PULLUP);
  pinMode(EmergencyStopPin, INPUT_PULLUP);
  pinMode(GatesButtonPin, INPUT_PULLUP);
  pinMode(RestraintsButtonPin, INPUT_PULLUP);
  pinMode(DispatchButtonPin, INPUT_PULLUP);
  pinMode(TroubleLightPin, OUTPUT);
  pinMode(SeatsFloorLightPin, OUTPUT);
  pinMode(GatesLightPin, OUTPUT);
  pinMode(RestraintsLightPin, OUTPUT);
  pinMode(DispatchLightPin, OUTPUT);
  eStopState = digitalRead(EmergencyStopPin);
  digitalWrite(EStopLightPin, eStopState);
}

void loop() {
  // put your main code here, to run repeatedly:
  currentMillis = millis();
  if (digitalRead(PowerSwitchPin) == LOW && keyboardState == false) { //enable keyboard if power on
    Keyboard.begin();
    keyboardState = true;
  }
  if (digitalRead(PowerSwitchPin) == LOW && digitalRead(EmergencyStopPin) == LOW) { // only process controls if power is on, E-stop is off
    if (trainParked) {
      processControls();
    }
    updateLights();
  }
  else {  // turn off all lights if power is off or E-stop is on
    lightsOut();
  }
  updateStates();
  if (digitalRead(EmergencyStopPin) != eStopState) { //Toggle E-stop
    Keyboard.press(201); //Press F8
    Keyboard.releaseAll();
    eStopState = digitalRead(EmergencyStopPin);
    digitalWrite(EStopLightPin, eStopState);
    delay(DebounceDelay);
  }
  if (digitalRead(ResetButtonPin) == LOW) { //reset station state
    reset();
  }
  if (digitalRead(PowerSwitchPin) == HIGH && keyboardState == true) { //disable keyboard if power off
    Keyboard.end();
    keyboardState = false;
  }
}

void processControls() {
  if (digitalRead(SeatsFloorButtonPin) == LOW && (isFloorlessOrFlying == false)) { //enable floorless or flying if seats/floor button pressed
    isFloorlessOrFlying = true;
    seatsFloorLoading = true;
    seatsFloorDispatch = false;
    delay(DebounceDelay);
  }
  if (digitalRead(SeatsFloorButtonPin) == LOW && canMoveSeatsFloor) { //read seats/floor button and check if usable
    moveSeatsFloor();
  }
  if (digitalRead(GatesButtonPin) == LOW && canMoveGatesRestraints) { //read gates button and check if usable
    moveGates();
  }
  if (digitalRead(RestraintsButtonPin) == LOW && canMoveGatesRestraints) { //read restraints button and check if usable
    moveRestraints();
  }
  if (digitalRead(DispatchButtonPin) == LOW && canDispatch) { //read dispatch button and check if usable
    dispatchTrain();
  }
}

void reset() { //reset station state
  if (digitalRead(EmergencyStopPin) == HIGH) { //check if E-stop is pressed
    digitalWrite(TroubleLightPin, HIGH); // Trouble light if reset pressed with E-stop pressed
  }
  else {
    isFloorlessOrFlying = false; //set all station state variables to default
    restraintsOpen = false;
    restraintsClosed = true;
    gatesOpen = false;
    gatesClosed = true;
    seatsFloorLoading = false;
    seatsFloorDispatch = false;
    eStopState = LOW;
    DispatchStartTime = 0;
    SeatsFloorStartTime = 0;
    GatesStartTime = 0;
    RestraintsStartTime = 0;
    seatsFloorMotionDirection = 0;
    gatesMotionDirection = 0;
    restraintsMotionDirection = 0;
    canDispatch = true;
    canMoveGatesRestraints = true;
    canMoveSeatsFloor = false;
    trainParked = true;
    digitalWrite(TroubleLightPin, LOW); //turn off trouble light if on
  }
  delay(DebounceDelay);
}

void lightsOut() { //turn off all lights
  digitalWrite(SeatsFloorLightPin, LOW);
  digitalWrite(GatesLightPin, LOW);
  digitalWrite(RestraintsLightPin, LOW);
  digitalWrite(DispatchLightPin, LOW);
}

void moveSeatsFloor() {
  if (seatsFloorDispatch) { //move seats/floor to loading position
    Keyboard.press(KEYPAD_4); //lower flyer seats
    Keyboard.press(KEYPAD_7); //raise floorless floor
    Keyboard.releaseAll();
    seatsFloorDispatch = false;
    seatsFloorMotionDirection = -1; //-1 = moving from dispatch position to loading position
    SeatsFloorStartTime = currentMillis; //start motion timer
  }
  if (seatsFloorLoading) { //move seats/floor to dispatch position
    Keyboard.press(KEYPAD_6); //raise flyer seats
    Keyboard.press(KEYPAD_1); //lower floorless floor
    Keyboard.releaseAll();
    seatsFloorLoading = false;
    seatsFloorMotionDirection = 1; //1 = moving from loading position to dispatch position
    SeatsFloorStartTime = currentMillis; //start motion timer
  }

}

void moveGates() {
  if (gatesClosed) { //open gates
    Keyboard.press(KEYPAD_9);
    Keyboard.releaseAll();
    gatesClosed = false;
    gatesMotionDirection = -1; //-1 = moving from dispatch position to loading position
    GatesStartTime = currentMillis; //start motion timer
  }
  if (gatesOpen) { //close gates
    Keyboard.press(KEYPAD_3);
    Keyboard.releaseAll();
    gatesOpen = false;
    gatesMotionDirection = 1; //1 = moving from loading position to dispatch position
    GatesStartTime = currentMillis; //start motion timer
  }
}

void moveRestraints() {
  if (restraintsClosed) { //open restraints
    Keyboard.press(KEYPAD_8);
    Keyboard.releaseAll();
    restraintsClosed = false;
    restraintsMotionDirection = -1; //-1 = moving from dispatch position to loading position
    RestraintsStartTime = currentMillis; //start motion timer
  }
  if (restraintsOpen) { //close restraints
    Keyboard.press(KEYPAD_2);
    Keyboard.releaseAll();
    restraintsOpen = false;
    restraintsMotionDirection = 1; //1 = moving from loading position to dispatch position
    RestraintsStartTime = currentMillis; //start motion timer
  }
}

void dispatchTrain() { //dispatch train and start timer
  Keyboard.press(KEYPAD_ENTER); //dispatch train
  Keyboard.releaseAll();
  trainParked = false;
  DispatchStartTime = currentMillis; //start motion timer
}

void updateLights() {
  if (currentMillis - previousBlinkMillis >= blinkInterval) { // every half second, check if lights should blink
    previousBlinkMillis = currentMillis;
    if (ledState == LOW) {
      ledState = HIGH;
    } else {
      ledState = LOW;
    }
    if (trainParked) { // only update lights if train parked in station
      if (isFloorlessOrFlying) {
        if (seatsFloorDispatch && canMoveSeatsFloor) {
          digitalWrite(SeatsFloorLightPin, ledState); //blink if in dispatch position and movable
        }
        if (seatsFloorMotionDirection != 0 || (canMoveSeatsFloor && seatsFloorLoading)) {
          digitalWrite(SeatsFloorLightPin, HIGH); //seats/floor light on if moving or in loading position and movable
        }
        if (seatsFloorMotionDirection == 0 && canMoveSeatsFloor == false) {
          digitalWrite(SeatsFloorLightPin, LOW); //seats/floor light off if inactive
        }
      } else {
        digitalWrite(SeatsFloorLightPin, LOW); //off if coaster not floorless or flying
      }
      if (canMoveGatesRestraints) { //check gates and restrtaints state
        if (gatesClosed) {
          digitalWrite(GatesLightPin, ledState); //blink if gates closed
        } else {
          digitalWrite(GatesLightPin, HIGH); //on if gates open
        }
        if (restraintsClosed) {
          digitalWrite(RestraintsLightPin, ledState); //blink if restraints closed
        } else {
          digitalWrite(RestraintsLightPin, HIGH); //on if restraints open
        }

      } else {
        digitalWrite(GatesLightPin, LOW); // gates light off if unavailable
        digitalWrite(RestraintsLightPin, LOW); //restraints light off if unavailable
      }
      if (canDispatch) { //blink dispatch light
        digitalWrite(DispatchLightPin, ledState);
      } else {
        digitalWrite(DispatchLightPin, LOW);
      }
    }
    else { // only dispatch lights on if train moving
      digitalWrite(SeatsFloorLightPin, LOW);
      digitalWrite(GatesLightPin, LOW);
      digitalWrite(RestraintsLightPin, LOW);
      digitalWrite(DispatchLightPin, HIGH);
    }
  }
}

void updateStates() {
  if ((isFloorlessOrFlying == false || seatsFloorDispatch) && gatesClosed && restraintsClosed && trainParked) {
    canDispatch = true;
  } else { //determine if train can be dispatched
    canDispatch = false;
  }
  if ((isFloorlessOrFlying == false || seatsFloorLoading) && trainParked) {
    canMoveGatesRestraints = true;
  } else { //determine if gates and restraints can be moved
    canMoveGatesRestraints = false;
  }
  if (isFloorlessOrFlying && gatesClosed && restraintsClosed && trainParked && (seatsFloorLoading || seatsFloorDispatch)) {
    canMoveSeatsFloor = true;
  } else { //determine if seats/floor can be moved
    canMoveSeatsFloor = false;
  }
  if ((currentMillis - GatesStartTime >= GatesRestraintsDelay) || GatesStartTime == 0) {
    if (gatesMotionDirection == -1) { // Check if gates are opening
      gatesMotionDirection = 0;
      gatesOpen = true;
    }
    if (gatesMotionDirection == 1) { //check if gates are closing
      gatesMotionDirection = 0;
      gatesClosed = true;
    }
  }
  if ((currentMillis - RestraintsStartTime >= GatesRestraintsDelay) || RestraintsStartTime == 0) {
    if (restraintsMotionDirection == -1) { // Check if restraints are opening
      restraintsMotionDirection = 0;
      restraintsOpen = true;
    }
    if (restraintsMotionDirection == 1) { //check if restraints are closing
      restraintsMotionDirection = 0;
      restraintsClosed = true;
    }
  }
  if ((currentMillis - SeatsFloorStartTime >= SeatsFloorDelay) || SeatsFloorStartTime == 0) {
    if (seatsFloorMotionDirection == -1) { //check if seats/floor moving to loading position
      seatsFloorMotionDirection = 0;
      seatsFloorLoading = true;
    }
    if (seatsFloorMotionDirection == 1) { //check if seats/floor moving to dispatch position
      seatsFloorMotionDirection = 0;
      seatsFloorDispatch = true;
    }
  }
  if ((currentMillis - DispatchStartTime >= DispatchDelay) || DispatchStartTime == 0) {
    trainParked = true;
  }
}


I'll be doing a demo video, but not sure when.

Post September 11th, 2021, 9:56 pm

Posts: 197
Points on hand: 4,311.00 Points
Added a new feature to the code: The trouble light now illuminates whenever you press a button at an inappropriate time. This will be caused by trying to dispatch with the gates or restraints open, or the seats/floor in loading position, trying to move the floor to dispatch position with the gates or restraints open, trying to open the gates or restraints with the seats/floor in dispatch position, or trying to do anything (other than E-stop and reset) while there isn't a train parked in the station. In all of these cases, it will only illuminate as long as the inappropriate button is being held down, it will turn off once it's released, with no reset required or any other consequences. It still works mostly the same with the E-stop, it'll turn on and stay on if you try to reset while E-stopped, but will now turn off once the E-stop is pulled out, rather than requiring a reset.

The current code:
#include <Keyboard.h>

const byte KEYPAD_1 = 225; //ASCII codes for numpad keys, to control gates, restraints, seats, floor, and dispatch.
const byte KEYPAD_2 = 226;
const byte KEYPAD_3 = 227;
const byte KEYPAD_4 = 228;
const byte KEYPAD_6 = 230;
const byte KEYPAD_7 = 231;
const byte KEYPAD_8 = 232;
const byte KEYPAD_9 = 233;
const byte KEYPAD_ENTER = 224;
const int EStopLightPin = 0;
const int PowerSwitchPin = 1;
const int ResetButtonPin = 2;
const int SeatsFloorButtonPin = 3;
const int EmergencyStopPin = 4;
const int GatesButtonPin = 5;
const int RestraintsButtonPin = 6;
const int DispatchButtonPin = 7;
const int TroubleLightPin = 8;
const int SeatsFloorLightPin = 9;
const int GatesLightPin = 10;
const int RestraintsLightPin = 11;
const int DispatchLightPin = 12;
const long DebounceDelay = 150;
const long GatesRestraintsDelay = 1000; // Time needed for gates and restraints to move
const long SeatsFloorDelay = 6000; //Time needed for floorless floor and flyer seats to move
const long DispatchDelay = 15000; //Time needed for trains to move in and out of station
unsigned long GatesStartTime = 0;
unsigned long RestraintsStartTime = 0;
unsigned long DispatchStartTime = 0;
unsigned long SeatsFloorStartTime = 0; //Time left for things to move
unsigned long currentMillis;
int eStopState; //track whether E-stop is pressed
int eStopError = 0;
int seatsFloorError = 0;
int restraintsError = 0;
int gatesError = 0;
int dispatchError = 0; //track when buttons are pressed at wrong time
bool keyboardState = false; //track whether virtual keyboard is active
bool isFloorlessOrFlying = false;
bool seatsFloorLoading = true;
bool seatsFloorDispatch = false;
bool restraintsOpen = false;
bool restraintsClosed = true;
bool gatesOpen = false;
bool gatesClosed = true;
bool canDispatch = true;
bool canMoveGatesRestraints = true;
bool canMoveSeatsFloor = false; //Track state of coaster
int seatsFloorMotionDirection = 0;
int gatesMotionDirection = 0;
int restraintsMotionDirection = 0; //These indicate which way gates/restraints/seats/floor are moving
bool trainParked = true;
unsigned long previousBlinkMillis = 0;
const long blinkInterval = 500; //Time between blinks of button lights
int ledState = LOW; //State for blinking LEDs

void setup() {
  // put your setup code here, to run once:
  pinMode(EStopLightPin, OUTPUT);
  pinMode(PowerSwitchPin, INPUT_PULLUP);
  pinMode(ResetButtonPin, INPUT_PULLUP);
  pinMode(SeatsFloorButtonPin, INPUT_PULLUP);
  pinMode(EmergencyStopPin, INPUT_PULLUP);
  pinMode(GatesButtonPin, INPUT_PULLUP);
  pinMode(RestraintsButtonPin, INPUT_PULLUP);
  pinMode(DispatchButtonPin, INPUT_PULLUP);
  pinMode(TroubleLightPin, OUTPUT);
  pinMode(SeatsFloorLightPin, OUTPUT);
  pinMode(GatesLightPin, OUTPUT);
  pinMode(RestraintsLightPin, OUTPUT);
  pinMode(DispatchLightPin, OUTPUT);
  eStopState = digitalRead(EmergencyStopPin);
  digitalWrite(EStopLightPin, eStopState); //set initial state of E-stop
}

void loop() {
  // put your main code here, to run repeatedly:
  currentMillis = millis();
  if (digitalRead(PowerSwitchPin) == LOW) { // only process controls and update lights if power is on
    processControls();
    updateLights();
    if (keyboardState == false) { //Enable keyboard when power turned on
      Keyboard.begin();
      keyboardState = true;
    }
  }
  else {  // turn off all lights and disable keyboard if power is off
    lightsOut();
    if (keyboardState == true) {
      Keyboard.end();
      keyboardState = false;
    }
  }
  updateStates();
  if (digitalRead(EmergencyStopPin) != eStopState) { //Toggle E-stop
    Keyboard.press(201); //Press F8
    Keyboard.releaseAll();
    eStopState = digitalRead(EmergencyStopPin);
    digitalWrite(EStopLightPin, eStopState);
    delay(DebounceDelay);
    if (eStopState == LOW) {
      eStopError = 0; //clear E-stop error when pulled
    }
  }
  if (digitalRead(ResetButtonPin) == LOW) { //reset station state
    reset();
  }
  if ((eStopError + seatsFloorError + gatesError + restraintsError + dispatchError) > 0) {
    digitalWrite(TroubleLightPin, HIGH);
  } else { //Turn on trouble light if any errors present, otherwise turn off
    digitalWrite(TroubleLightPin, LOW);
  }
}

void processControls() {
  if (digitalRead(SeatsFloorButtonPin) == LOW && (isFloorlessOrFlying == false)) { //enable floorless or flying if seats/floor button pressed
    isFloorlessOrFlying = true;
    delay(DebounceDelay);
  }
  if (digitalRead(SeatsFloorButtonPin) == LOW && isFloorlessOrFlying) { //read seats/floor button
    if (canMoveSeatsFloor) {
      moveSeatsFloor();
    } else { //move seats/floor if possible, otherwise throw error
      if (seatsFloorMotionDirection == 0 && SeatsFloorStartTime > 0) {
        seatsFloorError = 1;
      }
    }
  }
  if (digitalRead(GatesButtonPin) == LOW) { //read gates button
    if (canMoveGatesRestraints) {
      moveGates();
    } else { //move gates if possible, otherwise throw error
      gatesError = 1;
    }
  }
  if (digitalRead(RestraintsButtonPin) == LOW) { //read restraints button
    if (canMoveGatesRestraints) {
      moveRestraints();
    } else { //move gates if possible, otherwise throw error
      restraintsError = 1;
    }
  }
  if (digitalRead(DispatchButtonPin) == LOW) { //read dispatch button
    if (canDispatch) {
      dispatchTrain();
    } else { //Dispatch train if possible, otherwise throw error unless train moving
      if (trainParked || eStopState == HIGH) {
        dispatchError = 1;
      }     
    }
  }
}

void reset() { //reset station state
  if (digitalRead(EmergencyStopPin) == HIGH) { //check if E-stop is pressed
    eStopError = 1; // Throw error if reset pressed with E-stop pressed
  }
  else {
    isFloorlessOrFlying = false; //set all station state variables to default
    restraintsOpen = false;
    restraintsClosed = true;
    gatesOpen = false;
    gatesClosed = true;
    seatsFloorLoading = true;
    seatsFloorDispatch = false;
    eStopState = LOW;
    DispatchStartTime = 0;
    SeatsFloorStartTime = 0;
    GatesStartTime = 0;
    RestraintsStartTime = 0;
    seatsFloorMotionDirection = 0;
    gatesMotionDirection = 0;
    restraintsMotionDirection = 0;
    canDispatch = true;
    canMoveGatesRestraints = true;
    canMoveSeatsFloor = false;
    trainParked = true;
    eStopError = 0;
    seatsFloorError = 0;
    restraintsError = 0;
    gatesError = 0;
    dispatchError = 0;
  }
  delay(DebounceDelay);
}

void lightsOut() { //turn off all lights
  digitalWrite(SeatsFloorLightPin, LOW);
  digitalWrite(GatesLightPin, LOW);
  digitalWrite(RestraintsLightPin, LOW);
  digitalWrite(DispatchLightPin, LOW);
}

void moveSeatsFloor() {
  if (seatsFloorDispatch) { //move seats/floor to loading position
    Keyboard.press(KEYPAD_4); //lower flyer seats
    Keyboard.press(KEYPAD_7); //raise floorless floor
    Keyboard.releaseAll();
    seatsFloorDispatch = false;
    seatsFloorMotionDirection = -1; //-1 = moving from dispatch position to loading position
    SeatsFloorStartTime = currentMillis; //start motion timer
  }
  if (seatsFloorLoading) { //move seats/floor to dispatch position
    Keyboard.press(KEYPAD_6); //raise flyer seats
    Keyboard.press(KEYPAD_1); //lower floorless floor
    Keyboard.releaseAll();
    seatsFloorLoading = false;
    seatsFloorMotionDirection = 1; //1 = moving from loading position to dispatch position
    SeatsFloorStartTime = currentMillis; //start motion timer
  }

}

void moveGates() {
  if (gatesClosed) { //open gates
    Keyboard.press(KEYPAD_9);
    Keyboard.releaseAll();
    gatesClosed = false;
    gatesMotionDirection = -1; //-1 = moving from dispatch position to loading position
    GatesStartTime = currentMillis; //start motion timer
  }
  if (gatesOpen) { //close gates
    Keyboard.press(KEYPAD_3);
    Keyboard.releaseAll();
    gatesOpen = false;
    gatesMotionDirection = 1; //1 = moving from loading position to dispatch position
    GatesStartTime = currentMillis; //start motion timer
  }
}

void moveRestraints() {
  if (restraintsClosed) { //open restraints
    Keyboard.press(KEYPAD_8);
    Keyboard.releaseAll();
    restraintsClosed = false;
    restraintsMotionDirection = -1; //-1 = moving from dispatch position to loading position
    RestraintsStartTime = currentMillis; //start motion timer
  }
  if (restraintsOpen) { //close restraints
    Keyboard.press(KEYPAD_2);
    Keyboard.releaseAll();
    restraintsOpen = false;
    restraintsMotionDirection = 1; //1 = moving from loading position to dispatch position
    RestraintsStartTime = currentMillis; //start motion timer
  }
}

void dispatchTrain() { //dispatch train and start timer
  Keyboard.press(KEYPAD_ENTER); //dispatch train
  Keyboard.releaseAll();
  trainParked = false;
  DispatchStartTime = currentMillis; //start motion timer
}

void updateLights() {
  if (currentMillis - previousBlinkMillis >= blinkInterval) { // every half second, check if lights should blink
    previousBlinkMillis = currentMillis;
    if (ledState == LOW) {
      ledState = HIGH;
    } else { //update ledState to blink lights
      ledState = LOW;
    }
  }
  if (trainParked) { // only update lights if train parked in station
    if (isFloorlessOrFlying) { //only update seats/floor light for appropriate coasters
      if (seatsFloorDispatch && canMoveSeatsFloor) {
        digitalWrite(SeatsFloorLightPin, ledState); //blink if in dispatch position and movable
      }
      if (seatsFloorMotionDirection != 0 || (canMoveSeatsFloor && seatsFloorLoading)) {
        digitalWrite(SeatsFloorLightPin, HIGH); //seats/floor light on if moving or in loading position and movable
      }
      if (seatsFloorMotionDirection == 0 && canMoveSeatsFloor == false) {
        digitalWrite(SeatsFloorLightPin, LOW); //seats/floor light off if inactive
      }
    } else {
      digitalWrite(SeatsFloorLightPin, LOW); //off if coaster not floorless or flying
    }
    if (canMoveGatesRestraints) { //check gates and restrtaints state
      if (gatesClosed && gatesMotionDirection == 0) {
        digitalWrite(GatesLightPin, ledState); //blink if gates closed
      } else {
        digitalWrite(GatesLightPin, HIGH); //on if gates open or moving
      }
      if (restraintsClosed && restraintsMotionDirection == 0) {
        digitalWrite(RestraintsLightPin, ledState); //blink if restraints closed
      } else {
        digitalWrite(RestraintsLightPin, HIGH); //on if restraints open or moving
      }

    } else {
      digitalWrite(GatesLightPin, LOW); // gates light off if unavailable
      digitalWrite(RestraintsLightPin, LOW); //restraints light off if unavailable
    }
    if (canDispatch) { //blink dispatch light if usable
      digitalWrite(DispatchLightPin, ledState);
    } else { //off if dispatch not possible
      digitalWrite(DispatchLightPin, LOW);
    }
  }
  else { // only dispatch lights on if train moving
    digitalWrite(SeatsFloorLightPin, LOW);
    digitalWrite(GatesLightPin, LOW);
    digitalWrite(RestraintsLightPin, LOW);
    digitalWrite(DispatchLightPin, HIGH);
  }
}

void updateStates() {
  if (digitalRead(DispatchButtonPin) == HIGH) {
    dispatchError = 0;
  }
  if (digitalRead(GatesButtonPin) == HIGH) {
    gatesError = 0;
  }
  if (digitalRead(RestraintsButtonPin) == HIGH) {
    restraintsError = 0;
  }
  if (digitalRead(SeatsFloorButtonPin) == HIGH) {
    seatsFloorError = 0;
  } //clear all errors when buttons released
  if ((isFloorlessOrFlying == false || seatsFloorDispatch) && gatesClosed && restraintsClosed && trainParked && eStopState == LOW) {
    canDispatch = true;
  } else { //determine if train can be dispatched
    canDispatch = false;
  }
  if ((isFloorlessOrFlying == false || seatsFloorLoading) && trainParked) {
    canMoveGatesRestraints = true;
  } else { //determine if gates and restraints can be moved
    canMoveGatesRestraints = false;
  }
  if (isFloorlessOrFlying && gatesClosed && restraintsClosed && trainParked && (seatsFloorLoading || seatsFloorDispatch)) {
    canMoveSeatsFloor = true;
  } else { //determine if seats/floor can be moved
    canMoveSeatsFloor = false;
  }
  if ((currentMillis - GatesStartTime >= GatesRestraintsDelay) || GatesStartTime == 0) {
    if (gatesMotionDirection == -1) { // Check if gates are opening
      gatesMotionDirection = 0;
      gatesOpen = true;
    }
    if (gatesMotionDirection == 1) { //check if gates are closing
      gatesMotionDirection = 0;
      gatesClosed = true;
    }
  }
  if ((currentMillis - RestraintsStartTime >= GatesRestraintsDelay) || RestraintsStartTime == 0) {
    if (restraintsMotionDirection == -1) { // Check if restraints are opening
      restraintsMotionDirection = 0;
      restraintsOpen = true;
    }
    if (restraintsMotionDirection == 1) { //check if restraints are closing
      restraintsMotionDirection = 0;
      restraintsClosed = true;
    }
  }
  if ((currentMillis - SeatsFloorStartTime >= SeatsFloorDelay) || SeatsFloorStartTime == 0) {
    if (seatsFloorMotionDirection == -1) { //check if seats/floor moving to loading position
      seatsFloorMotionDirection = 0;
      seatsFloorLoading = true;
    }
    if (seatsFloorMotionDirection == 1) { //check if seats/floor moving to dispatch position
      seatsFloorMotionDirection = 0;
      seatsFloorDispatch = true;
    }
  }
  if ((currentMillis - DispatchStartTime >= DispatchDelay) || DispatchStartTime == 0) {
    trainParked = true; //check when dispatch/advance sequence is complete
  }
}


Return to NoLimits Coaster 2

 


  • Related topics
    Replies
    Views
    Last post
cron