Programming a safety / abort button?

So this is something I might or might not do depending on how difficult it would be. This first project of mine is already proving to be one heck of a burn-in test, so this step might be a little much, but if it won’t be too difficult to program, I definitely think it’s a good idea for this project.

So, as you may have seen in some of my other posts here, my first project is going to be the Protector’s Broadsword from Starbound, which features a transforming hilt. What I’m considering doing is placing a hidden third button in the inner surfaces of the side pieces of the hilt (it will actually be two to four buttons or microswitches, one or two in each of the moving sides, but they will all have the same purpose and so will be wired in parallel and treated as one button). The purpose of these would be to detect if there’s an obstruction in the way when the hilt transforms, for example if the sword is accidentally activated while still in its scabbard.

Here’s a quick outline of the steps I’d like it to take while powering up the blade.

  1. If the safety button is currently being pressed, pressing the power button will do nothing besides play a short sound to indicate failure to power on.
  2. If the safety button is not pressed and the power button is held, the servos will be sent the signal to transform the hilt.
  3. If the safety button is pressed at any point during the servo’s movement (The servos I’ll be using won’t have feedback, so I’ll probably instead just have a short wait timer to allow them to do what they need to), then the servos will be immediately signaled to return to their starting position, a failure sound will play, and any additional press or release events from the power button will be ignored for a few seconds.
  4. If transformation is completed successfully without the safety button being pressed, a “ready” sound will play. At this point, releasing the power button will ignite the blade, or if the power button had already been released during the transformation, it will skip the ready sound and jump to immediately igniting the blade.
  5. While the blade is powered on, the safety button will no longer be needed, and will be disabled until the blade is powered off again.

So yeah, I would assume that the pre-check would be simple, only requiring an if-statement. But I’m not sure how to go about aborting an event in progress.

Here is what I would do: (note, completely untested!)

// This is a copy of InOutHelperX,
// modified to control a separate transformation.

bool transform = false;
float transformation_state = 0.0;

class  ServoF {
public:
  const int transform_millis = 2000;
  const int retract_millis = 2000;
  void run(BladeBase* blade) {
    uint32_t now = micros();
    uint32_t delta = now - last_micros_;
    last_micros_ = now;
    if (transform) {
      if (transformation_state == 0.0) {
        trannsformation_state  = 0.00001;
      } else {
        transformation_state += delta / (transform_millis * 1000.0);
        transformation_state = std::min(transformation_state, 1.0f);
      }
    } else {
      tramsformation_state -= delta / (retract_millis * 1000.0);
      tramsformation_state = std::max(transformation_state, 0.0f);
    }
    ret_ = transformation_state * 32768.0;
  }
  int getInteger(int led) { return ret_; }

private:
  uint32_t last_micros_;
  int ret_;
};

You can put this in your config file, at the top of the CONFIG_PRESETS section
Now, to start the transformation, you do: transfrorm = true;
To check if the transformation is done: if (transformation_state == 1.0)
To retract, or to abort: transform = false;

Your style that controls the servo would look something like:
StylePtr<InOutHelperX<TRANSFORMED_COLOR, ServoX, RETRACTED_COLOR>>()

Where TRANSFORMED_COLOR and RETRACTED_COLOR are whatever RGB values make the servo do the right thing…

2 Likes

Sorry if some of these are dumb questions. It’s been over a decade and a half since I last touched C++ and I’m rusty as heck. :rofl:

  • The class example you gave is called ServoF, but the style example has ServoX as an argument. Am I right in thinking that’s a typo and that they actually need to both use the same name?

  • Will simply changing the value of transform handle all the motion, or will I need to also turn that particular “blade” on and off too? If it’s the latter case, what functions do I need to control blades individually? I’d like for the transformation to take place before the other blades turn on, so I’m guessing that simply using On() and Off() won’t quite fit my needs in that case.

  • Programming the button actions, how would I write a case statement that checks that a button is NOT pressed?
    Two examples:
    – Hold (short) pow while aux is also being held, and while aux2 is NOT pressed.
    – Power is not pressed when transformation_state reaches 1.0 and mode is still off (EVENT_RELEASED won’t necessarily be what I want here because the release could occur before the other conditions are met)

Yes, they should be the same.

yes

It’s actually harder to write one which triggers regardless of whether another button is pressed or not.

One of these: (possibly with MODE_ON instead of MODE_OFF)

   case EVENTID(BUTTON_POWER, EVENT_HELD,  MODE_OFF | BUTTON_AUX):
   case EVENTID(BUTTON_POWER, EVENT_CLICK_SHORT, MODE_OFF | BUTTON_AUX);

