Impulse 04 // Crash Course Protopie Part 2

ProtoPie 101 Crash Course | ProtoPie School

After completing the first half of the ProtoPie Crash Course, I was motivated to dive into the second half because of the quick learning and skills I had already gained in the first part of the course. With three more lessons this time about advanced techniques, I gained a deeper understanding of ProtoPie’s capabilities. The content this time was Conditional Logic and Triggers, Variables and Functions & a Wrap Up to summarise and review all the learning from the whole course.

The fourth lesson introduced me to conditional logic and advanced triggers. These features allowed me to create interactions that responded intelligently to user inputs. This was a significant step up from the basic interactions we learned earlier.

Conditional Logic

We started by creating a password validation interaction using conditionals. This exercise showed me how to add logic to prototypes without needing to write a single line of code. By setting conditions, I was able to create a prototype that checked whether a password met specific criteria and provided real-time feedback to the user.

Chain and Range Triggers

Next, we explored the Chain Trigger, which is used for creating navigation aids. I designed an interaction where tapping on different sections of a menu smoothly scrolled to the corresponding part of the page. The Range Trigger was another great too which I used to create an auto-play video carousel that responded dynamically as the user scrolled. Both triggers added a new layer of sophistication to my prototypes.

The fifth lesson was all about harnessing the full power of ProtoPie by using variables, functions, and components. These features gave me access to the possibilities of creating complex, yet manageable, prototypes.

Variables and Functions

I started by learning how to use variables and formulas to store and manipulate data within a prototype. This was a game-changer for me, as it allowed for dynamic interactions. For example, I created a camera focus point interaction where users could tap anywhere on the screen to adjust the focus dynamically. Using variables made the interaction feel incredibly realistic.

Custom Greetings and Smart Light Control

Next, I built a customized greeting interaction that displayed the user’s name based on their input. This feature demonstrated how ProtoPie could personalize experiences. We also designed a smart light control prototype where users could adjust the brightness and color of a light bulb. This exercise showcased how ProtoPie could simulate IoT interactions effectively.

Multi-Screen Smart Home Control

The highlight of this lesson was creating a multi-screen smart home control interface. By using components and the Send & Receive feature, we linked multiple screens together seamlessly. This exercise emphasized the importance of reusability and organization in prototyping complex systems.

The final lesson was a wrap-up session that consolidated everything we had learned throughout the course. It included a knowledge exam, which tested our understanding of ProtoPie’s features. I was happy to pass the exam and receive my certificate of the crash course in Protopie.

Helpful Resources

Before the course ended, we were provided with a lot of resources to continue our ProtoPie journey. These included detailed documentation, community forums, and example projects. Knowing that I have these resources to look up gives me confidence to tackle even the most challenging prototyping tasks in the future.

The second half of the ProtoPie crash course, like the first, was interesting and full of useful skills and possibilities for future prototypes. It opened my mind to not only think about how to design interactions that are both functional and intuitive, but also that I am now able to test and prototype them myself. The hands-on exercises, as in the first part, allowed me to experiment with the more advanced features and gain practical experience, which, as I said before, is the only way I really learn, by trying things and doing them. By the end of the course, I felt equipped to create prototypes that go beyond static designs and truly mimic real-world interactions. Because ProtoPie’s is so easy to use, I think it will be my go-to tool for prototyping. It is also a good element for my Master’s thesis, in which I plan to connect the analogue and digital worlds in a calmer way by creating new ways of interacting between them. As I plan to build and test a physical prototype in the thesis, I will most likely need some sort of digital layer, which I now feel able to realise, or at least build a mock-up of.

Impulse 03 // Crash Course Protopie Part 1

ProtoPie 101 Crash Course | ProtoPie School

For this semester and next, we have been given the opportunity to use Protopie with a full licence as part of our studies. Because this was introduced in a subject where we could choose the topic, we wanted to work on ourselves, and the topic I chose was a group project to further develop a game we made in the first semester of the Masters. I decided to use two of my impulse blog posts to learn how to use and prototype with Protopie. Fortunately, Protopie offers a comprehensive crash course, divided into six lessons, to learn and master many of the possibilities it offers. As the course is quite extensive, I have split it into two parts, each covering three lessons of the course. So here is the first half of the course on the basics, interactive transitions & sensor-based interactions.

