# Motor Rotation Based on Joystick  Counting

This Python code demonstrates a joystick-controlled motor rotation system that enables a user to count joystick inputs and control the motor's rotations accordingly. The application uses a **USB-connected motor controller** to read joystick inputs and perform precise motor movements based on the input count. Below is a step-by-step explanation of the code.

## Required Libraries and Modules

```python
import serial
from smd.red import Master, Red
from threading import Thread
import time
from serial.tools.list_ports import comports
from platform import system
```

* **`serial`**: Handles USB serial communication with the motor controller.
* **`smd.red`**: Provides classes (`Master` and `Red`) for interacting with the motor controller and motor.
* **`threading.Thread`**: Enables multi-threading, allowing joystick input to be handled in parallel with other operations.
* **`time`**: Used for timing and delays.
* **`serial.tools.list_ports`**: Lists available USB ports on the system.
* **`platform.system`**: Detects the operating system (Windows, Linux, or macOS) for platform-specific USB handling.

## USB Port Detection Function

```python
def USB_Port():
    if system() == "Windows":
        ports = list(comports())
        if ports:
            for port, desc, hwid in sorted(ports):
                if 'USB Serial Port' in desc:
                    return port
        return None

    elif system() == "Linux":
        ports = list(serial.tools.list_ports.comports())
        if ports:
            for port, desc, hwid in sorted(ports):
                if '/dev/ttyUSB' in port:
                    return port

    elif system() == "Darwin":  # macOS
        ports = list(serial.tools.list_ports.comports())
        if ports:
            for port, desc, hwid in sorted(ports):
                if '/dev/tty.usbserial' in port or '/dev/tty.usbmodem' in port:
                    return port

    return None
```

* This function detects the USB port where the motor controller is connected:
  * **Windows**: Searches for ports labeled `USB Serial Port`.
  * **Linux**: Looks for ports starting with `/dev/ttyUSB`.
  * **macOS**: Searches for ports containing `/dev/tty.usbserial` or `/dev/tty.usbmodem`.
* If no suitable port is found, the function returns `None`.

## Motor Initialization

```python
port = USB_Port()
m = Master(port)
motor = m.attach(Red(0))
```

* **`USB_Port()`**: Detects the USB port for the motor controller.
* **`Master(port)`**: Establishes communication with the motor controller on the detected port.
* **`m.attach(Red(0))`**: Attaches a motor with ID `0`.

## Motor Parameter Configuration

```python
m.set_shaft_rpm(0, 100)
m.set_shaft_cpr(0, 6533)
m.set_control_parameters_velocity(0, 10, 1, 0) 
m.set_operation_mode(0, 2)
```

* **`set_shaft_rpm(0,`** 10&#x30;**`)`**: Sets the motor's maximum speed to 6533 RPM.
* **`set_shaft_cpr(0,`** 653&#x33;**`)`**: Defines the encoder resolution as 100 counts per revolution (CPR).
* **`set_control_parameters_velocity(0, 10, 1, 0)`**: Configures PID velocity control parameters.
* **`set_operation_mode(0, 2)`**: Sets the motor's operation mode to velocity control.

## Counting Mechanism and Motor Rotation

The code implements two key functionalities:

1. **Counting Mode**: The user can increment a counter by pressing and releasing the joystick button.
2. **Motor Rotation**: The motor rotates a specified number of turns based on the counter value.

Motor Rotation Function

```python
def rotate_motor(turns):
    steps_per_turn = 6533
    target_position = turns * steps_per_turn
    current_position = m.get_position(0)
    m.set_velocity(id=0, sp=10000)
    while abs(m.get_position(0) - current_position) < target_position:
        time.sleep(0.01)
    m.set_velocity(id=0, sp=0)
```

* **Calculate Target Position**: The number of steps required for the specified turns is calculated.
* **Control Motor Movement**: The motor rotates until the current position reaches the target position.
* **Stop the Motor**: The motor's velocity is set to zero once the target position is achieved.

**Joystick Control Function**

```python
def joystick_control():
    global counting_mode, turn_count, initial_press_time, final_press_time
    
    while True:
        joystick = m.get_joystick(0, 1)

        if joystick is not None:
            button_pressed = joystick[2]  # Button

            # Switching to counting mode: If the button is held down for 5 seconds for the first time
            if button_pressed and not counting_mode:
                initial_press_time = time.time()
                while button_pressed and time.time() - initial_press_time < 5:
                    button_pressed = m.get_joystick(0, 1)[2]  # Check if the button is still pressed
                    time.sleep(0.1)
                if time.time() - initial_press_time >= 2:
                    counting_mode = True
                    print("Counting mode has been entered. You can count by pressing and pulling.")
            
            # Increase turn_count by push-pull operation in counting mode
            elif counting_mode and button_pressed:
                turn_count += 1
                print(f"The counter was increased: {turn_count}")

            # Rotate the motor: Press and hold the button for 5 seconds in counting mode to rotate the motor.
            if counting_mode and button_pressed:
                final_press_time = time.time()
                while button_pressed and time.time() - final_press_time < 5:
                    button_pressed = m.get_joystick(0, 1)[2]
                    time.sleep(0.1)
                if time.time() - final_press_time >= 2:
                    print(f"The motor will turn {turn_count} times.")
                    rotate_motor(turn_count)
                    turn_count = 0  # Reset the turn count
                    counting_mode = False  # Exit counting mode
                    print("The motor is turned on and the counter is reset to zero.")

        time.sleep(0.01)
```

