diff --git a/Code/Control/Laptop_Code/ESP32_AT/imageTread_AT_multiple.py b/Code/Control/Laptop_Code/ESP32_AT/imageTread_AT_multiple.py
new file mode 100644
index 0000000000000000000000000000000000000000..051e15706a666ab5e0ff014303eb3d83678ed596
--- /dev/null
+++ b/Code/Control/Laptop_Code/ESP32_AT/imageTread_AT_multiple.py
@@ -0,0 +1,205 @@
+import cv2
+from urllib.request import urlopen, Request
+import numpy as np
+import time
+import math
+# import apriltag
+from pupil_apriltags import Detector
+
+detector = Detector(families='tag36h11',
+                    nthreads=1,
+                    quad_decimate=1.0,
+                    quad_sigma=0.0,
+                    refine_edges=1,
+                    decode_sharpening=0.25,
+                    debug=0)
+
+def nothing(x):
+    pass
+
+
+
+def get_AT_6DOF_info(url):
+    tid,tx,ty,tz= 0,100000,100000,0
+    rx = [0.0,0.0,0.0]
+    ry = [0.0, 0.0, 0.0]
+    rz = [0.0, 0.0, 0.0]
+
+    header = {
+        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36."}
+    req = Request(url, headers=header)
+    img_resp = urlopen(req, timeout=60)
+    imgnp = np.array(bytearray(img_resp.read()), dtype=np.uint8)
+    frame = cv2.imdecode(imgnp, -1)
+
+    # ret,frame = cap.read()
+    gray_image = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
+
+    h, w, _ = frame.shape
+    # put a dot in center of the frame
+    cv2.circle(frame, (w // 2, h // 2), 7, (255, 0, 0), -1)
+    # set camera parameters
+    fx = 800
+    fy = 600
+    cx = 400
+    cy = 300
+    AT_size = 0.16
+    results = detector.detect(gray_image, estimate_tag_pose=True, camera_params=[fx, fy, cx, cy], tag_size=AT_size)
+    debug_print = 0
+
+    Every_April_Tag_Infos = []
+
+    for r in results:
+        # extract the bounding box (x, y)-coordinates for the AprilTag
+        # and convert each of the (x, y)-coordinate pairs to integers
+        (ptA, ptB, ptC, ptD) = r.corners
+        ptB = (int(ptB[0]), int(ptB[1]))
+        ptC = (int(ptC[0]), int(ptC[1]))
+        ptD = (int(ptD[0]), int(ptD[1]))
+        ptA = (int(ptA[0]), int(ptA[1]))
+        tx, ty, tz = r.pose_t # in meters
+        rx,ry,rz = r.pose_R
+        tid = r.tag_id
+
+        # draw the bounding box of the AprilTag detection
+        cv2.line(frame, ptA, ptB, (0, 255, 0), 5)
+        cv2.line(frame, ptB, ptC, (0, 255, 0), 5)
+        cv2.line(frame, ptC, ptD, (0, 255, 0), 5)
+        cv2.line(frame, ptD, ptA, (0, 255, 0), 5)
+        # draw the center (x, y)-coordinates of the AprilTag
+        (cX, cY) = (int(r.center[0]), int(r.center[1]))
+        cv2.circle(frame, (cX, cY), 5, (0, 0, 255), -1)
+
+        # draw the tag family on the image
+
+
+        tagFamily = r.tag_family.decode("utf-8")
+
+
+        cv2.putText(frame, tagFamily, (ptA[0], ptA[1] - 15), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
+
+        cv2.putText(frame, "tx: {:.2f}  ty: {:.2f}  tz:{:.2f}".format(tx[0], ty[0], tz[0]), (ptA[0], ptA[1] + 30),
+                    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
+
+        if debug_print == 1:
+            print("tx,ty,tz:{},{},{}".format(tx, ty, tz))
+            print("cX,cY:{},{}".format(cX, cY))
+            print("[INFO] tag id: {}".format(tid))
+
+        Single_April_Tag_Info = [tid,tx,ty,tz,rx,ry,rz]
+        Every_April_Tag_Infos.append(Single_April_Tag_Info)
+
+    # show the output image after AprilTag detection
+    cv2.imshow("Image", frame)
+
+    return Every_April_Tag_Infos
+
+
+def rotationMatrixToEulerAngles(rx,ry,rz) :
+
+    sy = math.sqrt(rx[0] * rx[0] + ry[0] * ry[0])
+    roll = math.atan2(rz[1] , rz[2])
+    pitch = math.atan2(-rz[0], sy)
+    yaw = math.atan2(ry[0], rx[0])
+
+    return np.array([roll, pitch, yaw])
+
+def AT_info_list(Every_April_Tag_Infos):
+    num_of_ATs = len(Every_April_Tag_Infos)
+
+    tids = []
+    txs,tys,tzs = [],[],[]
+    rxs,rys,rzs = [],[],[]
+
+    if num_of_ATs != 0:
+        for i in range(num_of_ATs):
+            if i == 0:
+                print("========================")
+                print("=== Num of AT: {} =======".format(num_of_ATs))
+                print("========================")
+
+            tid = Every_April_Tag_Infos[i][0]
+            tx = Every_April_Tag_Infos[i][1]
+            ty = Every_April_Tag_Infos[i][2]
+            tz = Every_April_Tag_Infos[i][3]
+            rx = Every_April_Tag_Infos[i][4]
+            ry = Every_April_Tag_Infos[i][5]
+            rz = Every_April_Tag_Infos[i][6]
+
+            tids.append(tid)
+            txs.append(tx)
+            tys.append(ty)
+            tzs.append(tz)
+            rxs.append(rx)
+            rys.append(ry)
+            rzs.append(rz)
+    return tids,txs,tys,tzs,rxs,rys,rzs
+
+def get_AT_6DOF_info_list(url):
+    Every_April_Tag_Infos = get_AT_6DOF_info(url)
+    ids,txs,tys,tzs,rxs,rys,rzs = AT_info_list(Every_April_Tag_Infos)
+    return ids,txs,tys,tzs,rxs,rys,rzs
+
+if __name__ == "__main__":
+    #change the IP address below according to the
+    #IP shown in the Serial monitor of Arduino code
+    # url='http://192.168.4.1/cam-hi.jpg'
+    # url='http://192.168.1.107/cam-hi.jpg'
+    # url='http://192.168.4.1/cam-hi.jpg'
+    # url = 'http://192.168.1.118/cam-hi.jpg'
+    # url = 'http://192.168.0.176/cam-hi.jpg' # 2
+    # url = 'http://192.168.0.112/cam-hi.jpg' # 4
+    url = "http://10.0.0.3/cam-hi.jpg"
+
+    tid,tx,ty,tz,rx,ry,rz = 0,0,0,0,0,0,0
+    test_webcam = 0
+    if test_webcam == 1:
+        cap = cv2.VideoCapture(0)
+    while True:
+        # Every_April_Tag_Infos = get_AT_6DOF_info(url)
+        # ids,txs,tys,tzs,rxs,rys,rzs = AT_info_list(Every_April_Tag_Infos)
+
+        ids, txs, tys, tzs, rxs, rys, rzs = get_AT_6DOF_info_list(url)
+        print("ids:{}".format(ids))
+        print("txs:{}".format(txs))
+        print("tys:{}".format(tys))
+        print("tzs:{}".format(tzs))
+        print("rxs:{}".format(rxs))
+        print("rys:{}".format(rys))
+        print("rzs:{}".format(rzs))
+        """
+        num_of_ATs = len(Every_April_Tag_Infos)
+
+        for i in range(num_of_ATs):
+            if i == 0:
+                print("========================")
+                print("=== Num of AT: {} =======".format(num_of_ATs) )
+                print("========================")
+
+            tid = Every_April_Tag_Infos[i][0]
+            tx  = Every_April_Tag_Infos[i][1]
+            ty  = Every_April_Tag_Infos[i][2]
+            tz  = Every_April_Tag_Infos[i][3]
+            rx  = Every_April_Tag_Infos[i][4]
+            ry  = Every_April_Tag_Infos[i][5]
+            rz  = Every_April_Tag_Infos[i][6]
+
+            print("-----------------------")
+            print("tid:{}".format(tid))
+            print("tx,ty,tz:{},{},{}".format(tx,ty,tz))
+            print("rx,ry,rz:{},{},{}".format(rx,ry,rz))
+
+            # R = np.array([rx,ry,rz])
+            roll,pitch, yaw = rotationMatrixToEulerAngles(rx,ry,rz)
+            roll = roll * 180 /math.pi
+            pitch = pitch * 180 / math.pi
+            yaw = yaw * 180 / math.pi
+            print("roll,pitch,yaw:{},{},{}".format(roll, pitch, yaw))
+        """
+        key=cv2.waitKey(5)
+        if key==ord('q'):
+            break
+
+    cv2.destroyAllWindows()
+
+
diff --git a/Code/Control/Laptop_Code/constants.py b/Code/Control/Laptop_Code/constants.py
index 4bf80a33dc3d73df3ba1b13f4423b82830ae9aae..cf107ee169462aa071eba5fb4eb5f210e4060979 100644
--- a/Code/Control/Laptop_Code/constants.py
+++ b/Code/Control/Laptop_Code/constants.py
@@ -58,5 +58,5 @@ kph,kih,kdh = 1.2,0.01,0.5
 # Break between AT detection during ball seeking
 AT_detectBreak = 60
 
-
+url_AT = 'http://10.0.0.5/cam-hi.jpg'  # 1
 url_gb = 'http://10.0.0.4/cam-hi.jpg'  # 6
\ No newline at end of file
diff --git a/Code/Control/Laptop_Code/joystick/controller_pwm.py b/Code/Control/Laptop_Code/joystick/controller_pwm.py
new file mode 100644
index 0000000000000000000000000000000000000000..f2f2e22e31f7d517b92e49121fae0bdd804b7678
--- /dev/null
+++ b/Code/Control/Laptop_Code/joystick/controller_pwm.py
@@ -0,0 +1,139 @@
+import pygame
+
+def manual_vertical():
+    pwm1 = convertAxis(axes_win.get("vertical"))
+    pwm2 = pwm1
+    if pwm1 > 0:
+        dir1 = "+"
+        dir2 = "+"
+    else:
+        dir1 = "-"
+        dir2 = "-"
+
+    return abs(pwm1), abs(pwm2), dir1, dir2
+
+def manual_horizontal():
+    data_f = convertAxis(axes_win.get("forward"))
+    data_t = convertAxis(axes_win.get("turn"))
+    if abs(data_f) > abs(data_t):
+        pwm3 = data_f
+        pwm4 = pwm3
+        if pwm3 > 0:
+            dir3 = "+"
+            dir4 = "+"
+        else:
+            dir3 = "-"
+            dir4 = "-"
+    elif abs(data_f) < abs(data_t):
+        pwm3 = data_t
+        pwm4 = pwm3
+        if pwm3 > 0:
+            dir3 = "-"
+            dir4 = "+"
+        else:
+            dir3 = "+"
+            dir4 = "-"
+    else:
+        pwm3 = 0
+        pwm4 = 0
+        dir3 = "+"
+        dir4 = "-"
+
+    return abs(pwm3),abs(pwm4),dir3,dir4
+
+def manual_control():
+    pwm1,pwm2,dir1,dir2 = manual_vertical()
+    pwm3,pwm4,dir3,dir4 = manual_horizontal()
+    print("pwm1,pwm2,pwm3,pwm4:{},{},{},{}".format(pwm1,pwm2,pwm3,pwm4))
+    print("dir1,dir2,dir3,dir4:{},{},{},{}".format(dir1,dir2,dir3,dir4))
+    print("_________________________________")
+
+def rescale(oldValue, newMin=0, newMax=255):
+    oldMax = 1
+    oldMin = -1
+
+    oldRange = (oldMax - oldMin)
+    newRange = (newMax - newMin)
+    newValue = (((oldValue - oldMin) * newRange) / oldRange) + newMin
+    return int(newValue)
+
+def convertThrust(data):
+    return rescale(data, -255, 255)
+
+def convertAxis(axes):
+    data = joystick.get_axis(axes)*-1
+    pwm = convertThrust(data)
+    return pwm
+
+def go_back2_uper_level(flag_b,print_count_b):
+    if joystick.get_button(axes_win.get('button_back')):
+        flag_b = 0
+        print_count_b = 1
+    return flag_b,print_count_b
+
+def pygame_init(done):
+    for event in pygame.event.get():
+        if event.type == pygame.QUIT:
+            done = True
+    return done
+if __name__ == '__main__':
+    axes_win = dict(
+        forward = 1,
+        turn = 0,
+        vertical = 3,
+        button_a = 0, # auto control
+        button_b = 1, # manual_ball_capture
+        button_x = 2, # manual_no_ball_capture
+        button_y = 3, # let the lidar to determine ball caputure
+        button_LB = 4, # manual control
+        button_RB = 5, # stop all motors
+        button_back = 6, # go back to the first level
+        button_start = 7, # test camera core function
+    )
+    # print(axes_win.get('forward'))
+
+    # Set up joystick
+    pygame.init()
+    pygame.joystick.init()
+    done = False
+
+    flag = 0
+    print_count = 0
+
+    controller_id = 1
+    while not done:
+
+        done = pygame_init(done)
+
+        joystick = pygame.joystick.Joystick(controller_id)
+        joystick.init()
+
+        if joystick.get_button(axes_win.get('button_start')) and joystick.get_button(axes_win.get('button_back')):
+            print("kill the program")
+            done = True
+
+        elif joystick.get_button(axes_win.get('button_a')):
+            print("auto_control")
+
+        elif joystick.get_button(axes_win.get('button_RB')):
+            print("stop all motors")
+
+        elif joystick.get_button(axes_win.get('button_LB')):
+            print("manual control")
+            flag = 2
+            while (flag == 2):
+                done = pygame_init(done)
+                manual_control()
+                flag, print_count = go_back2_uper_level(flag, print_count)
+
+        elif joystick.get_button(axes_win.get('button_start')):
+            print("test the camera core function")
+
+        pygame.time.wait(100)
+
+        if print_count != 0:
+            print("No subsystem is running")
+            print_count = 0
+        pygame.time.wait(100)
+
+    pygame.quit()
\ No newline at end of file
diff --git a/Code/Control/Laptop_Code/joystick/controller_test.py b/Code/Control/Laptop_Code/joystick/controller_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..7d6eba48abb51ec334610d42607bf9174b5b9557
--- /dev/null
+++ b/Code/Control/Laptop_Code/joystick/controller_test.py
@@ -0,0 +1,147 @@
+import pygame
+
+# Define some colors
+BLACK = (0, 0, 0)
+WHITE = (255, 255, 255)
+
+
+class TextPrint(object):
+    """
+    This is a simple class that will help us print to the screen
+    It has nothing to do with the joysticks, just outputting the
+    information.
+    """
+
+    def __init__(self):
+        """ Constructor """
+        self.reset()
+        self.x_pos = 10
+        self.y_pos = 10
+        self.font = pygame.font.Font(None, 20)
+
+    def print(self, my_screen, text_string):
+        """ Draw text onto the screen. """
+        text_bitmap = self.font.render(text_string, True, BLACK)
+        my_screen.blit(text_bitmap, [self.x_pos, self.y_pos])
+        self.y_pos += self.line_height
+
+    def reset(self):
+        """ Reset text to the top of the screen. """
+        self.x_pos = 10
+        self.y_pos = 10
+        self.line_height = 15
+
+    def indent(self):
+        """ Indent the next line of text """
+        self.x_pos += 10
+
+    def unindent(self):
+        """ Unindent the next line of text """
+        self.x_pos -= 10
+
+if __name__ == '__main__':
+
+    pygame.init()
+
+    # Set the width and height of the screen [width,height]
+    size = [500, 700]
+    screen = pygame.display.set_mode(size)
+
+    pygame.display.set_caption("My Game")
+
+    # Loop until the user clicks the close button.
+    done = False
+
+    # Used to manage how fast the screen updates
+    clock = pygame.time.Clock()
+
+    # Initialize the joysticks
+    pygame.joystick.init()
+
+    # Get ready to print
+    textPrint = TextPrint()
+
+    # -------- Main Program Loop -----------
+    while not done:
+        # EVENT PROCESSING STEP
+        for event in pygame.event.get():
+            if event.type == pygame.QUIT:
+                done = True
+
+            # Possible joystick actions: JOYAXISMOTION JOYBALLMOTION JOYBUTTONDOWN
+            # JOYBUTTONUP JOYHATMOTION
+            if event.type == pygame.JOYBUTTONDOWN:
+                print("Joystick button pressed.")
+            if event.type == pygame.JOYBUTTONUP:
+                print("Joystick button released.")
+
+        # DRAWING STEP
+        # First, clear the screen to white. Don't put other drawing commands
+        # above this, or they will be erased with this command.
+        screen.fill(WHITE)
+        textPrint.reset()
+
+        # Get count of joysticks
+        joystick_count = pygame.joystick.get_count()
+
+        textPrint.print(screen, "Number of joysticks: {}".format(joystick_count))
+        textPrint.indent()
+
+        # For each joystick:
+        for i in range(joystick_count):
+            joystick = pygame.joystick.Joystick(i)
+            joystick.init()
+
+            textPrint.print(screen, "Joystick {}".format(i))
+            textPrint.indent()
+
+            # Get the name from the OS for the controller/joystick
+            name = joystick.get_name()
+            textPrint.print(screen, "Joystick name: {}".format(name))
+
+            # Usually axis run in pairs, up/down for one, and left/right for
+            # the other.
+            axes = joystick.get_numaxes()
+            textPrint.print(screen, "Number of axes: {}".format(axes))
+            textPrint.indent()
+
+            for i in range(axes):
+                axis = joystick.get_axis(i)
+                print("{:>1.0f}".format(axis))
+                textPrint.print(screen, "Axis {} value: {:>6.3f}".format(i, axis))
+            textPrint.unindent()
+
+            buttons = joystick.get_numbuttons()
+            textPrint.print(screen, "Number of buttons: {}".format(buttons))
+            textPrint.indent()
+
+            for i in range(buttons):
+                button = joystick.get_button(i)
+                textPrint.print(screen, "Button {:>2} value: {}".format(i, button))
+            textPrint.unindent()
+
+            # Hat switch. All or nothing for direction, not like joysticks.
+            # Value comes back in an array.
+            hats = joystick.get_numhats()
+            textPrint.print(screen, "Number of hats: {}".format(hats))
+            textPrint.indent()
+
+            for i in range(hats):
+                hat = joystick.get_hat(i)
+                textPrint.print(screen, "Hat {} value: {}".format(i, str(hat)))
+            textPrint.unindent()
+
+            textPrint.unindent()
+
+        # ALL CODE TO DRAW SHOULD GO ABOVE THIS COMMENT
+
+        # Go ahead and update the screen with what we've drawn.
+        pygame.display.flip()
+
+        # Limit to 60 frames per second
+        clock.tick(60)
+
+    # Close the window and quit.
+    # If you forget this line, the program will 'hang'
+    # on exit if running from IDLE.
+    pygame.quit()
\ No newline at end of file
diff --git a/Code/Control/Laptop_Code/main_joystick.py b/Code/Control/Laptop_Code/main_joystick.py
new file mode 100644
index 0000000000000000000000000000000000000000..177228dcd42b1db13fdc9edd57f23b5cc9f729ba
--- /dev/null
+++ b/Code/Control/Laptop_Code/main_joystick.py
@@ -0,0 +1,601 @@
+import pygame
+import serial
+import time
+import simple_pid.PID as PID
+from constants import *
+from ESP32_AT.imageTread_AT import get_AT_6DOF_info
+from ESP32_AT.imageTread_AT_multiple import get_AT_6DOF_info_list
+
+global mc_print,ml_on,esp_cam_on,feather_data_on
+
+mc_print = 1 # manual control print flag
+ml_on = 1
+feather_data_on = 1
+
+if ml_on == 1:
+    import ball_detection.ball_detection as ball_detection
+
+def manual_vertical():
+    pwm1 = convertAxis(axes_win.get("vertical"))
+    pwm2 = pwm1
+    if pwm1 > 0:
+        dir1 = "+"
+        dir2 = "+"
+    else:
+        dir1 = "-"
+        dir2 = "-"
+
+    return abs(pwm1), abs(pwm2), dir1, dir2
+
+def manual_horizontal():
+    data_f = convertAxis(axes_win.get("forward"))
+    data_t = convertAxis(axes_win.get("turn"))
+    if abs(data_f) > abs(data_t):
+        pwm3 = data_f
+        pwm4 = pwm3
+        if pwm3 > 0:
+            dir3 = "+"
+            dir4 = "+"
+        else:
+            dir3 = "-"
+            dir4 = "-"
+    elif abs(data_f) < abs(data_t):
+        pwm3 = data_t
+        pwm4 = pwm3
+        if pwm3 > 0:
+            dir3 = "-"
+            dir4 = "+"
+        else:
+            dir3 = "+"
+            dir4 = "-"
+    else:
+        pwm3 = 0
+        pwm4 = 0
+        dir3 = "+"
+        dir4 = "+"
+
+    return abs(pwm3),abs(pwm4),dir3,dir4
+
+def manual_control(serial_port):
+    pwm1,pwm2,dir1,dir2 = manual_vertical()
+    pwm3,pwm4,dir3,dir4 = manual_horizontal()
+    if mc_print == 1:
+        print("_______________________________________________")
+        print("pwm1,pwm2,pwm3,pwm4:{},{},{},{}".format(pwm1,pwm2,pwm3,pwm4))
+        print("dir1,dir2,dir3,dir4:{},{},{},{}".format(dir1,dir2,dir3,dir4))
+        print("_______________________________________________")
+    return pwm1, pwm2, pwm3, pwm4, dir1, dir2, dir3, dir4
+
+def rescale(oldValue, newMin=0, newMax=255):
+    oldMax = 1
+    oldMin = -1
+
+    oldRange = (oldMax - oldMin)
+    newRange = (newMax - newMin)
+    newValue = (((oldValue - oldMin) * newRange) / oldRange) + newMin
+    return int(newValue)
+
+def convertThrust(data):
+    return rescale(data, -255, 255)
+
+def convertAxis(axes):
+    data = joystick.get_axis(axes)*-1
+    pwm = convertThrust(data)
+    return pwm
+
+def go_back2_upper_level(flag_b,print_count_b):
+    if joystick.get_button(axes_win.get('button_back')):
+        flag_b = 0
+        print_count_b = 1
+    return flag_b,print_count_b
+
+def pygame_init(done):
+    for event in pygame.event.get():
+        if event.type == pygame.QUIT:
+            done = True
+    return done
+
+def stop_all():
+    pwm1, pwm2, pwm3, pwm4 = 0 , 0 , 0 , 0
+    dir1, dir2, dir3, dir4 = '+', '-', '+', '-'
+    return pwm1, pwm2, pwm3, pwm4, dir1, dir2, dir3, dir4
+
+def decode_ctl(Ctl_com):
+    pwm1 = Ctl_com[0]
+    pwm2 = Ctl_com[1]
+    pwm3 = Ctl_com[2]
+    pwm4 = Ctl_com[3]
+    dir1 = Ctl_com[4]
+    dir2 = Ctl_com[5]
+    dir3 = Ctl_com[6]
+    dir4 = Ctl_com[7]
+    return pwm1, pwm2, pwm3, pwm4, dir1, dir2, dir3, dir4
+
+def serial_port_out(serial_port, Ctl_com):
+    pwm1, pwm2, pwm3, pwm4, dir1, dir2, dir3, dir4 = decode_ctl(Ctl_com)
+    '''
+    Description:
+        Feed to ESP32_Master to send ESP32_Slave necessary information
+        the format of sending is pwm are 3 digit space
+
+    Input:
+        serial_port                                     :   serial.Serail object
+        pwm1, pwm2, pwm3, pwm4, dir1, dir2, dir3, dir4  :   variables to send
+
+    Output:
+        None
+    '''
+    output_message = ''
+
+    for pwm_itr in [pwm1, pwm2, pwm3, pwm4]:
+        # print(pwm_itr)
+        if len(str(pwm_itr)) == 2:
+            output_message += '0'
+        elif len(str(pwm_itr)) == 1:
+            output_message += '00'
+        output_message += str(pwm_itr)
+        print(pwm_itr)
+
+    output_message = output_message + dir1 + dir2 +  dir3 +  dir4 + '\n'
+    print("serial out ...")
+    print(output_message)
+    serial_port.write(output_message.encode())
+
+def test_function():
+
+    # tid, tx, ty, tz, rx, ry, rz = get_AT_6DOF_info(url_AT)
+    tids, txs, tys, tzs, rxs, rys, rzs = get_AT_6DOF_info_list(url_AT)
+    gbx, gby, gb_dist = ball_detection.detectLive(url_gb, model, minDetectionScore, showSight=True)
+
+    print("testing new function")
+    print("-----------------------")
+    # print("tid:{}".format(tid))
+    # print("tx,ty,tz:{},{},{}".format(tx,ty,tz))
+    # print("rx,ry,rz:{},{},{}".format(rx,ry,rz))
+    print("tid:{}".format(tids))
+    print("tx,ty,tz:{},{},{}".format(txs,tys,tzs))
+    print("rx,ry,rz:{},{},{}".format(rxs,rys,rzs))
+    print("gbx,gby,gb_dist:{},{},{}".format(gbx,gby,gb_dist))
+
+def auto_init(serial_port,auto_init_count,gbx, gby, gb_dist, tx, ty, tz, rx, ry, rz, LIDAR_dist1, LIDAR_dist2, debugM):
+    print("This is auto_init")
+    auto_init_count += 1
+    bcap_man = -1 # default: use lidar to determine
+    Ctl_com = main_control(gbx, gby, gb_dist, tx, ty, tz, rx, ry, rz, LIDAR_dist1, LIDAR_dist2, debugM, bcap_man)
+    serial_port_out(serial_port, Ctl_com)
+    return auto_init_count,bcap_man
+
+def auto_control(serial_port):
+    # Ctl_com = [0, 0, 0, 0, "+", "+", "+", "+"]
+
+    if ml == 1:
+        gbx, gby, gb_dist = ball_detection.detectLive(url_gb, model, minDetectionScore, showSight=True)
+        print("gbx,gby:{},{}".format(gbx, gby))
+
+    line = serial_port.readline()
+    if feather_data_on == 1:
+        if line == b'SERIAL_IN_START\r\n':
+            tx, ty, tz, rx, ry, rz, LIDAR_dist1, LIDAR_dist2, debugM = serial_port_in_v1(serial_port)
+            # only getting the Lidar data back
+            time.sleep(waitTime) # in second
+
+    if esp_cam_on == 1:
+        # ids,txs,tys,tzs,rxs,rys,rzs = get_AT_6DOF_info_list(url_AT)
+        tid, tx, ty, tz, rx, ry, rz = get_AT_6DOF_info(url_AT)
+
+    # main auto control loop
+    Ctl_com = main_control(gbx, gby, gb_dist, tx, ty, tz, rx, ry, rz, LIDAR_dist1, LIDAR_dist2, debugM, bcap_man)
+
+    return Ctl_com
+
+def manual_in_auto(Ctl_com, serial_port, flag):
+    if joystick.get_button(axes_win.get('button_LB')):
+        flag = 12
+        print("manual in auto")
+    while (flag==12):
+        done = pygame_init(done)
+        Ctl_com = manual_control()
+        serial_port_out(serial_port, Ctl_com)
+        flag = manual_return2auto(flag)
+    return flag
+
+def manual_return2auto(flag):
+    if joystick.get_button(axes_win.get('button_a')):
+        flag = 1
+    return flag
+
+def manual_ballcapture(bcap_man):
+    if joystick.get_button(axes_win.get('button_b')): # the green ball is capture
+        bcap_man = 1
+    elif joystick.get_button(axes_win.get('button_x')):
+        bcap_man = 0
+    elif joystick.get_button(axes_win.get('button_y')):
+        bcap_man = -1
+    return bcap_man
+
+def serial_port_in_v1(serial_port):
+    '''
+    Description:
+        Take all ESP32_Master serial port's printIn and take all necessary input object
+
+    Input:
+        serial_port     :    serial.Serail object
+
+    Output:
+        tx, ty, tz, rx, ry, rz, LIDAR_dist1, LIDAR_dist2, DebugM
+    '''
+
+    # DEBUG Verbose
+    print("initiating one round of serial in ...")
+
+    for i in range(8):
+        line = serial_port.readline()
+        val = int(line.decode())
+
+        if   i == 0:
+            tx = val
+        elif i == 1:
+            ty = val
+        elif i == 2:
+            tz = val
+        elif i == 3:
+            rx = val
+        elif i == 4:
+            ry = val
+        elif i == 5:
+            rz = val
+        elif i == 6:
+            LIDAR_dist1 = val
+        elif i == 7:
+            LIDAR_dist2 = val
+
+    line = serial_port.readline()
+    debugM = line.decode()
+
+    # DEBUG Verbose
+    print("tx:{}".format(tx))
+    print("ty:{}".format(ty))
+    print("tz:{}".format(tz))
+    print("rx:{}".format(rx))
+    print("ry:{}".format(ry))
+    print("rz:{}".format(rz))
+    print("LIDAR_dist1:{}".format(LIDAR_dist1))
+    print("LIDAR_dist2:{}".format(LIDAR_dist2))
+    print(debugM)
+
+    return tx, ty, tz, rx, ry, rz, LIDAR_dist1, LIDAR_dist2, debugM
+
+def main_control(gbx, gby, gb_dist, tx, ty, tz, rx, ry, rz, LIDAR_dist1, LIDAR_dist2, debugM, bcap_man):
+    print('in main_control')
+    pwm1, pwm2, pwm3, pwm4 = 0 , 0 , 0 , 0
+    dir1, dir2, dir3, dir4 = '+', '-', '+', '-'
+
+    # detection flag: ballDetect,goalDetect,ballCapture
+    ballDetect = ball_detect(gbx, gby)
+    goalDetect = goal_detect(tx, ty)
+    if bcap_man == 1:
+        ballCapture = 1  # Manually determine Ball captured
+    elif bcap_man == 0:
+        ballCapture = 0 # Ball not captured
+    elif bcap_man == -1:
+        ballCapture = ball_capture(LIDAR_dist1)
+
+    if ballCapture: # Ball captured
+        print('ballCapture TRUE')
+        if goalDetect:  # Goal detected
+            # stop_all()  # Debug
+            pwm1, pwm2, pwm3, pwm4, dir1, dir2, dir3, dir4 = move2goal(tx, ty,tz)
+        else:  # Goal not detected
+            # stop_all()  # Debug
+            pwm1, pwm2, pwm3, pwm4, dir1, dir2, dir3, dir4 = rotate_one_direction()
+    else:  # Ball not captured
+        print('ballCapture FALSE')
+        if ballDetect:  # Ball detected
+            pwm1, pwm2, pwm3, pwm4, dir1, dir2, dir3, dir4 = move2ball(gbx,gby,gb_dist)
+        else:  # Ball not detected
+            # stop_all()  # Debug
+            pwm1, pwm2, pwm3, pwm4, dir1, dir2, dir3, dir4 = rotate_one_direction()
+
+            # pwm1, pwm2, pwm3, pwm4, dir1, dir2, dir3, dir4 = ball_seeking(count_h,tx,ty)
+
+    return pwm1, pwm2, pwm3, pwm4, dir1, dir2, dir3, dir4
+
+# ====== Logic-directing Functions ====
+def ball_detect(gbx, gby):
+    '''
+    return True if green ball is detected
+    '''
+    if gbx == -1 and gby == -1:
+        return False
+    else:
+        return True
+
+def goal_detect(tx,ty):
+    '''
+    return True if April Tag is detected
+    '''
+    if tx == 100000 and ty == 100000:
+        return False
+    else:
+        return True
+
+def ball_capture(LIDAR_dist1):
+    '''
+    return True if April Tag is detected
+    '''
+    if (LIDAR_dist1 < LIDAR_Thres) and (LIDAR_dist1 > 0):  # Ball captured
+        return True
+    else:
+        return False
+
+def AT_detect(tx,ty):
+    if tx == 100000 and ty == 100000:
+        return False
+    else:
+        return True
+
+# ====== action Functions ====
+def move2goal(tx, ty,tz):
+    """
+    Description:
+        Given the center of the AT tx, ty. Call PID control to output the blimp
+        motor to manuver to the goal
+
+    Input:
+        tx    :    x component, center of April Tag
+        ty    :    y component, center of Aprol Tag
+
+    Output:
+        pwm1, pwm2, pwm3, pwm4
+        dir1, dir2, dir3, dir4
+    """
+    # April Tag Center
+    setpoint_x1 = 0.0
+    setpoint_y1 = 0.0
+
+    if tz < 2.0:
+        kpy_g,kiy_g,kdy_g =  2, 0.1, 0.5
+        base_speed = 200
+        AT_goal_Delta = -150
+    else:
+        kpy_g,kiy_g,kdy_g = 2, 0.1, 0.5
+        # kpy_g,kiy_g,kdy_g = 0.0, 0.00, 0.0
+        base_speed = 140
+        AT_goal_Delta = 0
+    inputx = tx * 100/ 1.00
+    inputy =  (ty * 100 + AT_goal_Delta) / 1.00 #
+
+    pid_x = PID(kpx_g, kix_g, kdx_g, setpoint = setpoint_x1)
+    pid_y = PID(kpy_g, kiy_g, kdy_g, setpoint = setpoint_y1)
+
+    pid_x.auto_mode = True
+    pid_x.set_auto_mode(True, last_output = 8.0)
+    pid_x.output_limits = (-255,255)
+    pid_y.output_limits = (-255,255)
+
+    outputx = pid_x(inputx)
+    outputy = pid_y(inputy)
+    print("outputy:{}".format(outputy))
+    # Vertical
+    pwm1 = abs(outputy)
+    pwm2 = abs(outputy)
+
+    if(outputy > 0):
+        dir1 = '+'
+        dir2 = '+'
+    else:
+        dir1 = '-'
+        dir2 = '-'
+
+    # Horizontal
+    lspeed = -1 * outputx + base_speed
+    rspeed =  1 * outputx + base_speed
+    pwm3 = abs(lspeed)
+    pwm4 = abs(rspeed)
+    if (lspeed > 0):
+        dir3 = '+'
+    else:
+        dir3 = '-'
+    if (rspeed > 0):
+        dir4 = '+'
+    else:
+        dir4 = '-'
+
+    return int(pwm1), int(pwm2), int(pwm3), int(pwm4), dir1, dir2, dir3, dir4
+
+def rotate_one_direction_ssp(ssp):
+    pwm1, pwm2, pwm3, pwm4 = 0, 0, ssp, ssp
+    dir1, dir2, dir3, dir4 = '+', '+', '+', '-'
+
+    return int(pwm1), int(pwm2), int(pwm3), int(pwm4), dir1, dir2, dir3, dir4
+
+def rotate_one_direction():
+    pwm1, pwm2, pwm3, pwm4 = 0, 0, seeking_speed, seeking_speed
+    dir1, dir2, dir3, dir4 = '+', '+', '+', '-'
+
+    return int(pwm1), int(pwm2), int(pwm3), int(pwm4), dir1, dir2, dir3, dir4
+
+def move_in_spiral(ssp, dir):
+    pwm1, pwm2, pwm3, pwm4 = ssp, ssp, ssp, ssp/2
+    dir1, dir2, dir3, dir4 = '+', '+', '+', '-'
+    if dir == 0:
+        dir1, dir2 = '-', '-'
+
+    return int(pwm1), int(pwm2), int(pwm3), int(pwm4), dir1, dir2, dir3, dir4
+
+def move2ball(gbx, gby, gb_dist):
+    """
+    Description:
+        Given the center of x y dist of green ball detected. Call PID control to
+        output the blimp motor to manuver to the green ball
+
+    Input:
+        gbx     :  x component, center of green ball
+        gby     :  y component, center of green ball
+        gb_dist :  distance to green ball
+
+    Output:
+        pwm1, pwm2, pwm3, pwm4
+        dir1, dir2, dir3, dir4
+    """
+    inputx = gbx / 1.00
+    inputy = gby / 1.00
+
+    # ESP-Cam Center
+    setpoint_x = 400
+    setpoint_y = 300
+
+    pid_x = PID(kpx, kix, kdx, setpoint = setpoint_x)
+    pid_y = PID(kpy, kiy, kdy, setpoint = setpoint_y)
+
+    pid_x.auto_mode = True
+    pid_x.set_auto_mode(True, last_output = 8.0)
+    pid_x.output_limits = (-255,255)
+    pid_y.output_limits = (-255,255)
+
+    outputx = pid_x(inputx)
+    outputy = pid_y(inputy)
+
+    # Vertical
+    pwm1 = abs(outputy)
+    pwm2 = abs(outputy)
+
+    if(outputy > 0):
+        dir1 = '+'
+        dir2 = '+'
+    else:
+        dir1 = '-'
+        dir2 = '-'
+
+    # Horizontal
+    lspeed = -1 * outputx + base_speed
+    rspeed =  1 * outputx + base_speed
+    pwm3 = min(abs(lspeed), 255)
+    pwm4 = min(abs(rspeed), 255)
+    if (lspeed > 0):
+        dir3 = '+'
+    else:
+        dir3 = '-'
+    if (rspeed > 0):
+        dir4 = '+'
+    else:
+        dir4 = '-'
+
+    return int(pwm1), int(pwm2), int(pwm3), int(pwm4), dir1, dir2, dir3, dir4
+
+if __name__ == '__main__':
+
+    # =========== SET UP ============
+    # Defining Variables for ESP 32 Serial I/O
+    PORT = "COM11" # for Alienware
+    serial_port = serial.Serial(PORT, 115200)
+    serial_port.close()
+    serial_port.open()
+
+    # Weit Time
+    waitTime = 0.05
+
+    # define game controller axes value
+    axes_win = dict(
+        forward = 1,
+        turn = 0,
+        vertical = 3,
+        button_a = 0, # auto control
+        button_b = 1, # manual_ball_capture
+        button_x = 2, # manual_no_ball_capture
+        button_y = 3, # let the lidar to determine ball caputure
+        button_LB = 4, # manual control
+        button_RB = 5, # stop all motors
+        button_back = 6, # go back to the first level
+        button_start = 7, # test camera core function
+    )
+    # print(axes_win.get('forward'))
+
+    # Set up joystick
+    pygame.init()
+    pygame.joystick.init()
+    done = False
+
+    # Loading the PyTorch ML model for ball detection
+    if ml == 1:
+        ball_detection.modifyCore()
+        model = ball_detection.returnModel(device, labelSet, modelLoc, modelFile)
+
+    # =========== DECLARE VARIABLES ===========
+    # game controller id
+    controller_id = 1 # default is 0 if you plug in only one game controller
+
+    # ESP CAM In
+    gbx, gby = -1, -1  # by default (-1 means no found green ball)
+    gb_dist = -1  # by default (-1 means no found green ball)
+
+    # Serial Port In
+    tx, ty, tz = 100000, 100000, 100000  # by default (0 means no found AirTag)
+    rx, ry, rz = 0, 0, 0
+    LIDAR_dist1 = 0
+    LIDAR_dist2 = 0
+    debugM = 'Testing'
+
+    # control commands
+    Ctl_com = [0, 0, 0, 0, "+", "+", "+", "+"]
+
+    # flag variables
+    flag = 0
+    print_count = 0
+    auto_init_count = 0
+    while not done:
+
+        done = pygame_init(done)
+
+        joystick = pygame.joystick.Joystick(controller_id)
+        joystick.init()
+
+        if joystick.get_button(axes_win.get('button_start')) and joystick.get_button(axes_win.get('button_back')):
+            print("kill the program")
+            done = True
+
+        elif joystick.get_button(axes_win.get('button_a')):
+            print("auto_control")
+            flag = 1
+            while (flag==1):
+                done = pygame_init(done)
+                if auto_init_count == 0:
+                    auto_init_count,bcap_man = auto_init(serial_port,auto_init_count,gbx, gby, gb_dist, tx, ty, tz, rx, ry, rz, LIDAR_dist1, LIDAR_dist2, debugM)
+                Ctl_com = auto_control(serial_port)
+                serial_port_out(serial_port, Ctl_com) # send the control data out
+                time.sleep(waitTime)
+
+                flag, print_count = go_back2_upper_level(flag, print_count) # go back to upper level by pressing "back"
+                flag = manual_in_auto(Ctl_com, serial_port, flag) # turn into manual control in auto_control
+                bcap_man = manual_ballcapture(bcap_man)    # manually determine ball capture status
+
+        elif joystick.get_button(axes_win.get('button_LB')):
+            print("manual control")
+            flag = 2
+            while (flag == 2):
+                done = pygame_init(done)
+                Ctl_com = manual_control()
+                serial_port_out(serial_port, Ctl_com)
+                flag, print_count = go_back2_upper_level(flag, print_count)
+
+        elif joystick.get_button(axes_win.get('button_start')):
+            print("test the camera core function")
+            flag = 3
+            while (flag == 3):
+                done = pygame_init(done)
+                test_function()
+                flag, print_count = go_back2_upper_level(flag, print_count)
+
+        elif joystick.get_button(axes_win.get('button_RB')):
+            print("stop all motors")
+            Ctl_com = stop_all()
+            serial_port_out(serial_port, Ctl_com)
+
+        pygame.time.wait(100)
+
+        if print_count != 0:
+            print("No subsystem is running")
+            print_count = 0
+        pygame.time.wait(100)
+
+    pygame.quit()
\ No newline at end of file