This one doesn’t quite make sense to me, but based on your earlier descriptions, I think you might want something like:

    bool transformation_done_ = false;

    void Loop() override {
       PropBase::Loop();
       if (transform) {
         if (!transformation_done_ && transformation_state == 1.0) {
           // Transformation done, check if we should turn saber on.
           // (if power is still held.)
           transformation_done_ = true;
           if (current_modifiers & BUTTON_POWER) {
              current_modifiers = 0;
              On();
           }
       } else {
          transformation_done_ = false;
       }
    }

// In EVENT2 we would do something like:

    case EVENTID(BUTTON_POWER, EVENT_PRESSED, MODE_OFF):
         if (!transform) {
              transform = true;
         } else {
              On();
         }
         return true;

Or maybe it’s this?

   case EVENTID(BUTTON_POWER, EVENT_PRESSED, MODE_OFF):
         if (!transform) {
              transform = true;
         } else {
              On();
         }
         return true;
    case EVENTID(BUTTON_POWER, EVENT_RELEASED, MODE_OFF):
        if (transformation_state == 1.0) {
           On();
           return true;
        }
        break;
1 Like

Okay, awesome! Those code snippets aren’t quite exactly what I need, but they’re more than enough to give me an idea. Thanks!

Here’s what my Loop override looks like.

bool ready_sound_played = false;
void Loop() override {
	PropBase::Loop();
	if (transform && (current_modifiers & MODE_OFF) && (transformation_state == 1.0)) {
		// Transformation done, check if we should turn saber on.
		// If power is still held, play sound to indicate that blade is ready to turn on.
		if (current_modifiers & BUTTON_POWER) {
			 if (!ready_sound_played){
				Play("ready.wav");
				ready_sound_played = true;
			 }
		}
		else { //if power is not held, turn saber on.
			current_modifiers = 0;
			if (ready_sound_played) ready_sound_played = false; //reset ready_sound_played if necessary
			On();
		}
	}
}

And here’s my related case statements

case EVENTID(BUTTON_AUX2, EVENT_PRESSED, MODE_OFF): //Safety button is pressed during transformation. Play buzz and abort transform
		if (transform){
			Play("buzz.wav");
			transform = false;
			return true;
		}
		
case EVENTID(BUTTON_POWER, EVENT_DOUBLE_CLICK, MODE_OFF | BUTTON_AUX2): //Attempting to power on while safety button is pressed.
		if (transform) transform = false;
		Play("buzz.wav");
		return true;

case EVENTID(BUTTON_POWER, EVENT_DOUBLE_CLICK, MODE_OFF)://begin Preon transformation
        if (current_modifiers & BUTTON_AUX2) {  //First check that safety button isn't pressed (Not sure if this is necessary here since other cases check for this)
			Play("buzz.wav");
			return true;
		}
		else{
			transform = true;
			return true;
		}

Edit: Corrected a mistake in the loop code.

This is not what you want.
What are you trying to check here?

I recommend having a “transformation_done” variable instead.
I think your current implementation would re-ignite the saber if you tried to turn it off while it’s still transformed.

You need a “break” after this.
(Unless you intended it to fall through?)

This is not necessary.
It won’t get here if AUX2 is pressed.

The section within this if-branch should only be needed while off and preparing to power on, so once the sword is already on, it should be skipped over. Right? Otherwise, if I’m understanding it correctly, it would play the ready sound again anytime I press power while it’s on, and it would also continuously call On() when I’m not pressing power.

I don’t think that will be a problem with how I’m setting it up. The event that will turn the blade off will also set transform to false before doing so.

Oop! You’re right. Thanks for the catch! :laughing:
Looking at the code again too, I think the case statement also needs MODE_ANY_BUTTON since I want a press on AUX2 during transformation to ALWAYS abort it regardless of other buttons pressed.

You’ll want to use !SaberBase::IsOn()

One more question, because a random idea just hit me.
So, with the way the AUX2 / safety buttons are going to be positioned, a scabbard could be made that would keep the buttons pressed when the blade is sheathed. So with this in mind, would it be possible for a long-hold of AUX2 to activate idle / sleep mode? Keeping in mind that AUX2 would continue to be held during sleep.

So more specifically, this would be what I’d need:
-Long-hold on AUX2 will play a “power_down” sound and then put the sword to sleep.
-Continued holding of AUX2 will not wake from sleep. EVENT_RELEASED should be able to wake it from sleep, but any other events on AUX2 should not.
-When an event occurs that wakes the sword from sleep, it will check if AUX2 is still pressed, and if so, it will do nothing and immediately go back to sleep.
-Play a “power_up” sound upon waking up.