The course started off with a comprehensive introduction to ProtoPie. The first lesson covered the tool’s three main purposes: to create, test, and share prototypes. This was perfect for me, as I’d only ever worked with Figmas prototyping tools before. ProtoPie promised to enable more dynamic and realistic interactions.

Creating Prototypes

It started with learning how to set up our projects. The process was straightforward. Once the project was ready, we explored how to seamlessly import designs from tools such as Figma, Adobe XD or Sketch. Next, we were introduced to the basic features of ProtoPie. I learned how to create interactions by simply dragging and dropping elements. The interface was intuitive, even for someone with limited experience of advanced prototyping. Creating interactions felt like building with digital Lego – any action or trigger could be linked to create a seamless process.

Testing and Sharing

Once my prototype was ready, the next step was to test and share it. ProtoPie allows us to view our prototypes directly on devices such as smartphones and tablets, which made them tangible. I could see how the designs would work in real-life scenarios. Sharing was just as easy. I uploaded my project to the ProtoPie Cloud, which made it easy to collaborate with others. Another good feature is the interaction recordings. These allow you to document specific interactions. ProtoPie also has the functionality of Interaction Libraries, which allows teams to standardise design components. This can certainly save a lot of time on larger projects.

In the second lesson, it was time for hands-on with creating various types of interactions.

Screen Transitions

It started by teaching how to prototype automatic, semi-automatic, and fully custom screen transitions. I particularly enjoyed working on custom transitions because they allowed me to design interactions tailored to specific design case.

Scrolling and Paging

Next, the course dived into scrolling and paging interactions. I had always struggled to make scrolling interactions look good or useful in Figma. In ProtoPie the results were realistic, exactly like the scrolling you’d expect in a native app.

Sliding Menus

The last part of this lesson was designing sliding menus. We explored three different ways to create them, ranging from simple swiping gestures to more complex interactions that combined multiple triggers.

The third lesson took ProtoPie’s capabilities to the next level by introducing sensor-aided interactions. This feature truly sets ProtoPie apart from other prototyping tools because it enables designers to use device sensors without needing any coding knowledge.

Using Device Sensors

The workshop started with an introduction to using a phone’s camera in prototypes. I created interactions where the camera’s feed became part of the design. This was particularly useful for scenarios like augmented reality apps or interactive tutorials.

Input Fields and Native Keyboards

Next, the course explored prototyping with input fields and native keyboards. This feature was a pleasant surprise, as it allowed me to create realistic forms and search bars that behaved just like the ones in real apps. I can already see how this could improve user testing sessions, as participants would interact with the prototypes in a natural way.

Voice Interactions

The final part of this lesson focused on voice interactions. ProtoPie made it easy to incorporate voice commands and responses into prototypes. This feature opened endless possibilities for designing interfaces for voice-activated devices or accessibility features. I was amazed at how simple it was to implement this functionality.

The first three lessons of the ProtoPie crash course already showed a lot of possibilities in prototyping. Each lesson built on the previous one, gradually introducing more complex features. I appreciated the hands-on approach, as it allowed me to apply what I learned immediately, which is the best way for me to learn and retain things.

10 – “Story Teller” – Prototype Video Demo

Introduction

For the final blog post of this series, I have taken the time to prepare a comprehensive demonstration video, which provides a detailed overview of my low-fidelity prototype. This video is designed to give viewers an understanding of the basic functionalities and design elements of our prototype, showcasing its potential and the direction we are heading in with this project.

Video

Conclusions

The process of prototyping has been incredibly valuable in visualizing the potential of Story Teller. It has allowed me to identify key functionalities and design elements and brought to light areas that need further development and refinement. As I move forward, I plan to conduct further user testing to gain valuable feedback and insights. This feedback will inform my next iterations and help me continually improve and refine the tool. My ultimate goal is to create a user-friendly and intuitive platform that supports the creative process of interactive storytelling, as part of my final Master’s Thesis.

Calm Technology // 20

End of the semester and end of my prototyping journey with Tap in the field of Calm Technology for now. After finishing a polished interface to control Tap, I created an explanatory video about the process of prototyping on Tap and the final working features, which you can watch below. Overall it was a fun experience to dive into this prototyping process and learn a thousand little things along the way, from running stepper motors to writing code for Arduino and that failure is part of the process. Now that it is all done, I would like to thank ChatGPT for helping me, a non-programmer, to code and DeepLWrite for turning my quickly scribbled blog post scripts into easily readable blog posts. Making this whole project possible.