* **Button Press Detection**: Reads the joystick button state from the motor controller.
* **Entering Counting Mode**: Holding the button for 5 seconds activates counting mode.
* **Incrementing the Counter**: In counting mode, pressing and releasing the button increases the counter.
* **Triggering Motor Rotation**: Holding the button for another 5 seconds rotates the motor according to the counter value.

## Application Workflow

1. **Joystick Monitoring**: The `joystick_control` function continuously monitors joystick input in a separate thread.
2. **Counting Mode Activation**: The user can activate counting mode by holding the joystick button for 5 seconds.
3. **Increment Counter**: In counting mode, each press-and-release increments the counter.
4. **Motor Rotation**: Holding the button for 5 seconds in counting mode rotates the motor by the specified number of turns.

## Full Code:

```python
import serial
from smd.red import Master, Red
from threading import Thread
import time
from serial.tools.list_ports import comports
from platform import system

def USB_Port():
    if system() == "Windows":
        ports = list(comports())
        if ports:
            for port, desc, hwid in sorted(ports):
                if 'USB Serial Port' in desc:
                    return port
        return None

    elif system() == "Linux":
        ports = list(serial.tools.list_ports.comports())
        if ports:
            for port, desc, hwid in sorted(ports):
                if '/dev/ttyUSB' in port:
                    return port

    elif system() == "Darwin":  # macOS
        ports = list(serial.tools.list_ports.comports())
        if ports:
            for port, desc, hwid in sorted(ports):
                if '/dev/tty.usbserial' in port or '/dev/tty.usbmodem' in port:
                    return port

    return None

port = USB_Port()
m = Master(port)
motor = m.attach(Red(0))

# Set motor parameters
m.set_shaft_rpm(0, 6533)
m.set_shaft_cpr(0, 100)
m.set_control_parameters_velocity(0, 10, 1, 0) 
m.set_operation_mode(0, 2)

# Variable to count how many turns to perform when the button is released
counting_mode = False
turn_count = 0
initial_press_time = 0
final_press_time = 0 

# Function to rotate the motor a specific number of turns
def rotate_motor(turns):
    steps_per_turn = 6533  # Set according to the encoder CPR value
    target_position = turns * steps_per_turn  # Calculate the target position
    current_position = m.get_position(0)  # get motor position

    # Rotate the motor until the target position is reached.
    m.set_velocity(id=0, sp=10000)  # Set to maximum speed
    while abs(m.get_position(0) - current_position) < target_position:
        time.sleep(0.01)  # Wait for a short time
    
    m.set_velocity(id=0, sp=0)  # Stop the motor


# Function to handle joystick control
def joystick_control():
    global counting_mode, turn_count, initial_press_time, final_press_time
    
    while True:
        joystick = m.get_joystick(0, 1)

        if joystick is not None:
            button_pressed = joystick[2]  # Button

            # Switching to counting mode: If the button is held down for 5 seconds for the first time
            if button_pressed and not counting_mode:
                initial_press_time = time.time()
                while button_pressed and time.time() - initial_press_time < 5:
                    button_pressed = m.get_joystick(0, 1)[2]  # Check if the button is still pressed
                    time.sleep(0.1)
                if time.time() - initial_press_time >= 2:
                    counting_mode = True
                    print("Counting mode has been entered. You can count by pressing and pulling.")
            
            # Increase turn_count by push-pull operation in counting mode
            elif counting_mode and button_pressed:
                turn_count += 1
                print(f"The counter was increased: {turn_count}")

            # Rotate the motor: Press and hold the button for 5 seconds in counting mode to rotate the motor.
            if counting_mode and button_pressed:
                final_press_time = time.time()
                while button_pressed and time.time() - final_press_time < 5:
                    button_pressed = m.get_joystick(0, 1)[2]
                    time.sleep(0.1)
                if time.time() - final_press_time >= 2:
                    print(f"The motor will turn {turn_count} times.")
                    rotate_motor(turn_count)
                    turn_count = 0  # Reset the turn count
                    counting_mode = False  # Exit counting mode
                    print("The motor is turned on and the counter is reset to zero.")

        time.sleep(0.01)
        
joystick_thread = Thread(target=joystick_control)
joystick_thread.daemon = True
joystick_thread.start()

while True:
    time.sleep(1)
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.acrome.net/smd-kits/starter-kit/what-you-can-build/motor-rotation-based-on-joystick-counting.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
