A.K.03/Manual/3_Module_1.ipynb
2025-11-20 16:35:06 +01:00

894 lines
49 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cells": [
{
"metadata": {},
"cell_type": "markdown",
"source": [
"![book header](pictures/header.png)\n",
"\n",
"[Table of Contents](0_Table_of_Contents.ipynb)\n",
"\n",
"# Chapter 3: Module 1 - Connecting with and Controlling KITT\n",
"\n",
"**Contents**\n",
"\n",
"* [Brief Overview](#brief-overview)\n",
"* [Communicating with KITT](#communicating-with-kitt)\n",
"* [Implementing a Basic KITT Control Script](#implementing-a-basic-kitt-control-script)\n",
"* [FAQ](faq)\n",
"\n",
"In this module, we will start working with the car simulator and the car. We will provide an overview and a brief description of the vehicle, and implement some basic control scripts."
]
},
{
"metadata": {},
"cell_type": "code",
"source": [
"# Import necessary libraries\n",
"import time\n",
"\n",
"# Uncomment one of the following lines depending on your setup\n",
"\n",
"# If you are using the real car, uncomment the next line and comment the simulator line\n",
"#from serial import Serial\n",
"\n",
"# If you are using the simulator, uncomment the next line and comment the real car line\n",
"from Manual.KITT_Simulator.serial_simulator import Serial\n",
"\n",
"# Note: After changing the import statement, you need to restart the kernel for changes to take effect."
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Brief Overview\n",
"\n",
"KITT is a vehicle that you can control remotely, similar to a typical RC car. While traditional RC cars usually come with a joystick for control, this project has adapted the car so that you can use your computer instead. You can program your computer and use your keyboard to make KITT move forward, backward, or turn.\n",
"\n",
"However, the goal of the project is to automate KITT, making it drive autonomously. As your computer will do the calculations, you will need to connect the car wirelessly to your computer. Over the wireless link, you can send commands to KITT, such as controlling its direction or speed. You can also request sensor data, which is a key difference from conventional RC cars. So this setup is a **two-way** communication link.\n",
"\n",
"In this module, you will learn how to set up this wireless connection and start controlling KITT using basic keyboard commands. We will use object-oriented programming. We will also do this in such a way that, by changing one line, you can swap the real car for a simulated (model) car, allowing you to test your scripts in a simulation before going into the field.\n",
"\n",
"### KITT Basics\n",
"\n",
"KITT is built on a modified toy car model, the Traxxas E-MAXX (see picture below). In addition to its original components, KITT is equipped with two ultrasonic sensors, an LCD display, and the necessary electronics to transmit data via a Bluetooth connection. The LCD on the car provides real-time information, such as the readings from the distance measurement sensors and the battery voltage level.\n",
"\n",
"In the Files>Datasheets>KITT datasheet.pdf, you can find a data sheet that gives a more detailed overview of the various components, such as the motor controller, LCD status indicator, ultrasonic sensors, and, most importantly, the communication module.\n",
"\n",
"**Note:** As a heads-up for the future, when you start gathering data from KITT via the wireless link, you might notice that it doesn't always match whats shown on the car's LCD. The data received directly from the car is more accurate, as the LCD might update at a slower rate. Therefore, while the LCD can serve as an initial indicator of any issues, it's best to rely on the direct data from the car for precise information.\n",
"\n",
"<img src=\"pictures/traxxas_e-maxx.png\" alt=\"Traxass E-MAXX along with it's dimensions\" width=\"600\" />\n",
"\n",
"#### Hardware\n",
"\n",
"KITT is equipped with a custom-made circuit board that brings together various components needed for its operation. At the heart of this board is a microcontroller, which acts as the main control unit.\n",
"\n",
"This board also includes everything necessary for wireless communication, like the Bluetooth module, as well as connectors for other parts of the car. Additionally, there's an amplifier on the board that's used for sending out audio signals that help with localization.\n",
"\n",
"The circuit board is powered by rechargeable batteries. These batteries supply the necessary voltage through a component that adjusts the voltage to the required levels.\n",
"\n",
"Here are the key components included on the board:\n",
"\n",
"- **Microcontroller:** NXP LPC4357 chip with an ARM CORTEX-M4/M0 core\n",
"- **Bluetooth module**\n",
"- **Amplifier for the audio beacon**\n",
"- **Buck converter** for voltage regulation\n",
"- **Voltage regulator:** LM1117-3.3 for generating 3.3V DC\n",
"\n",
"<img src=\"pictures/communication_overview.png\" alt=\"Schematic of communication with KITT and PC.\" width=\"600\" />\n",
"\n",
"KITT has a Bluetooth module for communication with your PC. It consists of the following elements:\n",
"\n",
"- On car: Roving Networks RN-41 I/RM, onboard Bluetooth module with UART control.\n",
"- On PC: LM Technologies LM506, USB Bluetooth V4.0 dongle with Broadcom BCM20702 chipset, or the internal Bluetooth module of your laptop.\n",
"\n",
"**Tip:** If you want to use one of the lab computers or have trouble connecting to KITT from your PC (check if all drivers are installed), you could use the USB Bluetooth dongle.\n",
"\n",
"KITT has four LEDs on the front, see the figure below. Their meaning is as follows:\n",
"\n",
"<img src=\"pictures/figurejoin.png\" alt=\"KITT control board and front LEDs\" width=\"600\" />\n",
"\n",
"- **Red (twice):** *5V* and *20V* supply voltages are present.\n",
"- **Green (blinking):** Bluetooth is searching for a connection; **(steady):** Bluetooth connected.\n",
"- **Yellow (blinking):** Bluetooth data transfer.\n",
"\n",
"The button next to the LEDs is a reset button for the MCU. This reset button allows the user to reset KITT's actions if it freezes or is unresponsive.\n",
"\n",
"#### Software Model\n",
"\n",
"The KITT test fields have limited availability. To facilitate software development and testing, you can use a KITT software model. It is in the package `KITT_Simulator`. If you use the `Serial` command from this package and connect to KITT (see below), then instead of connecting to the real car, you connect to the software model. You can send commands and read its status using the same commands as for the real KITT. Thus, you can first test all your algorithms on the simulated model and later on the real KITT.\n",
"\n",
"Of course, this only works if the model is sufficiently close. At some point, you'll have to adjust the parameters in the code to match the reality of your car (calibration).\n",
"\n",
"To toggle between running the simulated model or the real car, look at the top block of this notebook: by commenting or uncommenting the appropriate import line, you can switch between the real car and the simulator. You will need to restart the kernel after changing this command.\n",
"\n",
"\n",
"\n",
"## Communicating with KITT\n",
"\n",
"In this section, we cover the essential concepts required to effectively communicate with the KITT system. In the following section, we will apply these principles in practice, implementing them in the simulator and the car.\n",
"\n",
"#### pySerial\n",
"\n",
"`pySerial` is a Python module that provides a simple and efficient way to communicate with serial ports. It allows Python programs to access and manipulate the serial ports on a computer, allowing them to communicate with other devices connected to those ports. With `pySerial`, you can easily send and receive data to/from these devices. `pySerial` is cross-platform and works on Windows, macOS, and Linux operating systems.\n",
"\n",
"- `Serial(port, baudrate)` - This command initializes a serial connection. The `port` argument specifies the serial port to use (e.g., \"COM1\" on Windows or \"/dev/rfcomm0\" on Linux), and the `baudrate` argument specifies the data rate in bits per second.\n",
"- `serial.write(data)` - This command sends data over the serial connection. The `data` argument is a bytes object that contains the data to be sent.\n",
"- `serial.read(size)` - This command reads a specified number of bytes from the serial connection. It blocks until the specified number of bytes are received.\n",
"- `serial.read_until(bytes)` - This command reads bytes from the serial connection until the specified byte sequence is found. It blocks until then.\n",
"- `serial.flush()` - This command is used to flush the input and output buffers of the serial connection.\n",
"- `serial.close()` - This command is used to close the serial connection.\n",
"\n",
"More detailed and updated information regarding the pySerial module can also be found through this link: [pySerial Documentation](https://pyserial.readthedocs.io/en/latest/pyserial.html). See the FAQ at the end of this file for some further explanations on serial communication. Regarding the use of the Serial(port,baudrate) command for our purposes, more specific information appears at the end of this file under \"Connecting to KITT\".\n",
"\n",
"#### Simulated Serial Port\n",
"\n",
"The KITT simulator (car model) is also controlled by a serial port. This port uses the following functions:\n",
"\n",
"- `Serial(port, baudrate)`\n",
"- `serial.write(data)`\n",
"\n",
"These functions work identically to the `pySerial` functions, except that they communicate with the simulated KITT and not the real KITT.\n",
"\n",
"#### Controlling KITT\n",
"\n",
"After connecting to KITT, several commands are available to control it: driving commands, audio beacon commands, and status commands.\n",
"\n",
"**Driving Commands:** For driving, there are two types of instructions:\n",
"\n",
"- A direction command: `D`. Example: `D130`\n",
"- A motor speed command: `M`. Example: `M160`\n",
"\n",
"These control the motors using Pulse Width Modulation (PWM). Both commands are neutral at a setting of 150. The direction commands range from 200 (hard left) to 100 (hard right), and the motor commands range from 135 (backward) to 165 (forward).\n",
"\n",
"**Note:** There is a dead zone for the motor commands, so KITT will likely not start moving forward until the PWM is set to about 153. It is recommended to experiment with these values; they are also battery-dependent. You should also verify that 150 is the middle position for steering (sometimes there is a deviation of ±2), and test the maximum left and right positions.\n",
"\n",
"All the commands are sent in binary over the serial link and end with a new line character.\n",
"\n",
"```python\n",
"serial.write(b'code\\n')\n",
"```\n",
"\n",
"To set direction to 130 and motor speed to 160, it can be done as follows:\n",
"\n",
"```python\n",
"serial.write(b'D130\\n')\n",
"serial.write(b'M160\\n')\n",
"```\n",
"\n",
"**Note:** Once you set the motor speed commands, KITT will continue to act on this until you either transmit a new motor speed command or reset the MCU using the button on KITT, which will set both direction and motor speed commands to the neutral value of 150. To be safe, include suitable stop commands in your scripts.\n",
"\n",
"**Simulator Example:** Let's see this in the simulator:"
]
},
{
"metadata": {},
"cell_type": "code",
"source": [
"### Student Version: play with this! ###\n",
"\n",
"# Open the serial port with the correct port and baud rate\n",
"# Replace '/dev/ttyUSB0' with your actual serial port\n",
"serial = Serial('/dev/rfcomm2', 115200)\n",
"\n",
"# Allow time for any processing delays\n",
"time.sleep(1)\n",
"\n",
"# TODO: Set a motor speed and direction\n",
"# Use serial.write() to send the motor speed command\n",
"# Example: serial.write(b'M<speed_value>\\n')\n",
"# Use serial.write() to send the direction command to turn left\n",
"# Example: serial.write(b'D<direction_value>\\n')\n",
"# Hint: To turn left, set the direction value greater than 150 (neutral position)\n",
"\n",
"serial.write(b\"D150\\n\")\n",
"\n",
"serial.write(b\"M160\\n\")\n",
"\n",
"print(\"Motors are ON\")\n",
"\n",
"# Wait for some time to observe the car's movement\n",
"time.sleep(10)\n",
"\n",
"# Stop the car by setting motor speed to neutral (150)\n",
"# Use serial.write() to send the stop command\n",
"serial.write(b'M150\\n')\n",
"print(\"Motors are OFF\")\n",
"\n",
"# Wait to observe the car slowing down\n",
"time.sleep(5) # Notice the car keeps moving due to inertia\n",
"\n",
"# Close the serial connection (important!)\n",
"serial.close()"
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Additional Information:**\n",
"\n",
"*To make the car turn left instead of right, adjust the direction value in the `D` command accordingly. Remember that direction values range from 100 (hard right) to 200 (hard left), with 150 being neutral.*\n",
"\n",
"Also, notice that the car keeps moving after the motors are off. This is because of the car's dynamics, which will be explored in a later module.\n",
"\n",
"**Audio Beacon**\n",
"\n",
"The car has an audio beacon which will be used to localize the car. \n",
"The audio beacon, once switched on, continuously transmits a sequence of pulses. It is controlled by a programmable microcontroller on the car, and several parameters can be set. \n",
"\n",
"The pulse sequence is a modulated binary code sequence with “on-off keying” (OOK). If a bit in the\n",
"sequence is 0, nothing is transmitted; if the bit is 1, a modulation carrier frequency is transmitted during\n",
"a certain period. The bit sequence (code word) is programmable.\n",
"\n",
"Other parameters that determine the signal are\n",
"\n",
"- Modulation carrier frequency (parameter Timer0), at most 30 kHz, although this is probably beyond the specs of the loudspeaker and microphones;\n",
"- Duration of a single bit (parameter Timer1), this defines the rate at which the modulation carrier signal is switched on or off by the bits in the code word, and determines the bandwidth of the signal (at most 5 kHz);\n",
"- Repetition rate of the bit sequence (parameter Timer3). After the code sequence has been played over the loudspeaker, it will be silent for a certain period, and then the sequence is transmitted again, at a rate determined by Timer 3.\n",
"\n",
"The beacon parameters can be programmed using the following commands:\n",
"\n",
"- `F`: Set transmission frequency in Hz (controls Timer 0) - carrier frequency\n",
"- `B`: Set bit frequency in Hz (controls Timer 1) - determines the signal bandwidth\n",
"- `R`: Set repetition count (controls Timer 3) - determines the number of pulses per second\n",
"- `C`: Set 32-bit code - 8 characters representing Hex values\n",
"- `A`: Turn beacon on/off\n",
"\n",
"You can set the beacon parameters using the following commands:\n",
"\n",
"```python\n",
"serial.write(b'F10000\\n')\n",
"serial.write(b'B5000\\n')\n",
"serial.write(b'R2500\\n') \n",
"serial.write(b'C12345678\\n')\n",
"serial.write(b'A1\\n') # Turn beacon on\n",
"serial.write(b'A0\\n') # Turn beacon off\n",
"```\n",
"\n",
"<img src=\"pictures/Beacon.png\" alt=\"Illustration of the transmitted signal\" width=\"400\" />\n",
"\n",
"The repetition count formula is as follows:\n",
"\n",
"$$\\text{Repetition Count} = \\frac{\\text{Bit Frequency}}{\\text{Repetition Frequency}}$$\n",
"\n",
"For example, if the bit frequency is 5000 Hz, the repetition count should be 2500 to sound the beacon twice per second.\n",
"\n",
"Later, you will use this beacon to determine where your car is on the field. Remember that all the numbers provided here serve only as examples. It is up to you to determine what carrier frequency, code word, etc., best fits the goal of succeeding at the final challenge.\n",
"\n",
"Let's use these in the simulator. Unfortunately, the sound of the car cannot be heard in the simulation. A blue circle will appear on the car to indicate the when the beacon is on."
]
},
{
"metadata": {},
"cell_type": "code",
"source": [
"### Student Version ###\n",
"\n",
"# Open the serial port with the correct port and baud rate\n",
"# Replace '/dev/ttyUSB0' with your actual serial port\n",
"serial = Serial('/dev/rfcomm2', 115200)\n",
"\n",
"# Set carrier frequency\n",
"# Define the desired carrier frequency in Hz (e.g., 10000)\n",
"carrier_frequency_value = 10000 # Replace with your value\n",
"# Convert the frequency to bytes (2 bytes, big endian)\n",
"carrier_frequency = carrier_frequency_value.to_bytes(2, byteorder='big')\n",
"# TODO: Send the 'F' command with the carrier frequency\n",
"serial.write(b\"\".join([b\"F\",carrier_frequency,b\"\\n\"]))\n",
"# Set bit frequency\n",
"# Define the desired bit frequency in Hz (e.g., 5000)\n",
"bit_frequency_value = 5000 # Replace with your value\n",
"# Convert the bit frequency to bytes\n",
"bit_frequency = bit_frequency_value.to_bytes(2, byteorder='big')\n",
"# TODO: Send the 'B' command with the bit frequency\n",
"serial.write(b\"\".join([b\"B\",bit_frequency,b\"\\n\"]))\n",
"\n",
"# Set repetition count\n",
"# Calculate the repetition count (should be at least 32)\n",
"# Repetition Count = Bit Frequency / Repetition Frequency\n",
"desired_repetition_frequency = 2 # Replace with your value\n",
"repetition_count_value = bit_frequency_value // desired_repetition_frequency\n",
"# Ensure repetition_count_value is at least 32\n",
"repetition_count_value = max(repetition_count_value, 32)\n",
"# Convert the repetition count to bytes\n",
"repetition_count = repetition_count_value.to_bytes(2, byteorder='big')\n",
"# TODO: Send the 'R' command with the repetition count\n",
"\n",
"serial.write(b\"\".join([b\"R\",repetition_count,b\"\\n\"]))\n",
"\n",
"# Set code pattern\n",
"# Define a 32-bit code in hexadecimal (e.g., 0x12345678)\n",
"code_value = 0x99999999 # Replace with your code\n",
"# Convert the code to bytes (4 bytes, big endian)\n",
"code = code_value.to_bytes(4, byteorder='big')\n",
"# TODO: Send the 'C' command with the code\n",
"serial.write(b\"\".join([b\"C\",code,b\"\\n\"]))\n",
"\n",
"# TODO: Allow some time for the settings to take effect\n",
"time.sleep(1)\n",
"# TODO: Turn the beacon ON by sending the 'A1' command\n",
"print(\"Beacon is ON\")\n",
"serial.write(b\"A1\\n\")\n",
"# Wait while the beacon is on\n",
"time.sleep(3)\n",
"\n",
"# TODO: Turn the beacon OFF by sending the 'A0' command\n",
"print(\"Beacon is OFF\")\n",
"serial.write(b\"A0\\n\")\n",
"\n",
"# Allow some time before closing\n",
"time.sleep(1)\n",
"# TODO: Very Important! Close the serial connection\n",
"serial.close()\n"
],
"outputs": [],
"execution_count": null
},
{
"metadata": {},
"cell_type": "markdown",
"source": [
"**Additional Information:**\n",
"\n",
"*Remember to choose appropriate values for the carrier frequency, bit frequency, and code pattern. The carrier frequency should not exceed 20 kHz, and the bit frequency should be smaller than the carrier frequency. The code pattern is a 32-bit value that will be transmitted bit-wise over the beacon.*\n",
"\n",
"**Caution:** Be aware that the default code word for the beacon is `0x00000000`, which means KITT will not start making noise on its own when the beacon is turned on. You should specify a code as described above before you can hear the beacon make noise. Furthermore, an arbitrary carrier frequency, bit frequency, and repetition count can be used, but not all combinations make sense. (The carrier frequency should not be above 20 kHz; the bit frequency should be smaller than the carrier frequency.)\n",
"\n",
"**Status Command**\n",
"\n",
"Lastly, the status command, which requests the status and all the data from KITT, is `S`. The status string reports the current drive commands, the ultrasonic sensor distance (cm), the battery voltage (mV), the audio beacon status (on/off), and control parameters (code word, carrier frequency, bit frequency, repetition count). A sensor distance of 999 means overload (i.e., out of range).\n",
"\n",
"**Additional Information:**\n",
"\n",
"You can request information from KITT by sending one of the following commands:\n",
"\n",
"- **For all information**: Use the command:\n",
" ```python\n",
" serial.write(b'S\\n')\n",
" ```\n",
"\n",
"- **For distance measurements only**: Use the command:\n",
" ```python\n",
" serial.write(b'Sd\\n')\n",
" ```\n",
"\n",
"- **For battery voltage**: Use the command:\n",
" ```python\n",
" serial.write(b'Sv\\n')\n",
" ```\n",
"\n",
"- **For version information**: Use the command:\n",
" ```python\n",
" serial.write(b'V\\n')\n",
" ```\n",
"\n",
"After sending each command, you have to read out what KITT sends back. The response is a string of bytes that you should best decode before interpreting it.:\n",
"\n",
"```python\n",
"status = serial.read_until(b'\\n')\n",
"status = status.decode('utf-8')\n",
"print(status)\n",
"```"
]
},
{
"metadata": {},
"cell_type": "code",
"source": [
"### Student Version ###\n",
"\n",
"# TODO: Open the serial port with the correct port and baud rate\n",
"serial = Serial(\"/dev/rfcomm2\", 115200)\n",
"# TODO: Send the status command 'S\\n'\n",
"serial.write(b'S\\n')\n",
"# TODO: Read the response until the End-of-Transmission character (EOT): '\\x04'\n",
"status = serial.read_until(b\"\\x04\")\n",
"# TODO: Decode the status information received from the car\n",
"status = status.decode(\"utf-8\")\n",
"# Print the status information received from the car\n",
"print(f\"Car status is:\\n\\n{status}\")\n",
"\n",
"serial.close()\n",
"# TODO: Close the serial connection (important!)"
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Implementing a Basic KITT Control Script\n",
"\n",
"Now you can start writing a Python program to control the simulator or the car connected over Bluetooth. This program will allow you to control the car using the keyboard keys `w`, `a`, `s`, and `d`. The `e` and `r` keys will start and stop the audio beacon, respectively. Additionally, include a key to stop communication with KITT.\n",
"\n",
"The code block below gives a template for you to start with. We begin by defining the `KITT` class and some essential methods for communication. Classes define the structure and capabilities of objects, allowing you to create multiple instances with the same properties and methods. Next, you will complete the `keyboard_control` function. We'll use a simple input loop to read keyboard commands. You will finally call the function and test your code first on the simulator and then on the real car.\n",
"\n",
"### Explanation of the Code\n",
"\n",
"**KITT Class:**\n",
"\n",
"- **`__init__` Method:**\n",
" - Initializes the serial communication with the specified port and baud rate.\n",
" - Sets up the beacon parameters using the existing `send_command` method.\n",
"- **`send_command` Method:**\n",
" - Sends a given command to KITT over the established serial connection.\n",
"- **`set_speed` and `set_angle` Methods:**\n",
" - Adjust KITTs speed and steering angle, respectively.\n",
"- **`stop` Method:**\n",
" - Brings KITT to a halt by setting both speed and angle to their neutral states.\n",
"- **`start_beacon` and `stop_beacon` Methods:**\n",
" - Control the audio beacon by sending the appropriate commands.\n",
"- **`close` Method:**\n",
" - Ensures the proper closure of the serial communication when you're done using KITT.\n",
"\n",
"**`keyboard_control` Function:**\n",
"\n",
"- Designed as a continuous loop that reads keyboard commands from the user input.\n",
"- Interprets the input command and adjusts KITTs speed and steering angle or toggles the beacon accordingly.\n",
"- **Key Mappings:**\n",
" - `w`: Accelerate KITT forward.\n",
" - `s`: Decelerate or move KITT backward.\n",
" - `d`: Turn KITT right.\n",
" - `a`: Turn KITT left.\n",
" - `e`: Start the audio beacon.\n",
" - `r`: Stop the audio beacon.\n",
" - `q`: Stop communication with KITT and exit the program.\n",
"- Ensures that speed and steering values stay within the valid ranges.\n",
"- Handles exceptions and ensures resources are properly closed when exiting.\n",
"\n",
"**Test Code:**\n",
"\n",
"- The `if __name__ == \"__main__\"` block seen in the example code below contains the code that will execute when you run your script. This `if` construct allows the script to contain test code that will run only if the script is called directly.\n",
"- An instance of `KITT` is created with the appropriate serial port.\n",
"- The `keyboard_control` function is called with the `kitt` instance to start controlling the car.\n",
"\n",
"**Steps:**\n",
"\n",
"1. **Modify the `__init__` Method:**\n",
" - When the communication with KITT is started, initialize the beacon with the correct set of parameters using the existing `serial` port and `send_command`.\n",
"2. **Add `start_beacon` and `stop_beacon` Methods:**\n",
" - These methods will turn the beacon on or off. Since you set the beacon parameters during `__init__`, there is no need to resend them every time you turn the beacon on.\n",
"3. **Implemented a Simple Input Loop:**\n",
" - Using `input()` to read commands avoids issues with keybindings in VSCode or Jupyter notebooks.\n",
" - This method is more compatible and less likely to cause kernel crashes.\n",
"4. **Handled Exceptions and Resource Cleanup:**\n",
" - Added a `try...except...finally` block to handle any exceptions that might occur.\n",
" - Ensured that the car is stopped and the serial connection is closed when exiting the program."
]
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-11-19T20:47:25.030226Z",
"start_time": "2025-11-19T20:47:25.017941Z"
}
},
"cell_type": "code",
"source": [
"### Student Version ###\n",
"\n",
"class KITT:\n",
" def __init__(self, port, baudrate=115200):\n",
" # Initialize the serial connection\n",
" # self.serial = Serial(port, baudrate, rtscts=True)\n",
" self.serial = Serial(port, baudrate,rtscts=True)\n",
" # Initialize beacon parameters here using send_command\n",
" # Set carrier frequency, bit frequency, repetition count, and code pattern\n",
" carrier_frequency_value = 10000\n",
" carrier_frequency = carrier_frequency_value.to_bytes(2, byteorder='big')\n",
" self.send_command(str(b\"\".join([b\"F\",carrier_frequency,b\"\\n\"])))\n",
"\n",
"\n",
" bit_frequency_value = 5000\n",
" bit_frequency = bit_frequency_value.to_bytes(2, byteorder='big')\n",
" self.send_command(str(b\"\".join([b\"B\",bit_frequency,b\"\\n\"])))\n",
"\n",
"\n",
" desired_repetition_frequency = 2 # Replace with your value\n",
" repetition_count_value = bit_frequency_value // desired_repetition_frequency\n",
" repetition_count = repetition_count_value.to_bytes(2, byteorder='big')\n",
" # Ensure repetition_count_value is at least 32\n",
" repetition_count_value = max(repetition_count_value, 32)\n",
" repetition_count = repetition_count_value.to_bytes(2, byteorder='big')\n",
" self.send_command(str(b\"\".join([b\"R\",repetition_count,b\"\\n\"])))\n",
"\n",
" code_value = 0x99999999 # Replace with your code\n",
" code = code_value.to_bytes(4, byteorder='big')\n",
" self.send_command(str(b\"\".join([b\"C\",code,b\"\\n\"])))\n",
"\n",
" def send_command(self, command):\n",
" # Send the command string over the serial connection\n",
" self.serial.write(command.encode())\n",
"\n",
"\n",
" def set_speed(self, speed):\n",
" # Send the motor speed command using send_command\n",
" # Use the format 'M<speed>\\n'\n",
" self.send_command(f\"M{speed}\\n\")\n",
"\n",
"\n",
" def set_angle(self, angle):\n",
" # Send the steering angle command using send_command\n",
" # Use the format 'D<angle>\\n'\n",
" self.send_command(f\"D{angle}\\n\")\n",
"\n",
"\n",
" def stop(self):\n",
" # Stop the car by setting speed and angle to neutral (150)\n",
" # Call set_speed and set_angle with 150\n",
" self.set_speed(150)\n",
" self.set_angle(150)\n",
"\n",
" def start_beacon(self):\n",
" # Send commands to start the beacon\n",
" # Use the command 'A1\\n'\n",
" self.send_command(\"A1\\n\")\n",
"\n",
" def stop_beacon(self):\n",
" # Send commands to stop the beacon\n",
" # Use the command 'A0\\n'\n",
" self.send_command(\"A0\\n\")\n",
"\n",
" def close(self):\n",
" # Close the serial connection\n",
" # self.serial.close()\n",
" self.serial.close()\n"
],
"outputs": [],
"execution_count": 20
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-11-19T20:47:26.839827Z",
"start_time": "2025-11-19T20:47:26.826565Z"
}
},
"cell_type": "code",
"source": [
"### Student Version ###\n",
"\n",
"def wasd_control(kitt):\n",
" print(\"Control the car with W (forward), S (backward), A (left), D (right), E (start beacon), R (stop beacon), and Q (to exit).\")\n",
"\n",
" car_speed = 150 # Neutral speed\n",
" car_steering = 150 # Neutral steering\n",
"\n",
" try:\n",
" while True:\n",
" # Read input from the user\n",
" command = input(\"Enter command: \").lower()\n",
" if command == \"w\":\n",
" car_speed = car_speed + 5\n",
" elif command == \"s\":\n",
" car_speed -=5\n",
" elif command == \"a\":\n",
" car_steering +=5\n",
" elif command == \"d\":\n",
" car_steering -=5\n",
" elif command == \"e\":\n",
" kitt.start_beacon()\n",
" elif command == \"r\":\n",
" kitt.stop_beacon()\n",
" elif command == \"q\":\n",
" kitt.close()\n",
" break\n",
"\n",
" # TODO: depending on the command, adjust the car speed, steering angle, or start/stop the beacon variable\n",
"\n",
" # Ensure speed and steering values are within valid ranges\n",
" car_speed = min(max(car_speed, 135), 165)\n",
" car_steering = min(max(car_steering, 100), 200)\n",
"\n",
" # Send the speed and angle to KITT\n",
" kitt.set_speed(car_speed)\n",
" kitt.set_angle(car_steering)\n",
"\n",
" except Exception as e:\n",
" # Handle any exceptions\n",
" print(f\"An error occurred: {e}\")\n",
" finally:\n",
" # Ensure the car is stopped and serial connection is closed\n",
" kitt.stop()\n",
" kitt.close()"
],
"outputs": [],
"execution_count": 21
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-11-19T20:47:41.559047Z",
"start_time": "2025-11-19T20:47:30.566425Z"
}
},
"cell_type": "code",
"source": [
"### Student Version ###\n",
"\n",
"if __name__ == \"__main__\":\n",
" # Create an instance of KITT with the correct serial port\n",
" # Replace '/dev/ttyUSB0' with your actual serial port\n",
" kitt = KITT('/dev/rfcomm2') # Replace with the actual port\n",
"\n",
" # Start the control function\n",
" wasd_control(kitt)"
],
"outputs": [
{
"data": {
"text/plain": [
"Canvas(height=520, width=520)"
],
"application/vnd.jupyter.widget-view+json": {
"version_major": 2,
"version_minor": 0,
"model_id": "bf6b02cc69244330a431bf392738a083"
}
},
"metadata": {},
"output_type": "display_data",
"jetTransient": {
"display_id": null
}
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Control the car with W (forward), S (backward), A (left), D (right), E (start beacon), R (stop beacon), and Q (to exit).\n"
]
},
{
"ename": "KeyboardInterrupt",
"evalue": "Interrupted by user",
"output_type": "error",
"traceback": [
"\u001B[31m---------------------------------------------------------------------------\u001B[39m",
"\u001B[31mKeyboardInterrupt\u001B[39m Traceback (most recent call last)",
"\u001B[36mCell\u001B[39m\u001B[36m \u001B[39m\u001B[32mIn[22]\u001B[39m\u001B[32m, line 9\u001B[39m\n\u001B[32m 6\u001B[39m kitt = KITT(\u001B[33m'\u001B[39m\u001B[33m/dev/rfcomm2\u001B[39m\u001B[33m'\u001B[39m) \u001B[38;5;66;03m# Replace with the actual port\u001B[39;00m\n\u001B[32m 8\u001B[39m \u001B[38;5;66;03m# Start the control function\u001B[39;00m\n\u001B[32m----> \u001B[39m\u001B[32m9\u001B[39m \u001B[43mwasd_control\u001B[49m\u001B[43m(\u001B[49m\u001B[43mkitt\u001B[49m\u001B[43m)\u001B[49m\n",
"\u001B[36mCell\u001B[39m\u001B[36m \u001B[39m\u001B[32mIn[21]\u001B[39m\u001B[32m, line 12\u001B[39m, in \u001B[36mwasd_control\u001B[39m\u001B[34m(kitt)\u001B[39m\n\u001B[32m 9\u001B[39m \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[32m 10\u001B[39m \u001B[38;5;28;01mwhile\u001B[39;00m \u001B[38;5;28;01mTrue\u001B[39;00m:\n\u001B[32m 11\u001B[39m \u001B[38;5;66;03m# Read input from the user\u001B[39;00m\n\u001B[32m---> \u001B[39m\u001B[32m12\u001B[39m command = \u001B[38;5;28;43minput\u001B[39;49m\u001B[43m(\u001B[49m\u001B[33;43m\"\u001B[39;49m\u001B[33;43mEnter command: \u001B[39;49m\u001B[33;43m\"\u001B[39;49m\u001B[43m)\u001B[49m.lower()\n\u001B[32m 13\u001B[39m \u001B[38;5;28;01mif\u001B[39;00m command == \u001B[33m\"\u001B[39m\u001B[33mw\u001B[39m\u001B[33m\"\u001B[39m:\n\u001B[32m 14\u001B[39m car_speed = car_speed + \u001B[32m5\u001B[39m\n",
"\u001B[36mFile \u001B[39m\u001B[32m~/Documents/EE/Y2/IP3/A.K.03/.venv/lib/python3.13/site-packages/ipykernel/kernelbase.py:1396\u001B[39m, in \u001B[36mKernel.raw_input\u001B[39m\u001B[34m(self, prompt)\u001B[39m\n\u001B[32m 1394\u001B[39m msg = \u001B[33m\"\u001B[39m\u001B[33mraw_input was called, but this frontend does not support input requests.\u001B[39m\u001B[33m\"\u001B[39m\n\u001B[32m 1395\u001B[39m \u001B[38;5;28;01mraise\u001B[39;00m StdinNotImplementedError(msg)\n\u001B[32m-> \u001B[39m\u001B[32m1396\u001B[39m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;43mself\u001B[39;49m\u001B[43m.\u001B[49m\u001B[43m_input_request\u001B[49m\u001B[43m(\u001B[49m\n\u001B[32m 1397\u001B[39m \u001B[43m \u001B[49m\u001B[38;5;28;43mstr\u001B[39;49m\u001B[43m(\u001B[49m\u001B[43mprompt\u001B[49m\u001B[43m)\u001B[49m\u001B[43m,\u001B[49m\n\u001B[32m 1398\u001B[39m \u001B[43m \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[43m.\u001B[49m\u001B[43m_get_shell_context_var\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[43m.\u001B[49m\u001B[43m_shell_parent_ident\u001B[49m\u001B[43m)\u001B[49m\u001B[43m,\u001B[49m\n\u001B[32m 1399\u001B[39m \u001B[43m \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[43m.\u001B[49m\u001B[43mget_parent\u001B[49m\u001B[43m(\u001B[49m\u001B[33;43m\"\u001B[39;49m\u001B[33;43mshell\u001B[39;49m\u001B[33;43m\"\u001B[39;49m\u001B[43m)\u001B[49m\u001B[43m,\u001B[49m\n\u001B[32m 1400\u001B[39m \u001B[43m \u001B[49m\u001B[43mpassword\u001B[49m\u001B[43m=\u001B[49m\u001B[38;5;28;43;01mFalse\u001B[39;49;00m\u001B[43m,\u001B[49m\n\u001B[32m 1401\u001B[39m \u001B[43m\u001B[49m\u001B[43m)\u001B[49m\n",
"\u001B[36mFile \u001B[39m\u001B[32m~/Documents/EE/Y2/IP3/A.K.03/.venv/lib/python3.13/site-packages/ipykernel/kernelbase.py:1441\u001B[39m, in \u001B[36mKernel._input_request\u001B[39m\u001B[34m(self, prompt, ident, parent, password)\u001B[39m\n\u001B[32m 1438\u001B[39m \u001B[38;5;28;01mexcept\u001B[39;00m \u001B[38;5;167;01mKeyboardInterrupt\u001B[39;00m:\n\u001B[32m 1439\u001B[39m \u001B[38;5;66;03m# re-raise KeyboardInterrupt, to truncate traceback\u001B[39;00m\n\u001B[32m 1440\u001B[39m msg = \u001B[33m\"\u001B[39m\u001B[33mInterrupted by user\u001B[39m\u001B[33m\"\u001B[39m\n\u001B[32m-> \u001B[39m\u001B[32m1441\u001B[39m \u001B[38;5;28;01mraise\u001B[39;00m \u001B[38;5;167;01mKeyboardInterrupt\u001B[39;00m(msg) \u001B[38;5;28;01mfrom\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;28;01mNone\u001B[39;00m\n\u001B[32m 1442\u001B[39m \u001B[38;5;28;01mexcept\u001B[39;00m \u001B[38;5;167;01mException\u001B[39;00m:\n\u001B[32m 1443\u001B[39m \u001B[38;5;28mself\u001B[39m.log.warning(\u001B[33m\"\u001B[39m\u001B[33mInvalid Message:\u001B[39m\u001B[33m\"\u001B[39m, exc_info=\u001B[38;5;28;01mTrue\u001B[39;00m)\n",
"\u001B[31mKeyboardInterrupt\u001B[39m: Interrupted by user"
]
}
],
"execution_count": 22
},
{
"metadata": {},
"cell_type": "markdown",
"source": [
"#### Test the Code with the Real Car\n",
"\n",
"Now you can check and see if you can also control the real car with the code you wrote. For that, you need to restart the kernel and uncomment the appropriate `Serial` import at the top of the notebook. No code changes are required....\n",
"\n",
"However, theory and practice may differ. If you encounter any difficulties with connecting to KITT, read on.\n",
"\n",
"#### Connecting to KITT\n",
"To start with, connect the car to your computer over Bluetooth. Make sure you know the name of the Bluetooth port that the car is connected to. To access the link in Python, use:\n",
"\n",
"```python\n",
"serial_port = Serial(comport, 115200, rtscts=True)\n",
"```\n",
"\n",
"`serial_port` is an instance of `pySerial`, and this object can be used to manipulate KITT. The variable `comport` is the path to the port assigned to the Bluetooth connection with KITT. Which port specifically KITT is connected to can be found in your communication settings in Windows, etc.\n",
"\n",
"For example, if your transmission connection to KITT takes place over port 10 (Windows), then you should use:\n",
"```python\n",
"comport = 'COM10'\n",
"```\n",
"\n",
"Connecting to a serial port over Bluetooth can be tricky. Windows users, refer to Appendix B. Linux users can use `rfcomm` commands to bind the MAC address to a serial port; there is also a section about it on the [Arch Linux wiki](https://wiki.archlinux.org/title/Bluetooth#Bluetooth_serial). The MAC address of KITT always ends with the code printed on its label. For example, if the label reads DA:84, the MAC will end with `xx:xx:xx:xx:DA:84`.\n",
"\n",
"To find out which serial ports are available, you can use the following Python code:"
]
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-11-19T19:27:44.069076Z",
"start_time": "2025-11-19T19:27:44.033559Z"
}
},
"cell_type": "code",
"source": [
"import serial.tools.list_ports\n",
"\n",
"ports = list(serial.tools.list_ports.comports())\n",
"for port in ports:\n",
" print(port.device)"
],
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"/dev/ttyS31\n",
"/dev/ttyS30\n",
"/dev/ttyS29\n",
"/dev/ttyS28\n",
"/dev/ttyS27\n",
"/dev/ttyS26\n",
"/dev/ttyS25\n",
"/dev/ttyS24\n",
"/dev/ttyS23\n",
"/dev/ttyS22\n",
"/dev/ttyS21\n",
"/dev/ttyS20\n",
"/dev/ttyS19\n",
"/dev/ttyS18\n",
"/dev/ttyS17\n",
"/dev/ttyS16\n",
"/dev/ttyS15\n",
"/dev/ttyS14\n",
"/dev/ttyS13\n",
"/dev/ttyS12\n",
"/dev/ttyS11\n",
"/dev/ttyS10\n",
"/dev/ttyS9\n",
"/dev/ttyS8\n",
"/dev/ttyS7\n",
"/dev/ttyS6\n",
"/dev/ttyS5\n",
"/dev/ttyS4\n",
"/dev/ttyS3\n",
"/dev/ttyS2\n",
"/dev/ttyS1\n",
"/dev/ttyS0\n",
"/dev/rfcomm2\n",
"/dev/rfcomm1\n",
"/dev/rfcomm0\n"
]
}
],
"execution_count": 18
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Note:** Keep in mind that the Bluetooth connection can be disrupted by leaving ports open, quitting your work without closing the communication link, removing the Bluetooth dongle, or turning off the Bluetooth connection. Avoid disturbing the Bluetooth connection by ending the connection properly before doing any of the aforementioned things. If you disturb the Bluetooth link, you may need to reboot your computer to reset the operating system, which costs you valuable lab time.\n",
"\n",
"The connection must also be closed once you are done using the port:\n",
"```python\n",
"serial_port.close()\n",
"```\n",
"This will release the resources, enabling another process to use the link. \n",
"\n",
"#### Optional Extensions\n",
"\n",
"If you would like to go above and beyond with the task, here are a few tips that can help with extending the functionality of the program:\n",
"\n",
"- **Add Error Handling:** Currently, if there is an error with the Bluetooth connection or the serial communication, the program will simply crash. You could add some error handling to handle these cases more gracefully, such as printing an error message and exiting the program.\n",
"- **Add Speed Control:** Currently, the program only supports moving the car forward or backward and turning left or right. You could add support for controlling the speed of the car, such as by sending different commands to the car depending on how long the user holds down the forward or backward keys.\n",
"- **Add an Emergency Stop:** KITT doesnt have a brake; the 150 speed setting lets KITT roll out to standstill, which might take a long distance. Add an emergency brake by letting KITT drive backwards for a short period of time. (You should first detect if the previous speed setting caused KITT to move forward. You need to define a state variable to memorize the speed setting.)\n",
"\n",
"#### Mid-term Assessment and Report\n",
"\n",
"After you finish this assignment, and ultimately in week 4, showcase the functionality of your script to your assigned TA. After you pass this assessment, you are ready to document your results in your midterm report.\n",
"\n",
"For this module, you should include a chapter that covers the approach, implementation, testing, and results of the basic controller.\n",
"\n",
"Remember to document your code, using comments to define input/output variables of functions and to explain the logic and any modifications made. Your completed script will be crucial for the upcoming challenges, contributing to the overall autonomous driving system.\n",
"\n",
"After you have completed this module and have tested all the components thoroughly, start on the second part of the communication script outlined in Module 2."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "Aeu7KHKYeh_l",
"jp-MarkdownHeadingCollapsed": true
},
"source": [
"## FAQ"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"\n",
"**What is a serial communication and why is it used?**\n",
"\n",
"Serial communication is a way of transmitting data one bit at a time over a single communication line. It's commonly used to connect devices like computers and microcontrollers because it simplifies wiring and is efficient for long-distance communication. In this method, data is sent in a sequence, or \"serially,\" from the sender to the receiver, typically using standards like USB, RS-232, or UART."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"\n",
"\n",
"**What is the COM port?**\n",
"\n",
"In a Bluetooth connection, a COM port (short for \"communication port\") is a virtual port that allows your computer to communicate with Bluetooth devices as if they were connected via a traditional serial port (like those used for older wired connections). When you pair a Bluetooth device with your computer, the operating system assigns it a virtual COM port, which acts as an interface for data exchange between your computer and the Bluetooth device. This makes it easier to send and receive data over Bluetooth using standard serial communication methods.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**What is a buck converter and why is it used?**\n",
"\n",
"A buck converter is a type of DC-DC converter that steps down voltage from a higher level to a lower level while maintaining efficient power transfer. It works by rapidly switching a transistor on and off to control the energy transferred to an output capacitor, which smooths out the voltage to a lower, steady level.\n",
"\n",
"Buck converters are used because they are highly efficient, meaning they waste very little power as heat. This efficiency is particularly important in battery-powered devices, like KITT, where it's crucial to conserve energy and maintain a stable voltage for sensitive electronics. For instance, if the battery supplies 12V but the circuit requires 5V, a buck converter efficiently reduces the voltage to the needed level without generating much heat or wasting energy.\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"**I have paired the car with my PC, however the LED is still blinking; is the car connected?**\n",
"\n",
"Pairing your Bluetooth module and assigning a COM port is different from running the Python script. If you only pair the device, you'll notice that the LED continues to blink after some time. However, when you run the Python script and the virtual COM port is properly allocated, the LED will stop blinking. Simply pairing the device isn't enough to stop the LED from blinking—you need to run the Python script as instructed to see the LED become stable which means there is an actual connection."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "SMvdbS610IJG"
},
"source": [
"**What is pulse width modulation?**\n",
"\n",
"Pulse Width Modulation (PWM) is a technique used to control the amount of power delivered to an electronic device by varying the width of the pulses in a signal. It works by switching a signal on and off rapidly at a constant frequency, and by adjusting the ratio of the \"on\" time to the \"off\" time, you can control the average power delivered to a load.\n",
"\n",
"For example, if a signal is \"on\" for 50% of the time and \"off\" for the other 50%, the output will deliver 50% of the maximum power. This method is commonly used in applications like controlling the speed of motors, dimming LED lights, and generating analog signals from digital sources because it is efficient and provides precise control over power.\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "JERUY0Yu0zil"
},
"source": [
"**I've implemented the code, and everything functions correctly in the GUI and runs without errors, but the car isn't moving. What should I do?**\n",
"\n",
"Keep in mind that there's an additional switch that controls the power to the motors. If this switch is turned off, the motors won't receive any power from the battery, even if everything else is set up correctly; the car won't move. Make sure this switch is turned on."
]
}
],
"metadata": {
"colab": {
"provenance": []
},
"kernelspec": {
"display_name": "booktestenv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.4"
},
"widgets": {
"application/vnd.jupyter.widget-state+json": {}
}
},
"nbformat": 4,
"nbformat_minor": 4
}