09 – “Story Teller” – Prototype Development

Introduction

After conducting a thorough analysis of different research papers and exploring various existing authoring tools, I embarked on the journey of developing my own prototype. This process involved the examination of the strengths and weaknesses of each tool, as well as an in-depth understanding of the theories and methodologies presented in each research paper. With this knowledge, I then proceeded to apply what I had learned to the creation of my prototype. The objective of this blogpost is to document the process that I underwent during the prototyping phase.

Methodology

I began by listing all the sections I wanted to reimplement from StoryTec, including the G-Flash card system and Story Curve Visualization mode.

  • Story Editor: This is a free canvas where users can drag, resize, move, and zoom in and out of elements.
    • Scenes: These contain characters, interactive elements, and behaviors such as actions, dialogue, and events.
    • Complex Scenes (which I’ll refer to as Stages): These are the environments where scenes take place. They contain stage-specific elements and, of course, the scenes themselves.
  • Stage Editor: In StoryTec, this was a pluggable framework capable of constructing scenes in various ways. For ease of use, I will integrate this as a function of the Story Editor, utilizing a toolbar with drag-and-drop flashcards, similar to G-Flash. In my version, the cards will be fully customizable through the Property Editor.
  • Property Editor: This is a separate tab that adapts based on the selected item. It can add any kind of property to any element.
  • Action Set Editor: In StoryTec, this was a separate editor for setting the logic between element interactions. In my prototype, it will become a button in the Tool tab, connecting elements similarly to how Figma handles prototyping.
  • Asset Manager: A separate tab where uploaded assets can be dragged and dropped either onto the open canvas as images, videos, or sound, or onto elements as properties.
  • Story Curve Visualization: This is an option in the top bar that changes how the scenes are displayed, toggling from the free canvas to a double-axis view of the Story Curve.

After identifying the core features and buttons for my tool, I sketched a basic information architecture on paper. Following this, I created preliminary wireframes to visualize the page’s layout.

Pages, tabs and cards

Starting from the sketches, I prototyped a low-fidelity version of the main pages for my authoring tool.

The home page displays all the stories that a user might want to create, presented in a straightforward gallery.

Each Story file features an „infinite“ zoom canvas where elements, cards, and assets can be dragged and dropped, resized, opened, and minimized. This design maximizes flexibility of thought.

When an element is clicked, the Properties tab displays all details of that specific instance, and allows the creation of new properties if necessary. These properties can be used and referenced as logic variables when establishing connections between elements.

The Stage button in the Bottom Tool Bar offers two main interactions: the New Stage card and the Element card. Both can be dragged onto the canvas and resized to create the desired composition.

Similarly, the Scene button in the Bottom Tool Bar enables the user to draw a new scene or select one of the cards for characters, elements, or behaviors.

Finally, the Story Curve View visualizes different scenes on the Narration order/Story order axis. You can personalize this visualization using the settings tab.

Goals & Conclusion

The main goal of this prototype is to thoroughly test the usability of various complex features, particularly for beginners and new storytellers. I aim to conduct an extensive user test to gain insights into whether the basic principles of User Experience (UX) are being followed and function as intended.

After initial testing and result analysis, I’ll create a higher fidelity prototype. This version will focus more on the user interface, using feedback from the initial tests to ensure a smoother, more intuitive user experience.

In my next blog post, I’ll show the main features of this prototype through a detailed demo video. This will help me show the tool’s functionality and potential.

Calm Technology // 19

Since I had scripted the different gestures I wanted to have for Tap in Arduino, I could now combine all the gestures into one script. I then connected the Wemos board that controls Tap to the wifi so that I could trigger each of these gestures wirelessly from my laptop via a simple interface.

To create the interface, I used Max 8 to build a simple patch to send OSC messages to my Wemos board. The patch consists of a few buttons that trigger messages with a value between 0 & 3, which are then sent as an OSC message via UDP to trigger different parts of the Arduino script.

Max patch interface to control tap

I combined all the different gesture scripts into one Arduino script that can receive OSC messages via UDP. These messages set the value for a variable which is then queried in the loop of the script to start the desired gestures. After the gestures have finished, the variable is reset to put Tap back into a suspended state.

#include <AccelStepper.h>
#include <ESP8266WiFi.h>  // The Library for WIFI
#include <WiFiUdp.h>  // Library for UDP
#include <OSCMessage.h>  //Library for OSC

#define motorInterfaceType 1

// Define the stepper motor and the pins that is connected to // (STEP, DIR)
AccelStepper stepper1(motorInterfaceType, D5, D6); 
AccelStepper stepper2(motorInterfaceType, D7, D8);

// Set the target positions for both steppers
int PositionDown = 0;
int PositionUp = 0;

// State variable to keep track of movement sequence
int state1 = 0;
int state2 = 0;

// For triggering movements
int mode = 0;

WiFiUDP Udp;

const char* ssid = "************";           
const char* password = "************";      

const IPAddress dstIp(192,168,1,129); 
const unsigned int dstPort = 7250;  // Destination OSC
const unsigned int localPort = 7300; // Reciving OSC


////////////////////////////////////////////////////////////////////////////////////


void setup() {
  
  Serial.begin(9600);

  // Settings for Motor 1
  stepper1.setMaxSpeed(1000); 
  stepper1.setAcceleration(500);
  stepper1.setCurrentPosition(0);

  // Settings for Motor 2
  stepper2.setMaxSpeed(1000);
  stepper2.setAcceleration(500);
  stepper2.setCurrentPosition(0);

  // Connecting to WIFI
  Serial.print("Connecting WiFi ");
  // Prevent need for powercyle after upload.
  WiFi.disconnect();

  // Use DHCP to connect and obtain IP Address.
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid,password);

  // Wait until we have connected to the WiFi AP.
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("Done!");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  Udp.begin(localPort);

}


////////////////////////////////////////////////////////////////////////////////////


void loop() {

  handleOSC();

  if (mode == 0) {

    state1 = 0;
    state2 = 0;

  }

  if (mode == 1) {

    Wave1();
    Wave2();

  }

  if (mode == 2) {
    
    Knock1();

  }

  if (mode == 3) {
    
    Tap1();
    Tap2();

  }

}


////////////////////////////////////////////////////////////////////////////////////


void Wave1() {

  switch (state1) {

    case 0:
      stepper1.setAcceleration(200);
      PositionDown = 40;
      stepper1.moveTo(PositionDown);
      state1 = 1;
      break;
    
    case 1:
      if (stepper1.distanceToGo() != 0) {
        stepper1.run();
      } else {
        stepper1.stop();
        PositionDown = -40;
        stepper1.moveTo(PositionDown);
        state1 = 2;
      }
      break;
    
    case 2:
      if (stepper1.distanceToGo() != 0) {
        stepper1.run();
      } else {
        stepper1.stop();
        PositionDown = 20;
        stepper1.moveTo(PositionDown);
        state1 = 3;
      }
      break;

    case 3:
      if (stepper1.distanceToGo() != 0) {
        stepper1.run();
      } else {
        stepper1.stop();
        PositionDown = -20;
        stepper1.moveTo(PositionDown);
        state1 = 4;
      }
      break;

    case 4:
      stepper1.setAcceleration(100);
      if (stepper1.distanceToGo() != 0) {
        stepper1.run();
      } else {
        stepper1.stop();
        PositionDown = 0;
        stepper1.moveTo(PositionDown);
        state1 = 5;
      }
      break;
    
    case 5:
      if (stepper1.distanceToGo() != 0) {
        stepper1.run();
      } else {
        stepper1.stop();
      }
      break;
  }
}


void Wave2() {

  if (state1 >= 4){

    switch (state2) {

      case 0:
        stepper2.setAcceleration(275);
        PositionUp = 25;
        stepper2.moveTo(PositionUp);
        state2 = 1;
        break;
      
      case 1:
        if (stepper2.distanceToGo() != 0) {
          stepper2.run();
        } else {
          stepper2.stop();
          PositionUp = -25;
          stepper2.moveTo(PositionUp);
          state2 = 2;
        }
        break;
      
      case 2:
        if (stepper2.distanceToGo() != 0) {
          stepper2.run();
        } else {
          stepper2.stop();
          PositionUp = 15;
          stepper2.moveTo(PositionUp);
          state2 = 3;
        }
        break;

      case 3:
        
        if (stepper2.distanceToGo() != 0) {
          stepper2.run();
        } else {
          stepper2.stop();
          PositionUp = -15;
          stepper2.moveTo(PositionUp);
          state2 = 4;
        }
        break;

      case 4:
        if (stepper2.distanceToGo() != 0) {
          stepper2.run();
        } else {
          stepper2.stop();
          PositionUp = 15;
          stepper2.moveTo(PositionUp);
          state2 = 5;
        }
        break;

      case 5:
        if (stepper2.distanceToGo() != 0) {
          stepper2.run();
        } else {
          stepper2.stop();
          PositionUp = -15;
          stepper2.moveTo(PositionUp);
          state2 = 6;
        }
        break;

      case 6:
        if (stepper2.distanceToGo() != 0) {
          stepper2.run();
        } else {
          stepper2.stop();
          PositionUp = 0;
          stepper2.moveTo(PositionUp);
          state2 = 7;
        }
        break;  
      
      case 7:
        if (stepper2.distanceToGo() != 0) {
          stepper2.run();
        } else {
          stepper2.stop();
          mode = 0;          
        }
        break;
    }
  }
}


void Knock1() {

  switch (state1) {

    case 0:
      stepper2.setAcceleration(400);
      PositionUp = -20;
      stepper2.moveTo(PositionUp);
      state1 = 1;
      break;
    
    case 1:
      if (stepper2.distanceToGo() != 0) {
        stepper2.run();
      } else {
        stepper2.stop();
        PositionUp = 80;
        stepper2.moveTo(PositionUp);
        state1 = 2;
      }
      break;
    
    case 2:
      if (stepper2.distanceToGo() != 0) {
        stepper2.run();
      } else {
        stepper2.stop();
        PositionUp = 60;
        stepper2.moveTo(PositionUp);
        state1 = 3;
      }
      break;

    case 3:
      stepper2.setAcceleration(800);
      if (stepper2.distanceToGo() != 0) {
        stepper2.run();
      } else {
        stepper2.stop();
        PositionUp = 80;
        stepper2.moveTo(PositionUp);
        state1 = 4;
      }
      break;

    case 4:
      if (stepper2.distanceToGo() != 0) {
        stepper2.run();
      } else {
        stepper2.stop();
        PositionUp = 0;
        stepper2.moveTo(PositionUp);
        state1 = 5;
      }
      break;
    
    case 5:
      if (stepper2.distanceToGo() != 0) {
        stepper2.run();
      } else {
        stepper2.stop();
        mode = 0;
      }
      break;
  }
}


void Tap1() {

  if (state2 >= 2){

    switch (state1) {

      case 0:
        stepper1.setAcceleration(300);
        PositionDown = -20;
        stepper1.moveTo(PositionDown);
        state1 = 1;
        break;
        
      case 1:
        if (stepper1.distanceToGo() != 0) {
          stepper1.run();
        } else {
          stepper1.stop();
          PositionDown = 5;
          stepper1.moveTo(PositionDown);
          state1 = 2;
        }
        break;
        
      case 2:
        stepper1.setAcceleration(600);
        if (stepper1.distanceToGo() != 0) {
          stepper1.run();
        } else {
          stepper1.stop();
          PositionDown = -20;
          stepper1.moveTo(PositionDown);
          state1 = 3;
        }
        break;

      case 3:
        if (stepper1.distanceToGo() != 0) {
          stepper1.run();
        } else {
          stepper1.stop();
          PositionDown = 0;
          stepper1.moveTo(PositionDown);
          state1 = 4;
        }
        break;

      case 4:
        stepper1.setAcceleration(100);
        if (stepper1.distanceToGo() != 0) {
          stepper1.run();
        } else {
          stepper1.stop();
        }
        break;
      
    }
  }
}


void Tap2() {

  switch (state2) {

    case 0:
      stepper2.setAcceleration(400);
      PositionUp = 50;
      stepper2.moveTo(PositionUp);
      state2 = 1;
      break;
    
    case 1:
      if (stepper2.distanceToGo() != 0) {
        stepper2.run();
      } else {
        stepper2.stop();
        PositionUp = 0;
        stepper2.moveTo(PositionUp);
        state2 = 2;       
      }
      break;

    case 2:
      if (state1 >= 4) {
        stepper2.setAcceleration(200);
        if (stepper2.distanceToGo() != 0) {
          stepper2.run();
        } else {
          stepper2.stop();
          mode = 0;
        }
      }
      break;
        
  }
}


////////////////////////////////////////////////////////////////////////////////////


void handleOSC() {
  
  OSCMessage msg("/Mode");
  int size = Udp.parsePacket();
  if (size > 0) {
    while (size--) {
      msg.fill(Udp.read());
    }
    if (!msg.hasError()) {
      msg.dispatch("/Mode", Activating);
    }
  }
}


void Activating(OSCMessage &msg) {
  
  if (msg.isInt(0)) {
    int receivedMode = msg.getInt(0);
    mode = receivedMode;
    Serial.println("Received Mode Command");
    Serial.println(mode);

  }
}

After finishing the interface and the now complete Arduino script, I tested them together. With my laptop set up as a touch interface running Max 8 and the Wemos board running Tap as usual. All went quite smoothly and after some tweaking everything is now running stable. You can see the result below.

Tap controlled via a remote interface

With this, my prototype for this semester’s Design & Research project is almost finished. The last step will be to make the interface a bit cleaner and more intuitive, and then I will make a video explaining my prototype Tap and how it works.

Visual Prototype

After spending some weeks developing a technical prototype, I started building a visual one as well. The visual prototype represents the size and appearance of my concept, but serves no functionality. It is made of paper, cardboard, plastic, tape and fabric. 

I started drawing some sketches of the intended design. In the blog post Concept Definition and Aim from May, I stated what buttons I find important to include. I worked further with these and concluded to implement seven buttons: 

  • Power on/off
  • Sound up/down
  • Play melody/record
  • Replay melody
  • Return to previous melody
  • Pause melody
  • Skip to next melody

I want the design to be clean and understandable, without any unnecessary buttons. But after my user tests, I also understood that buttons such as replay and skip were crucial in order to serve good user experiences. 

The lower part of the product is the speaker, and the upper part involve the buttons. The biggest button in the middle is the one that will be used the most. When it is pressed, the random generated melody plays. This is also the button that must be pressed in order to record the sound of the user input. In the next version of my prototype, I will also include a LED ring around that gives instant feedback on the input. 

Calm Technology // 18

With my new prototype set up, I was ready to start scripting the different gestures for Tap. I decided to make three different gestures for now: the wave, the tap and the knock. The wave for Tap to say hello or to acknowledge a new command. The tap as a way of interacting with other objects and bringing them into the user’s focus. And finally, the knock as a more analogue way of notifying the user of, for example, a timer or a specific event.

The first gesture is the wave gesture. It is intended to let the user know that Tap is active or has accepted the new command or task. In this movement, the top rotates from left to right and the tapper waves from one side to the other a few times.

Tap waving
#include <AccelStepper.h>

#define motorInterfaceType 1

// Define the stepper motor and the pins that is connected to // (STEP, DIR)
AccelStepper stepper1(motorInterfaceType, D5, D6); 
AccelStepper stepper2(motorInterfaceType, D7, D8);

// Set the target positions for both steppers
int PositionDown = 0;
int PositionUp = 0;

// State variable to keep track of movement sequence
int state1 = 0;
int state2 = 0;

////////////////////////////////////////////////////////////////////////////////////


void setup() {
  
  Serial.begin(9600);

  // Settings for Motor 1
  stepper1.setMaxSpeed(1000); 
  stepper1.setAcceleration(500);
  stepper1.setCurrentPosition(0);

  // Settings for Motor 2
  stepper2.setMaxSpeed(1000);
  stepper2.setAcceleration(500);
  stepper2.setCurrentPosition(0);

}


////////////////////////////////////////////////////////////////////////////////////


void loop() {
  
  Wave1();
  Wave2();

}


////////////////////////////////////////////////////////////////////////////////////


void Wave1() {

  switch (state1) {

    case 0:
      stepper1.setAcceleration(200);
      PositionDown = 40;
      stepper1.moveTo(PositionDown);
      state1 = 1;
      break;
    
    case 1:
      if (stepper1.distanceToGo() != 0) {
        stepper1.run();
      } else {
        stepper1.stop();
        PositionDown = -40;
        stepper1.moveTo(PositionDown);
        state1 = 2;
      }
      break;
    
    case 2:
      if (stepper1.distanceToGo() != 0) {
        stepper1.run();
      } else {
        stepper1.stop();
        PositionDown = 20;
        stepper1.moveTo(PositionDown);
        state1 = 3;
      }
      break;

    case 3:
      if (stepper1.distanceToGo() != 0) {
        stepper1.run();
      } else {
        stepper1.stop();
        PositionDown = -20;
        stepper1.moveTo(PositionDown);
        state1 = 4;
      }
      break;

    case 4:
      stepper1.setAcceleration(100);
      if (stepper1.distanceToGo() != 0) {
        stepper1.run();
      } else {
        stepper1.stop();
        PositionDown = 0;
        stepper1.moveTo(PositionDown);
        state1 = 5;
      }
      break;
    
    case 5:
      if (stepper1.distanceToGo() != 0) {
        stepper1.run();
      } else {
        stepper1.stop();
      }
      break;

  }
}


void Wave2() {

  if (state1 >= 4){

    switch (state2) {

      case 0:
        stepper2.setAcceleration(275);
        PositionUp = 25;
        stepper2.moveTo(PositionUp);
        state2 = 1;
        break;
      
      case 1:
        if (stepper2.distanceToGo() != 0) {
          stepper2.run();
        } else {
          stepper2.stop();
          PositionUp = -25;
          stepper2.moveTo(PositionUp);
          state2 = 2;
        }
        break;
      
      case 2:
        if (stepper2.distanceToGo() != 0) {
          stepper2.run();
        } else {
          stepper2.stop();
          PositionUp = 15;
          stepper2.moveTo(PositionUp);
          state2 = 3;
        }
        break;

      case 3:
        
        if (stepper2.distanceToGo() != 0) {
          stepper2.run();
        } else {
          stepper2.stop();
          PositionUp = -15;
          stepper2.moveTo(PositionUp);
          state2 = 4;
        }
        break;

      case 4:
        if (stepper2.distanceToGo() != 0) {
          stepper2.run();
        } else {
          stepper2.stop();
          PositionUp = 15;
          stepper2.moveTo(PositionUp);
          state2 = 5;
        }
        break;

      case 5:
        if (stepper2.distanceToGo() != 0) {
          stepper2.run();
        } else {
          stepper2.stop();
          PositionUp = -15;
          stepper2.moveTo(PositionUp);
          state2 = 6;
        }
        break;

      case 6:
        if (stepper2.distanceToGo() != 0) {
          stepper2.run();
        } else {
          stepper2.stop();
          PositionUp = 0;
          stepper2.moveTo(PositionUp);
          state2 = 7;
        }
        break;  
      
      case 7:
        if (stepper2.distanceToGo() != 0) {
          stepper2.run();
        } else {
          stepper2.stop();
        }
        break;

    }
  }
}

The second gesture is the tap gesture. It is intended as an object-based reminder for things like drinking water or airing out your room. It can also be used in combination with objects to create a different sound for timers and reminders than knocking on the ground. In this gesture, the tapper moves down a quarter turn and the entire base then rotates the tapper twice against the object before returning to the default position.

Tap tapping
#include <AccelStepper.h>

#define motorInterfaceType 1

// Define the stepper motor and the pins that is connected to // (STEP, DIR)
AccelStepper stepper1(motorInterfaceType, D5, D6); 
AccelStepper stepper2(motorInterfaceType, D7, D8);

// Set the target positions for both steppers
int PositionDown = 0;
int PositionUp = 0;

// State variable to keep track of movement sequence
int state1 = 0;
int state2 = 0;

////////////////////////////////////////////////////////////////////////////////////


void setup() {
  
  Serial.begin(9600);

  // Settings for Motor 1
  stepper1.setMaxSpeed(1000); 
  stepper1.setAcceleration(500);
  stepper1.setCurrentPosition(0);

  // Settings for Motor 2
  stepper2.setMaxSpeed(1000);
  stepper2.setAcceleration(500);
  stepper2.setCurrentPosition(0);

}


////////////////////////////////////////////////////////////////////////////////////


void loop() {
  
  Tap1();
  Tap2();

}


////////////////////////////////////////////////////////////////////////////////////


void Tap1() {

  if (state2 >= 2){

    switch (state1) {

      case 0:
        stepper1.setAcceleration(300);
        PositionDown = -20;
        stepper1.moveTo(PositionDown);
        state1 = 1;
        break;
        
      case 1:
        if (stepper1.distanceToGo() != 0) {
          stepper1.run();
        } else {
          stepper1.stop();
          PositionDown = 5;
          stepper1.moveTo(PositionDown);
          state1 = 2;
        }
        break;
        
      case 2:
        stepper1.setAcceleration(600);
        if (stepper1.distanceToGo() != 0) {
          stepper1.run();
        } else {
          stepper1.stop();
          PositionDown = -20;
          stepper1.moveTo(PositionDown);
          state1 = 3;
        }
        break;

      case 3:
        if (stepper1.distanceToGo() != 0) {
          stepper1.run();
        } else {
          stepper1.stop();
          PositionDown = 0;
          stepper1.moveTo(PositionDown);
          state1 = 4;
        }
        break;

      case 4:
        stepper1.setAcceleration(100);
        if (stepper1.distanceToGo() != 0) {
          stepper1.run();
        } else {
          stepper1.stop();
        }
        break;
      
    }
  }
}


void Tap2() {

  switch (state2) {

    case 0:
      stepper2.setAcceleration(400);
      PositionUp = 50;
      stepper2.moveTo(PositionUp);
      state2 = 1;
      break;
    
    case 1:
      if (stepper2.distanceToGo() != 0) {
        stepper2.run();
      } else {
        stepper2.stop();
        PositionUp = 0;
        stepper2.moveTo(PositionUp);
        state2 = 2;       
      }
      break;

    case 2:
      if (state1 >= 4) {
        stepper2.setAcceleration(200);
        if (stepper2.distanceToGo() != 0) {
          stepper2.run();
        } else {
          stepper2.stop();
        }
      }
      break;
        
  }
}

The third gesture is the knock gesture. It is intended as an analogue notification for timers, reminders or other events. In this gesture, only the tapper itself moves. It moves slightly backwards and then knocks twice on the ground before returning to its normal position.

Tap knocking
#include <AccelStepper.h>

#define motorInterfaceType 1

// Define the stepper motor and the pins that is connected to // (STEP, DIR)
AccelStepper stepper1(motorInterfaceType, D5, D6); 
AccelStepper stepper2(motorInterfaceType, D7, D8);

// Set the target positions for both steppers
int PositionDown = 0;
int PositionUp = 0;

// State variable to keep track of movement sequence
int state1 = 0;
int state2 = 0;

////////////////////////////////////////////////////////////////////////////////////


void setup() {
  
  Serial.begin(9600);

  // Settings for Motor 1
  stepper1.setMaxSpeed(1000); 
  stepper1.setAcceleration(500);
  stepper1.setCurrentPosition(0);

  // Settings for Motor 2
  stepper2.setMaxSpeed(1000);
  stepper2.setAcceleration(500);
  stepper2.setCurrentPosition(0);

}


////////////////////////////////////////////////////////////////////////////////////


void loop() {
  
  Knock1();

}


////////////////////////////////////////////////////////////////////////////////////


void Knock1() {

  switch (state1) {

    case 0:
      stepper2.setAcceleration(400);
      PositionUp = -20;
      stepper2.moveTo(PositionUp);
      state1 = 1;
      break;
    
    case 1:
      if (stepper2.distanceToGo() != 0) {
        stepper2.run();
      } else {
        stepper2.stop();
        PositionUp = 80;
        stepper2.moveTo(PositionUp);
        state1 = 2;
      }
      break;
    
    case 2:
      if (stepper2.distanceToGo() != 0) {
        stepper2.run();
      } else {
        stepper2.stop();
        PositionUp = 60;
        stepper2.moveTo(PositionUp);
        state1 = 3;
      }
      break;

    case 3:
      stepper2.setAcceleration(800);
      if (stepper2.distanceToGo() != 0) {
        stepper2.run();
      } else {
        stepper2.stop();
        PositionUp = 80;
        stepper2.moveTo(PositionUp);
        state1 = 4;
      }
      break;

    case 4:
      if (stepper2.distanceToGo() != 0) {
        stepper2.run();
      } else {
        stepper2.stop();
        PositionUp = 0;
        stepper2.moveTo(PositionUp);
        state1 = 5;
      }
      break;
    
    case 5:
      if (stepper2.distanceToGo() != 0) {
        stepper2.run();
      } else {
        stepper2.stop();
      }
      break;

  }
}


The scripting and testing of the gestures went almost smoothly with the help of ChatGPT for the coding part. Except for one problem where the lower motor started to overheat and slowly melt my 3D print. Luckily I caught it before it did any irreversible damage. The problem ended up being that I set the current flow in the motor controller for the lower motor too high, which was quickly fixed and everything went smoothly again. From here I will now start to implement all the different gestures into one script and add the functionality of triggering the different gestures via OSC messages so that I can then create a dedicated interface to control Tap.