diff --git a/src/azer_robocup_project/Init_params/Real/wifi_params.json b/src/azer_robocup_project/Init_params/Real/wifi_params.json
index b9d65e3c221220750d3737cab177ca30938c7eec..40e13090b9b3cdba5a1bd47c7bf4e57d8f1843b7 100644
--- a/src/azer_robocup_project/Init_params/Real/wifi_params.json
+++ b/src/azer_robocup_project/Init_params/Real/wifi_params.json
@@ -8,5 +8,6 @@
   "MQTT_IS_ON": true,
   "MQTT_PORT": 1901,
   "MQTT_BALL_TOPIC": "starkit1/ball",
-  "MQTT_ROBOT_TOPIC": "starkit1/0/robot"
+  "MQTT_ROBOT_TOPIC": "starkit1/0/robot",
+  "MQTT_CAPTAIN_ID_TOPIC": "starkit1/captain_id"
 }
diff --git a/src/azer_robocup_project/Soccer/Localisation/class_Glob.py b/src/azer_robocup_project/Soccer/Localisation/class_Glob.py
index e0f04b767e3b31a5d68caf575fd02195983b26b1..43ab3b0c1250662453e82ef15c58b0b567f9bf8a 100644
--- a/src/azer_robocup_project/Soccer/Localisation/class_Glob.py
+++ b/src/azer_robocup_project/Soccer/Localisation/class_Glob.py
@@ -18,6 +18,7 @@ class Glob:
         self.SIMULATION = simulation             # 0 - Simulation without physics, 1 - Simulation with physics, 2 - live on openMV
         self.ball_coord =[0.0,0.0]
         self.pf_coord = [0.0,0.0,0.0]
+        self.captain_id = 1
         self.obstacles = []
         self.mqttc = None
         self.mqtt_ball_coord = None
@@ -63,10 +64,12 @@ class Glob:
                   self.mqttc = MQTTClient("openmv", self.wifi_params['HOST'] ,port=self.wifi_params['MQTT_PORT'])
                   self.ball_topic = self.wifi_params['MQTT_BALL_TOPIC'] 
                   self.robot_topic = self.wifi_params['MQTT_ROBOT_TOPIC']
+                  self.captain_id_topic = self.wifi_params['MQTT_CAPTAIN_ID_TOPIC']
                   self.mqttc.set_callback(self.mqtt_callback)
                   self.mqttc.connect()
                   self.mqttc.subscribe(self.ball_topic)
                   self.mqttc.subscribe(self.robot_topic)
+                  self.mqttc.subscribe(self.captain_id_topic)
                   import time
                   time.sleep(3000)
                   print ("Successful connection to mqtt: " , self.wifi_params['HOST']  )
@@ -101,6 +104,10 @@ class Glob:
              print("MQTT robot coordinate", self.mqtt_pf_coord)
              self.pf_coord[0] = self.mqtt_pf_coord[0]
              self.pf_coord[1] = self.mqtt_pf_coord[1]
+        elif (topic == bytes(self.captain_id_topic, 'utf-8')):
+            self.captain_id = int(msg)
+            print("Captain's id:", self.captain_id)
+
     def update_coord_by_mqtt(self):
         pin = self.pyb.Pin
         pin('P2', pin.AF_PP, pin.PULL_DOWN, af = pin.AF5_SPI2)            # pyb.Pin.AF_PP = 2, pyb.Pin.PULL_DOWN = 2, pyb.Pin.AF5_SPI2 = 5
@@ -111,17 +118,19 @@ class Glob:
         self.mqttc.connect()
         self.mqttc.subscribe(self.ball_topic)
         self.mqttc.subscribe(self.robot_topic)
+        self.mqttc.subscribe(self.captain_id_topic)
         tmp_coord = self.pf_coord
         tmp_ball = self.ball_coord
-        while (self.pf_coord == tmp_coord and self.ball_coord == tmp_ball):
+        tmp_captain_id = self.captain_id
+        while (self.pf_coord == tmp_coord and self.ball_coord == tmp_ball and self.captain_id == tmp_captain_id):
             self.mqttc.check_msg()
             print("Try get mqtt msg")
-            print('coord =', self.pf_coord, 'ball =', self.ball_coord)
+            print('coord =', self.pf_coord, 'ball =', self.ball_coord, 'captain_id =', self.captain_id)
         success_Code = True
         napravl = direction_To_Ball = math.atan2((self.ball_coord[1] - self.pf_coord[1]), (self.ball_coord[0] - self.pf_coord[0]))
         dist = math.sqrt((self.pf_coord[0] - self.ball_coord[0]) ** 2 + (self.pf_coord[1] - self.ball_coord[1]) ** 2)
 
-        return success_Code, napravl, dist
+        return success_Code, napravl, dist, self.captain_id
 
     def import_strategy_data(self, current_work_directory):
         with open(current_work_directory + "Init_params/strategy_data.json", "r") as f:
diff --git a/src/azer_robocup_project/Soccer/strategy.py b/src/azer_robocup_project/Soccer/strategy.py
index 40755d5fa2533a9cc5e290ff89850a018cd99855..d435cd3fa0761be79f32750519d61cd53d77e394 100644
--- a/src/azer_robocup_project/Soccer/strategy.py
+++ b/src/azer_robocup_project/Soccer/strategy.py
@@ -358,6 +358,13 @@ class Player():
         return yaw
 
     def forward_main_cycle(self, pressed_button):
+        id = 1
+        if self.first_pressed_button == 6:
+            id = 0
+        elif self.first_pressed_button == 2:
+            id = 1
+        elif self.first_pressed_button == 3:
+            id = 2
         self.glob.with_pf = False
         second_player_timer = self.motion.utime.time()
         self.f = Forward_Vector_Matrix(self.motion, self.local, self.glob)
@@ -386,8 +393,9 @@ class Player():
             success_Code = None
             napravl = None
             dist = None
+            captain_id = None
             if (self.glob.mqttc):
-                success_Code, napravl,  dist = self.glob.update_coord_by_mqtt()
+                success_Code, napravl,  dist, captain_id = self.glob.update_coord_by_mqtt()
             else:
                 success_Code, napravl, dist, speed = self.motion.seek_Ball_In_Pose(fast_Reaction_On = True, with_Localization = False,
                                                                               very_Fast = True)
@@ -395,35 +403,37 @@ class Player():
             # add mqtt connetction
             print("past mqttc check")
             first_look_point = self.glob.ball_coord
-            if self.glob.SIMULATION == 2 and self.glob.wifi_params['WIFI_IS_ON']: self.local.report_to_WIFI()
-            if pressed_button == 4 and (self.motion.utime.time() - second_player_timer) < 10 : continue
-            self.f.dir_To_Guest()
-            print('direction_To_Guest = ', math.degrees(self.f.direction_To_Guest), 'degrees')
-            print('coord =', self.glob.pf_coord, 'ball =', self.glob.ball_coord)
-            if dist == 0 and success_Code == False:
-                self.motion.turn_To_Course(self.glob.pf_coord[2]+ 2 * math.pi / 3)
-                continue
-            player_from_ball_yaw = coord2yaw(self.glob.pf_coord[0] - self.glob.ball_coord[0],
-                                                          self.glob.pf_coord[1] - self.glob.ball_coord[1]) - self.f.direction_To_Guest
-            player_from_ball_yaw = self.norm_yaw(player_from_ball_yaw)
-            player_in_front_of_ball = -math.pi/2 < player_from_ball_yaw < math.pi/2
-            player_in_fast_kick_position = (player_from_ball_yaw > 2.5 or player_from_ball_yaw < -2.5) and dist < 0.6
-            if dist > 0.35  and not player_in_fast_kick_position:
-                if dist > 1.5: stop_Over = True
-                else: stop_Over = False
-                direction_To_Ball = math.atan2((self.glob.ball_coord[1] - self.glob.pf_coord[1]), (self.glob.ball_coord[0] - self.glob.pf_coord[0]))
-                print('napravl :', napravl)
-                print('direction_To_Ball', direction_To_Ball)
-                #self.motion.far_distance_plan_approach(self.glob.ball_coord, self.f.direction_To_Guest, stop_Over = stop_Over)
-                self.motion.far_distance_straight_approach(self.glob.ball_coord, direction_To_Ball, stop_Over = stop_Over)
-                #self.go_Around_Ball(dist, napravl)
-                continue
-            if player_in_front_of_ball or not player_in_fast_kick_position:
-                self.go_Around_Ball(dist, napravl)
-            self.motion.turn_To_Course(self.f.direction_To_Guest)
-            small_kick = False
-            if self.f.kick_Power > 1: small_kick = True
-            success_Code = self.motion.near_distance_ball_approach_and_kick(self.f.direction_To_Guest, strong_kick = False, small_kick = small_kick, very_Fast = False)
+
+            if id == captain_id:
+                if self.glob.SIMULATION == 2 and self.glob.wifi_params['WIFI_IS_ON']: self.local.report_to_WIFI()
+                if pressed_button == 4 and (self.motion.utime.time() - second_player_timer) < 10 : continue
+                self.f.dir_To_Guest()
+                print('direction_To_Guest = ', math.degrees(self.f.direction_To_Guest), 'degrees')
+                print('coord =', self.glob.pf_coord, 'ball =', self.glob.ball_coord)
+                if dist == 0 and success_Code == False:
+                    self.motion.turn_To_Course(self.glob.pf_coord[2]+ 2 * math.pi / 3)
+                    continue
+                player_from_ball_yaw = coord2yaw(self.glob.pf_coord[0] - self.glob.ball_coord[0],
+                                                            self.glob.pf_coord[1] - self.glob.ball_coord[1]) - self.f.direction_To_Guest
+                player_from_ball_yaw = self.norm_yaw(player_from_ball_yaw)
+                player_in_front_of_ball = -math.pi/2 < player_from_ball_yaw < math.pi/2
+                player_in_fast_kick_position = (player_from_ball_yaw > 2.5 or player_from_ball_yaw < -2.5) and dist < 0.6
+                if dist > 0.35  and not player_in_fast_kick_position:
+                    if dist > 1.5: stop_Over = True
+                    else: stop_Over = False
+                    direction_To_Ball = math.atan2((self.glob.ball_coord[1] - self.glob.pf_coord[1]), (self.glob.ball_coord[0] - self.glob.pf_coord[0]))
+                    print('napravl :', napravl)
+                    print('direction_To_Ball', direction_To_Ball)
+                    #self.motion.far_distance_plan_approach(self.glob.ball_coord, self.f.direction_To_Guest, stop_Over = stop_Over)
+                    self.motion.far_distance_straight_approach(self.glob.ball_coord, direction_To_Ball, stop_Over = stop_Over)
+                    #self.go_Around_Ball(dist, napravl)
+                    continue
+                if player_in_front_of_ball or not player_in_fast_kick_position:
+                    self.go_Around_Ball(dist, napravl)
+                self.motion.turn_To_Course(self.f.direction_To_Guest)
+                small_kick = False
+                if self.f.kick_Power > 1: small_kick = True
+                success_Code = self.motion.near_distance_ball_approach_and_kick(self.f.direction_To_Guest, strong_kick = False, small_kick = small_kick, very_Fast = False)
 
     def goalkeeper1_main_cycle(self):
         def ball_position_is_dangerous(row, col):
diff --git a/src/server_main/scripts/xz_angle_from_quaternion.py b/src/server_main/scripts/xz_angle_from_quaternion.py
new file mode 100644
index 0000000000000000000000000000000000000000..056ab040cfc30c0122f21235040892dc3f5abbbd
--- /dev/null
+++ b/src/server_main/scripts/xz_angle_from_quaternion.py
@@ -0,0 +1,22 @@
+#angle between new z and old x
+
+def q_mult(q1, q2):
+    w1, x1, y1, z1 = q1
+    w2, x2, y2, z2 = q2
+    w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2
+    x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2
+    y = w1 * y2 + y1 * w2 + z1 * x2 - x1 * z2
+    z = w1 * z2 + z1 * w2 + x1 * y2 - y1 * x2
+    return w, x, y, z
+
+def q_conjugate(q):
+    w, x, y, z = q
+    return (w, -x, -y, -z)
+
+def qv_mult(q1, v1):
+    q2 = (0.0,) + v1
+    return q_mult(q_mult(q1, q2), q_conjugate(q1))[1:]
+
+def xz_angle_from_quat(quat):
+    z_vector = (0, 0, 1)
+    return qv_mult(quat, z_vector)[0]
diff --git a/src/server_main/server_main/server_node.py b/src/server_main/server_main/server_node.py
index 444e2114e8240ca6c67cdb2761e96f1088a60984..6b7517540e81e29a8ece1eccc9ebf31890fa0fb3 100644
--- a/src/server_main/server_main/server_node.py
+++ b/src/server_main/server_main/server_node.py
@@ -2,6 +2,7 @@ from ros2_aruco_interfaces.msg import ArucoMarkers
 
 from geometry_msgs.msg import Pose, PointStamped, PoseStamped, TransformStamped
 from sensor_msgs.msg import CameraInfo
+from std_msgs.msg import Int32
 from soccer_vision_2d_msgs.msg import BallArray, Ball
 import tf2_ros
 
@@ -20,6 +21,7 @@ from skspatial.objects import Line, Plane
 from pyquaternion import Quaternion
 import paho.mqtt.client as mqtt
 
+import numpy as np 
 
 class ServerNode(Node):
     def __init__(self):
@@ -58,6 +60,7 @@ class ServerNode(Node):
         self.file_to_save = "/home/root/workspace/a.out"
         self.create_publisher(PoseStamped, "/robot0_pose", 10)
         self.ball_pub = self.create_publisher(PointStamped, "/ball_point", 10)
+        self.captain_id_pub = self.create_publisher(Int32, "/captain_id", 10)
         self.declare_parameter("camera_frames", rclpy.Parameter.Type.STRING_ARRAY)
         self.camera_frames = self.get_parameter("camera_frames").value
         self.camera_models = {}  # {"frame_id": PinholeCameraModel}
@@ -75,6 +78,8 @@ class ServerNode(Node):
         self.current_aruco_poses = {}  # {if: x, y}
         self.current_robot_poses = {}
         self.world_robots_poses = {}
+        self.dists = {}
+        self.captain_id = 1
         self.init_mqqt()
 
     def on_connect(mqttc, obj, flags, rc):
@@ -94,6 +99,7 @@ class ServerNode(Node):
         self.broker = self.get_parameter("mqtt_broker").value
         self.declare_parameter("mqtt_port", 1900)
         self.port = self.get_parameter("mqtt_port").value
+        self.captain_id_topic = self.team_name + "/" + "captain_id"
         self.ball_topic = self.team_name + "/" + "ball"
         self.robots_topic = {}
         for robot_id in self.robot_ids:
@@ -213,22 +219,54 @@ class ServerNode(Node):
         shifted_pose = self.shift_pose_to_robot_centre(pose, id)
         rotated_pose = self.rotate_pose_to_forward(shifted_pose, id)
         return rotated_pose
+    
+    @staticmethod
+    def quatWAvg(Q):
+        '''
+        Averaging Quaternions.
+
+        Arguments:
+            Q(ndarray): an Mx4 ndarray of quaternions.
+            weights(list): an M elements list, a weight for each quaternion.
+        '''
+
+        # Form the symmetric accumulator matrix
+        A = np.zeros((4, 4))
+        M = Q.shape[1]
+
+        for i in range(M):
+            q = Q[:, i]
+            A += np.outer(q, q)# rank 1 update
+
+        # Get the eigenvector corresponding to largest eigen value
+        return np.linalg.eigh(A)[1][:, -1]
 
     def find_mid_pose(self, poses) -> Pose:
         mid_pose = Pose()
-        # find mid of orientation euler angles
-        for pose in poses:
+        Q_array = np.zeros((4, len(poses)))
+        # find mid of orientation and coordinates
+        for count, pose in enumerate(poses):
             mid_pose.position.x += pose.pose.position.x
             mid_pose.position.y += pose.pose.position.y
             mid_pose.position.z += pose.pose.position.z
-            mid_pose.orientation.x = pose.pose.orientation.x  # [TODO] Find medium
-            mid_pose.orientation.y = pose.pose.orientation.y
-            mid_pose.orientation.z = pose.pose.orientation.z
-            mid_pose.orientation.w = pose.pose.orientation.w
+
+            Q_array[0, count] = pose.pose.orientation.x
+            Q_array[1, count] = pose.pose.orientation.y
+            Q_array[2, count] = pose.pose.orientation.z
+            Q_array[3, count] = pose.pose.orientation.w
 
         mid_pose.position.x /= len(poses)
         mid_pose.position.y /= len(poses)
         mid_pose.position.z /= len(poses)
+
+        mid_quat = self.quatWAvg(Q_array) 
+        print('MID_quat')
+        print(mid_quat)
+        mid_pose.orientation.x = mid_quat[0]
+        mid_pose.orientation.y = mid_quat[1]
+        mid_pose.orientation.z = mid_quat[2]
+        mid_pose.orientation.w = mid_quat[3]
+
         return mid_pose
 
     def count_robots_poses(self):
@@ -255,6 +293,32 @@ class ServerNode(Node):
                 )  # transform aruco to forward
             pass
 
+        self.current_robot_poses = robots_poses  
+    
+    def compute_distances(self):
+        self.dists = {}
+        for robot_id in self.current_robot_poses.keys:
+            robot_x = self.world_robots_poses[robot_id].position.x
+            robot_y = self.world_robots_poses[robot_id].position.y
+
+            ball_x = self.ball_coordinate_world[0]
+            ball_y = self.ball_coordinate_world[1]
+            
+            self.dists[robot_id] = math.sqrt((robot_x - ball_x)**2 + (robot_y - ball_y)**2)
+    
+    def find_captain(self):
+        self.compute_distances()
+        if bool(self.dists):
+            self.captain_id = min(self.dists, key=self.dists.get)
+        else:
+            self.captain_id = 0 #goalkeeper
+    
+    def team_play(self):
+        if self.captain_id == 0: #goalkeeper
+            pass
+        else:
+            pass
+
     def update_ball_pose(self, msg):
         self.get_camera_to_world_frame()
         print(msg.header.frame_id)
@@ -300,24 +364,22 @@ class ServerNode(Node):
             ],
         )
         self.ball_coordinate_world = plane.intersect_line(line)
+        
+        #self.ball_coordinate_world = (ray_to_ball_world.point.x, ray_to_ball_world.point.y, ray_to_ball_world.point.z)
+
+    @staticmethod
+    def angle_from_quat(x, y, z, w):
+        quat = Quaternion(w, x, y, z)
+        z_vector = np.array([0., 0., 1.])
+        new_z = quat.rotate(z_vector)
+        return math.atan2(new_z[1], new_z[0])
+    
+    def get_yaw_world_rotation(self, pose:Pose) -> float:
+        return self.angle_from_quat(pose.orientation.x,pose.orientation.y,pose.orientation.z,pose.orientation.w)
+        
 
         # self.ball_coordinate_world = (ray_to_ball_world.point.x, ray_to_ball_world.point.y, ray_to_ball_world.point.z)
 
-    def get_yaw_world_rotation(self, pose: Pose) -> float:
-        x_ax = [0, 0, 1]
-        quat_arr = [
-            pose.orientation.x,
-            pose.orientation.y,
-            pose.orientation.z,
-            pose.orientation.w,
-        ]
-        quat_conj = tf_t.quaternion_conjugate(quat_arr)
-        #  rotation_matrix = tf_t.quaternion_matrix(quat_conj)
-        rotated_x = Quaternion(quat_arr).rotate(x_ax)  #  rotation_matrix @ z_ax
-        return math.atan2(
-            rotated_x[1], rotated_x[0]
-        )  # ?? https://math.stackexchange.com/questions/4191786/getting-the-rotation-angle-along-a-single-local-axis-from-a-quaternion
-
     def send_coord_to_robot(self, id):
         if id in self.world_robots_poses:
             msg = PoseStamped()
@@ -357,6 +419,17 @@ class ServerNode(Node):
             self.mqttc.publish(self.ball_topic, ball_msg, 1)
         
         self.count_robots_poses()
+
+        self.find_captain()        
+        captain_id_msg = Int32()
+        captain_id_msg.data = self.captain_id
+        self.captain_id_pub.publish(captain_id_msg)
+
+        captain_id_msg = (
+                f"{self.captain_id}"
+            )        
+        self.mqttc.publish(self.captain_id_topic, captain_id_msg, 1)
+        
         for id in self.robot_ids:
             self.send_coord_to_robot(id)
         self.mqttc.loop()
diff --git a/src/server_main/test/logger.py b/src/server_main/test/logger.py
new file mode 100644
index 0000000000000000000000000000000000000000..58c60f8e58877c92da39e8a3b9e71e711144af53
--- /dev/null
+++ b/src/server_main/test/logger.py
@@ -0,0 +1,51 @@
+
+import matplotlib.pyplot as plt
+import numpy as np
+# from collections import defaultdict
+# from multiprocessing import Process, Value
+# import json
+
+plt.style.use('seaborn') # fivethirtyeight is name of style
+plt.rcParams['font.size'] = 10
+plt.rcParams['legend.fontsize'] = 10
+plt.rc('xtick', labelsize=10) 
+plt.rc('ytick', labelsize=10)
+plt.rcParams['axes.labelsize'] = 10
+plt.rcParams['axes.titlesize'] = 10
+
+plt.rcParams['xtick.major.size'] = 5.0
+plt.rcParams['xtick.minor.size'] = 3.0
+plt.rcParams['ytick.major.size'] = 5.0
+plt.rcParams['ytick.minor.size'] = 3.0
+
+# plt.rcParams["font.family"] = "Times New Roman"
+
+class Logger:
+    def __init__(self):
+        self.yaw_log = []
+        self.plot_name = 'yaw_plot'       
+
+    def log_yaw(self, value):
+        self.yaw_log.append(value)
+
+    def init_plot_yaw(self):
+        self.fig, self.ax = plt.subplots(figsize=(30, 20))
+        # ax.tick_params(axis='both', which='major', labelsize=30)
+        self.ax.set(xlabel='step', ylabel='yaw [rad]',
+              title='Robot\'s Yaw')
+        # fig.suptitle('Yaw')
+        # ax.set_title('RobotYaw', loc='left', fontsize='large')
+        # self.ax.plot(self.yaw_log)
+        # plt.savefig(self.plot_name)
+    
+    def update_plot_yaw(self):
+        plt.ion()
+        plt.show()
+        self.ax.plot(self.yaw_log)
+        plt.pause(0.5)
+
+    def plot_all_and_save_fig(self):
+        self.ax.plot(self.yaw_log)
+        plt.savefig(self.plot_name)
+
+        
\ No newline at end of file
diff --git a/src/server_main/test/test_angle_from_quat.py b/src/server_main/test/test_angle_from_quat.py
new file mode 100644
index 0000000000000000000000000000000000000000..d31ae207946fd96f24c1adb22952b15a2b81f2c2
--- /dev/null
+++ b/src/server_main/test/test_angle_from_quat.py
@@ -0,0 +1,13 @@
+from pyquaternion import Quaternion
+import numpy as np
+import math
+
+def angle_from_quat(x, y, z, w):
+    quat = Quaternion(w, x, y, z)
+    z_vector = np.array([0., 0., 1.])
+    new_z = quat.rotate(z_vector)
+    print(new_z)
+    return math.atan2(new_z[1], new_z[0])
+
+
+print(angle_from_quat(-0.707, 0, 0, 0.707))
diff --git a/src/server_main/test/test_avg_quat.py b/src/server_main/test/test_avg_quat.py
new file mode 100644
index 0000000000000000000000000000000000000000..564c1e8b4e382bdc9021ebc5ba780bb915d207d8
--- /dev/null
+++ b/src/server_main/test/test_avg_quat.py
@@ -0,0 +1,32 @@
+import numpy as np
+def quatWAvg(Q):
+    '''
+    Averaging Quaternions.
+
+    Arguments:
+        Q(ndarray): an Mx4 ndarray of quaternions.
+        weights(list): an M elements list, a weight for each quaternion.
+    '''
+
+    # Form the symmetric accumulator matrix
+    A = np.zeros((4, 4))
+    M = Q.shape[1]
+    # wSum = 0
+
+    for i in range(M):
+        q = Q[:, i]
+        # w_i = weights[i]
+        A += np.outer(q, q)# rank 1 update
+        # wSum += w_i
+
+    # scale
+    # A /= wSum
+
+    # Get the eigenvector corresponding to largest eigen value
+    return np.linalg.eigh(A)[1][:, -1]
+
+quat_array = np.array(([[0.707, -0.383],
+                        [0,     0.0],
+                        [0,     0.0],
+                        [0.707, 0.924]]))
+print(quatWAvg(quat_array))
\ No newline at end of file
diff --git a/src/strategy/launch/game.launch.py b/src/strategy/launch/game.launch.py
new file mode 100644
index 0000000000000000000000000000000000000000..d5dbd4fdac9088455a9314209395ef922b688a27
--- /dev/null
+++ b/src/strategy/launch/game.launch.py
@@ -0,0 +1,168 @@
+from launch import LaunchDescription
+from launch_ros.actions import Node
+from launch.actions import ExecuteProcess
+import os
+from ament_index_python.packages import get_package_share_directory
+import yaml
+import json
+
+
+def load_json_file(file_path):
+    with open(file_path, "r") as file:
+        return json.load(file)
+
+
+def load_yaml_file(file_path):
+    with open(file_path, "r") as file:
+        return yaml.safe_load(file)
+
+
+def create_tf_nodes(static_transforms: dict):
+    tf_nodes = []
+    for transform in static_transforms["static_transforms"]:
+        node = Node(
+            package="tf2_ros",
+            executable="static_transform_publisher",
+            name=transform["child_frame_id"],
+            arguments=[
+                str(transform["translation"][0]),
+                str(transform["translation"][1]),
+                str(transform["translation"][2]),
+                str(transform["rotation"][0]),
+                str(transform["rotation"][1]),
+                str(transform["rotation"][2]),
+                str(transform["rotation"][3]),
+                transform["parent_frame_id"],
+                transform["child_frame_id"],
+            ],
+        )
+        tf_nodes.append(node)
+    return tf_nodes
+
+
+def create_localization_nodes(robots_config) -> list[Node]:
+    robots = load_json_file(robots_config)["robots"]
+    for robot_id in robots:
+        # preprocessor node
+        # localization node
+        pass
+
+
+def create_approach_nodes(robots_config) -> list[Node]:
+    pass
+
+
+def generate_launch_description():
+    camera_config1 = os.path.join(
+        get_package_share_directory("server_main"), "config", "camera_params.yaml"
+    )
+    camera_config2 = os.path.join(
+        get_package_share_directory("server_main"), "config", "camera_params2.yaml"
+    )
+    robot_config = os.path.join(
+        get_package_share_directory("server_main"), "config", "robots_config.json"
+    )
+    yolo_dir = get_package_share_directory("yolo_openvino_ros2")
+    yolo_config_dir = yolo_dir + "/config/"
+
+    tf_config = os.path.join(
+        get_package_share_directory("calibration_node"),
+        "config",
+        "static_transforms.yaml",
+    )
+
+    static_transforms = load_yaml_file(tf_config)
+    tf_nodes = create_tf_nodes(static_transforms)
+    localization_nodes = create_localization_nodes(robot_config)
+    approach_nodes = create_approach_nodes(robot_config)
+
+    return LaunchDescription(
+        [
+            Node(
+                package="usb_cam",
+                executable="usb_cam_node_exe",
+                parameters=[camera_config1],
+                output="screen",
+                emulate_tty=True,
+                namespace="camera1",
+            ),
+            Node(
+                package="ros2_aruco",
+                executable="aruco_node",
+                parameters=[
+                    {"camera_frame": "camera1"},
+                    {"aruco_dictionary_id": "DICT_4X4_100"},
+                    {"marker_size": 0.07},
+                ],
+                remappings=[
+                    ("/camera/image_raw", "/camera1/image_raw"),
+                    ("/camera/camera_info", "/camera1/camera_info"),
+                ],
+                output="screen",
+                emulate_tty=True,
+            ),
+            Node(
+                package="yolo_openvino_ros2",
+                executable="yolo_openvino_node",
+                name="yolo_openvino_node",
+                remappings=[
+                    ("/ball_on_image", "/camera1/ball_on_image"),
+                    ("/image_raw", "/camera1/image_raw"),
+                ],
+                parameters=[os.path.join(yolo_config_dir, "yolo_roma.yaml")],
+            ),
+            Node(
+                package="ros2_aruco",
+                executable="aruco_node",
+                parameters=[
+                    {"camera_frame": "camera2"},
+                    {"aruco_dictionary_id": "DICT_4X4_100"},
+                    {"marker_size": 0.07},
+                ],
+                remappings=[
+                    ("/camera/image_raw", "/camera2/image_raw"),
+                    ("/camera/camera_info", "/camera2/camera_info"),
+                ],
+                output="screen",
+                emulate_tty=True,
+            ),
+            Node(
+                package="usb_cam",
+                executable="usb_cam_node_exe",
+                parameters=[camera_config2],
+                output="screen",
+                emulate_tty=True,
+                namespace="camera2",
+            ),
+            Node(
+                package="yolo_openvino_ros2",
+                executable="yolo_openvino_node",
+                name="yolo_openvino_node",
+                parameters=[
+                    os.path.join(yolo_config_dir, "yolo_roma.yaml"),
+                ],
+                remappings=[
+                    ("/ball_on_image", "/camera2/ball_on_image"),
+                    ("/image_raw", "/camera2/image_raw"),
+                ],
+                
+                output="screen",
+            ),
+            *localization_nodes,
+            *tf_nodes,
+            # launch localization for every robot
+            # launch approach_teb for every robot
+            # launch strategy
+            # launch pic2real camera1
+            # launch pic2real camera2
+            Node(
+                package="strategy",
+                executable="strategy_node",
+                parameters=[
+                    {"robots_params": robot_config},
+                ],
+                output="screen",
+                emulate_tty=True,
+            ),
+        ]
+    )
diff --git a/src/strategy/package.xml b/src/strategy/package.xml
new file mode 100644
index 0000000000000000000000000000000000000000..d23693e91f15436262ca6f39dc3f32d1296b60df
--- /dev/null
+++ b/src/strategy/package.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
+<package format="3">
+  <name>strategy</name>
+  <version>0.0.0</version>
+  <description>TODO: Package description</description>
+  <maintainer email="root@todo.todo">root</maintainer>
+  <license>TODO: License declaration</license>
+
+  <test_depend>ament_copyright</test_depend>
+  <test_depend>ament_flake8</test_depend>
+  <test_depend>ament_pep257</test_depend>
+  <test_depend>python3-pytest</test_depend>
+
+  <export>
+    <build_type>ament_python</build_type>
+  </export>
+</package>
diff --git a/src/strategy/resource/strategy b/src/strategy/resource/strategy
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/strategy/setup.cfg b/src/strategy/setup.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..6731b2b86a5b64120d6fa2a86646bb26b5b1f02a
--- /dev/null
+++ b/src/strategy/setup.cfg
@@ -0,0 +1,4 @@
+[develop]
+script_dir=$base/lib/strategy
+[install]
+install_scripts=$base/lib/strategy
diff --git a/src/strategy/setup.py b/src/strategy/setup.py
new file mode 100644
index 0000000000000000000000000000000000000000..e7a11a712592a75790def0c31adbe1d11a9e158b
--- /dev/null
+++ b/src/strategy/setup.py
@@ -0,0 +1,26 @@
+from setuptools import setup
+
+package_name = 'strategy'
+
+setup(
+    name=package_name,
+    version='0.0.0',
+    packages=[package_name],
+    data_files=[
+        ('share/ament_index/resource_index/packages',
+            ['resource/' + package_name]),
+        ('share/' + package_name, ['package.xml']),
+    ],
+    install_requires=['setuptools'],
+    zip_safe=True,
+    maintainer='root',
+    maintainer_email='root@todo.todo',
+    description='TODO: Package description',
+    license='TODO: License declaration',
+    tests_require=['pytest'],
+    entry_points={
+        'console_scripts': [
+            'strategy_node = strategy.strategy_node:main'
+        ],
+    },
+)
diff --git a/src/strategy/strategy/__init__.py b/src/strategy/strategy/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/strategy/strategy/__pycache__/__init__.cpython-310.pyc b/src/strategy/strategy/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..30d5f0c0e098f222149efe2b318b02d4110a1ffe
Binary files /dev/null and b/src/strategy/strategy/__pycache__/__init__.cpython-310.pyc differ
diff --git a/src/strategy/strategy/solo_robot_strategy.py b/src/strategy/strategy/solo_robot_strategy.py
new file mode 100644
index 0000000000000000000000000000000000000000..d9d79c23f62097aa0779d4c4bc93edd20673225e
--- /dev/null
+++ b/src/strategy/strategy/solo_robot_strategy.py
@@ -0,0 +1,288 @@
+from dataclasses import dataclass
+from enum import Enum
+import typing as tp
+from geometry_msgs.msg import Pose, PointStamped, PoseStamped, Pose2D, TransformStamped
+import math
+
+# need struct with motions mapping and json
+KICK_RIGHT = 128
+KICK_LEFT = 64
+STAND_UP = None
+FIELD_LENGTH = 4.0
+FIELD_WIDE = 3.0
+
+
+class RobotState(Enum):
+    WAIT = 1
+    GO_TO_BALL = 2  # Approach local and global
+    APPROACH = 3
+    STAND_UP = 4
+    KICK = 5
+    GO_HOME = 6
+
+
+
+
+class CmdType(Enum):
+    GO = 1
+    MOTION = 2
+
+
+class Role(Enum):
+    ATTACKER = "attacker"
+    DEFENDER = "defender"
+    GOALKEEPER = "goalkeeper"
+
+
+class GameState(Enum):
+    START = 1
+    RUN = 2
+    PAUSED = 3
+
+
+@dataclass
+class RobotInfo:
+    robot_pose: Pose2D
+    ball_pose: Pose2D
+    game_state: GameState
+    role: Role
+    goal_pose: tp.Optional[Pose2D]
+    # Add home position
+
+@dataclass
+class Cmd:
+    type_: CmdType
+    data: tp.Any
+
+
+@dataclass
+class Transition:
+    condition: tp.Callable[..., bool]
+    action: tp.Callable[..., None]
+
+
+def angle_betwen(from_: Pose2D, to: Pose2D) -> float:
+    # calculate theta
+    # from 0 to pi okay so use acos
+    # Only forward. Russuian go! Russian go!
+    x = to.x - from_.x
+    y = to.y - from_.y
+    return math.atan2(y, x) 
+
+
+def approach_action(robot_info: RobotInfo) -> tp.Optional[Cmd]:
+    go_pose = robot_info.ball_pose # copy ?
+    goal_centre = Pose2D()
+    goal_centre.x = 0 #FIELD_WIDE / 2
+    goal_centre.y = FIELD_LENGTH / 2
+    goal_centre.theta = 0
+    # FIELD_WIDE / 2, FIELD_LENGTH / 2, 0)
+    go_pose.theta = angle_betwen(
+        robot_info.robot_pose, goal_centre 
+    )
+    robot_info.goal_pose = go_pose
+    return Cmd(1, go_pose)
+
+
+def kick_action(robot_info) -> tp.Optional[Cmd]:
+    # def calculate leg for kick
+
+    return Cmd(2, KICK_LEFT)
+
+
+def stand_up_action():
+    return Cmd(2, STAND_UP)
+
+
+def wait_action(robot_info) -> tp.Optional[Cmd]:
+    return None
+
+
+def go_home_action(robot_info) -> tp.Optional[Cmd]:
+    return None
+
+
+attacker_state_actions = {
+    RobotState.WAIT: wait_action,
+    RobotState.APPROACH: approach_action,
+    RobotState.KICK: kick_action,
+    RobotState.STAND_UP: stand_up_action,
+    RobotState.GO_HOME: go_home_action,
+}
+
+distance_epsilon = 0.05
+angle_epsilon_rad = 0.35 # 20 degree
+
+# Other metric ? 
+def is_ball_near_to_robot(robot_info:RobotInfo):
+    print (f"Goal is {robot_info.goal_pose}")
+    print(f"Robot pose is {robot_info.robot_pose}")
+    return   abs(robot_info.goal_pose.x - robot_info.robot_pose.x) < distance_epsilon and \
+            abs(robot_info.goal_pose.y - robot_info.robot_pose.y) < distance_epsilon and \
+            abs(robot_info.goal_pose.theta - robot_info.robot_pose.theta) < angle_epsilon_rad
+    
+def is_robot_at_home():
+    return True
+
+@dataclass
+class StateDict():
+    states: tuple[RobotState]
+    state_transitions: dict[RobotState, Transition]
+
+attacker_state_transitions = {
+    RobotState.WAIT: StateDict((RobotState.GO_HOME, RobotState.APPROACH),{
+        RobotState.GO_HOME: Transition(
+            lambda robot_info: robot_info.game_state == GameState.START, lambda: None
+        ),
+        RobotState.APPROACH: Transition(
+            lambda robot_info: robot_info.game_state == GameState.RUN,
+            lambda: print("SET PARAM Approach On"),
+        ),
+    }),
+    RobotState.APPROACH: StateDict((RobotState.KICK, RobotState.WAIT),{
+        RobotState.KICK: Transition(
+            lambda robot_info: is_ball_near_to_robot(robot_info), lambda:  print("SET param off")
+        ),
+        RobotState.WAIT: Transition(
+            lambda robot_info: robot_info.game_state == GameState.PAUSED, lambda: None
+        ),
+    }),
+    RobotState.KICK:StateDict( (RobotState.APPROACH, RobotState.WAIT),{RobotState.APPROACH: Transition(lambda robot_info: not is_ball_near_to_robot(robot_info), lambda: print("Set param on")), 
+                      RobotState.WAIT: Transition(lambda robot_info: True, lambda: None)}),
+    RobotState.GO_HOME:StateDict((RobotState.WAIT), {RobotState.WAIT: Transition(lambda robot_info: is_robot_at_home(robot_info),lambda: None)}),
+}
+
+class Robot(object):
+    def __init__(
+        self,
+        role: Role,
+        behaviour_dict: dict[RobotState, dict[StateDict]],
+        action_dict: dict[RobotState, tp.Callable],
+        start_robot_info
+    ):
+        self.pose:Pose2D
+        self.role = role
+        self.goal_pose:Pose2D
+        self.state = RobotState.WAIT
+        self.action_dict = action_dict
+        self.action = self.action_dict[self.state]
+        self.behaviour_dict = behaviour_dict
+        self.robot_info = start_robot_info
+    def set_robot_info(self, robot_info):
+        self.robot_info = robot_info
+    def update_pose(self, pose: Pose2D) -> None:
+        self.robot_info.robot_pose = pose
+####ALRAM update goal pose
+    def get_pose(self) -> Pose2D:
+        return self.robot_info.robot_pose
+
+    def set_ball_pose(self, ball_pose: Pose2D):
+        self.robot_info.ball_pose = ball_pose
+    def set_game_state(self, game_state: GameState):
+        self.robot_info.game_state = game_state
+    def step(self) -> None:
+        for state in self.behaviour_dict[self.state].states:
+            if (self.behaviour_dict[self.state].state_transitions[state].condition(self.robot_info)):
+                print(state)
+                self.behaviour_dict[self.state].state_transitions[state].action()#self.robot_info)
+                self.action = self.action_dict[state]
+                self.state = state
+                break
+        self.action(self.robot_info)
+
+    def change_state(self, state: RobotState) -> None:
+        if self.state not in self.behaviour_dict:
+            print(f"Current state is a terminal state: {self.state}")
+            return
+        if state not in self.behaviour_dict[state]:
+            print(f"Has no transition from {self.state} to {state}")
+            return
+        self.behaviour_dict[self.state][state]
+        # HUETA!
+        self.action = self.behaviour_dict[self.state][state].action
+        return
+
+    # def do_action(self):
+    #     self.action()
+
+    def reset(self) -> None:
+        self.state = RobotState.WAIT
+        self.action = self.action_dict[self.state]
+
+action_dict = {}
+behaviour_dict = {}
+@dataclass
+class RobotTopicConfig:
+    robot_name: str
+    localization_topic: str # "/robot_" + robot_id + "/... "  
+    low_level_topic: str # "/robot_" + robot_id + "/..."
+    approach_topic: str # "/robot_" + robot_id + "/..."
+
+class RobotFabrica:
+    def __init__(self, config):
+        self.config = config
+        self.robot_configs = []
+        self.robots = dict()
+        self.init_robots()
+    def init_robots(self):
+        for robot_id in self.config["robots"]:
+            self.robot_configs.append(
+                RobotTopicConfig(
+                    "robot_" + robot_id,
+                    "idk", "idk", "idk"
+                )
+            )
+            role_set = {role.value for role in Role}
+            if ("role" in self.config["robots"] and self.config["robots"]["role"] in role_set):
+                self.robots["robot_" + robot_id] = Robot(
+                    Role(self.config["robots"]["role"]), behaviour_dict=behaviour_dict, action_dict=action_dict,
+                )
+            else:
+                default_robot_info = RobotInfo(Pose2D(), Pose2D(), GameState.START, Role.ATTACKER, Pose2D())
+                self.robots["robot_" + robot_id] = Robot(
+                    Role.ATTACKER, attacker_state_transitions, attacker_state_actions,
+                    default_robot_info
+                )
+
+        
+    def get_robots(self) -> dict[str, Robot]:
+        return self.robots
+    def get_robots_configs(self) -> list[RobotTopicConfig]:
+        return self.robot_configs
+
+
+class Strategy(object):
+    def __init__(self, fabrica: RobotFabrica) -> None:
+        self.fabrica = fabrica
+        self.robots = self.fabrica.get_robots()
+        self.ball_pose:Pose2D  # Pose2D x, y
+        self.game_state = GameState.START
+
+    def set_robot_pose(self, name: str, pose: Pose2D):
+        # try 
+        self.robots[name].update_pose(pose)
+
+    def set_ball_pose(self, pose: Pose2D):
+        for robot in self.robots.values():
+            robot.set_ball_pose(pose)
+
+    def change_game_state(self, state: GameState) -> None:
+        self.game_state = state
+        for robot in self.robots.values():
+            robot.set_game_state(state)
+
+    def step(self):
+
+        for robot in self.robots.values():
+            robot.step()
+
+    def is_have_cmd(self, name: str) -> bool:
+        # try
+        self.robots[name]
+
+    def get_cmd(self, name) -> tp.Optional[Cmd]:
+        if (self.robots[name].is_have_cmd()):
+            return self.robots[name]
+        return None
+    
+
diff --git a/src/strategy/strategy/strategy_node.py b/src/strategy/strategy/strategy_node.py
new file mode 100644
index 0000000000000000000000000000000000000000..f7b6dfd2cba7cb81173d31fe77898dc1dbaf4f3e
--- /dev/null
+++ b/src/strategy/strategy/strategy_node.py
@@ -0,0 +1,107 @@
+from ros2_aruco_interfaces.msg import ArucoMarkers
+
+from geometry_msgs.msg import Pose, PointStamped, PoseStamped, Pose2D, TransformStamped
+from sensor_msgs.msg import CameraInfo
+from std_msgs.msg import Int32
+from soccer_vision_2d_msgs.msg import BallArray, Ball
+import tf2_ros
+
+from image_geometry import PinholeCameraModel
+
+import rclpy
+from rclpy.node import Node
+import json
+from tf2_ros import TransformException
+from tf2_ros.buffer import Buffer
+from tf2_ros.transform_listener import TransformListener
+from tf2_geometry_msgs import do_transform_point
+import tf_transformations as tf_t
+import math
+from skspatial.objects import Line, Plane
+from pyquaternion import Quaternion
+import paho.mqtt.client as mqtt
+
+import numpy as np
+from dataclasses import dataclass
+from solo_robot_strategy import Strategy, Cmd, CmdType, RobotFabrica, FieldPose
+
+
+class StrategyNode(Node):
+    def __init__(self):
+        self.declare_parameter("robots_config", rclpy.Parameter.Type.STRING_ARRAY)
+        self.robot_config = self.get_parameter("robots_config")
+        #
+        # [TODO] Param for get events from cli
+        #
+        self.fabrica = RobotFabrica(self.robot_config)
+        self.strategy = Strategy(self.fabrica)
+        self._init_topics()
+        self.timer = self.create_timer(0.01, self.step)
+        self.queue_size = 10
+
+    def _init_topics(self):
+        robot_configs = self.fabrica.get_robot_configs()
+        self.approach_pubs = {}
+        self.low_lvl_pubs = {}
+        for config in robot_configs:
+            # in perfect world tf but pose will be okay
+            self.create_subscription(
+                PoseStamped,
+                config.localization_topic,
+                lambda msg: self.strategy.set_robot_pose(
+                    config.robot_name, self._msg_preprocess(msg)
+                ),
+                self.queue_size,
+            )
+
+            self.approach_pubs[config.robot_name] = self.create_publisher(
+                """Approach message type""", config.approach_topic, self.queue_size
+            )
+            self.low_lvl_pubs[config.robot_name] = self.create_publisher(
+                """low_level_msg""", config.low_level_topic, self.queue_size
+            )
+
+    def step(self):
+        self.strategy.step()
+        for name in self.robot_publishers:
+            if self.strategy.is_have_cmd(name):
+                cmd = self.strategy.get_cmd()
+                self.send_cmd(cmd)
+
+    def send_cmd(self, robot_name, cmd: Cmd):
+        if cmd.type_ == CmdType.GO:
+            msg = self._create_approach_msg("""What inside""")
+            self.approach_pubs[robot_name].publish(msg)
+        elif cmd.type_ == CmdType.KICK:
+            # Need stop approach
+            # then send kick command
+            msg = self._create_low_lvl_msg("""What inside""")
+            self.low_lvl_pubs[robot_name].publish(msg)
+
+    def _create_low_lvl_msg(robot_name):
+        # We hasnot kick api right now
+        pass
+
+    def _create_approach_msg(cmd: Cmd) -> PoseStamped:
+        pass
+
+    def _msg_preprocess(self, msg: Pose2D) -> Pose2D:
+        return Pose2D
+
+
+def main(args=None):
+    rclpy.init(args=args)
+
+    strategy_node = StrategyNode()
+
+    rclpy.spin(strategy_node)
+
+    # Destroy the node explicitly
+    # (optional - otherwise it will be done automatically
+    # when the garbage collector destroys the node object)
+    strategy_node.destroy_node()
+    rclpy.shutdown()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/src/strategy/strategy/strategy_test.py b/src/strategy/strategy/strategy_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..5b5004c5b95ad754b817ef657fc4d92d8cdd826d
--- /dev/null
+++ b/src/strategy/strategy/strategy_test.py
@@ -0,0 +1,34 @@
+import pytest 
+from solo_robot_strategy import Robot, RobotState, GameState, RobotFabrica, RobotInfo, Strategy
+from dataclasses import dataclass
+
+@dataclass
+class Pose2D:
+    x: float
+    y: float
+    theta: float
+
+solo_config = {
+    "robots":
+    {
+    "0":{None}
+    }
+}
+
+def test_strategy():
+    fabrica =  RobotFabrica(solo_config)
+    strategy = Strategy(fabrica)
+    strategy.change_game_state(GameState.RUN)
+    strategy.set_robot_pose("robot_0", Pose2D(0., 0.0, 0.))
+    strategy.step()
+    strategy.step()
+    strategy.set_ball_pose( Pose2D(0.,1., 0.))
+    strategy.step()
+    print(strategy.robots["robot_0"].state)
+    assert (strategy.robots["robot_0"].state == RobotState.APPROACH)
+    strategy.set_robot_pose("robot_0", Pose2D(0.0, 1.00001, 1.57))
+    strategy.step()
+    print(strategy.robots["robot_0"].state)
+    assert (strategy.robots["robot_0"].state == RobotState.KICK)
+    strategy.step()
+    assert (strategy.robots["robot_0"].state == RobotState.WAIT)
diff --git a/src/strategy/strategy/utils.py b/src/strategy/strategy/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/strategy/test/__pycache__/test_copyright.cpython-310-pytest-6.2.5.pyc b/src/strategy/test/__pycache__/test_copyright.cpython-310-pytest-6.2.5.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ffa8b3545fe30ce7220ae46fcad3a9de27f56513
Binary files /dev/null and b/src/strategy/test/__pycache__/test_copyright.cpython-310-pytest-6.2.5.pyc differ
diff --git a/src/strategy/test/__pycache__/test_flake8.cpython-310-pytest-6.2.5.pyc b/src/strategy/test/__pycache__/test_flake8.cpython-310-pytest-6.2.5.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..814f78f72a579a58925b9155b0bdef3832c5a9cd
Binary files /dev/null and b/src/strategy/test/__pycache__/test_flake8.cpython-310-pytest-6.2.5.pyc differ
diff --git a/src/strategy/test/__pycache__/test_pep257.cpython-310-pytest-6.2.5.pyc b/src/strategy/test/__pycache__/test_pep257.cpython-310-pytest-6.2.5.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d69972022a7afb18379182291ce901d9a7bf2523
Binary files /dev/null and b/src/strategy/test/__pycache__/test_pep257.cpython-310-pytest-6.2.5.pyc differ
diff --git a/src/strategy/test/test_copyright.py b/src/strategy/test/test_copyright.py
new file mode 100644
index 0000000000000000000000000000000000000000..97a39196e84db97954341162a6d2e7f771d938c0
--- /dev/null
+++ b/src/strategy/test/test_copyright.py
@@ -0,0 +1,25 @@
+# Copyright 2015 Open Source Robotics Foundation, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from ament_copyright.main import main
+import pytest
+
+
+# Remove the `skip` decorator once the source file(s) have a copyright header
+@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.')
+@pytest.mark.copyright
+@pytest.mark.linter
+def test_copyright():
+    rc = main(argv=['.', 'test'])
+    assert rc == 0, 'Found errors'
diff --git a/src/strategy/test/test_flake8.py b/src/strategy/test/test_flake8.py
new file mode 100644
index 0000000000000000000000000000000000000000..27ee1078ff077cc3a0fec75b7d023101a68164d1
--- /dev/null
+++ b/src/strategy/test/test_flake8.py
@@ -0,0 +1,25 @@
+# Copyright 2017 Open Source Robotics Foundation, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from ament_flake8.main import main_with_errors
+import pytest
+
+
+@pytest.mark.flake8
+@pytest.mark.linter
+def test_flake8():
+    rc, errors = main_with_errors(argv=[])
+    assert rc == 0, \
+        'Found %d code style errors / warnings:\n' % len(errors) + \
+        '\n'.join(errors)
diff --git a/src/strategy/test/test_pep257.py b/src/strategy/test/test_pep257.py
new file mode 100644
index 0000000000000000000000000000000000000000..b234a3840f4c5bd38f043638c8622b8f240e1185
--- /dev/null
+++ b/src/strategy/test/test_pep257.py
@@ -0,0 +1,23 @@
+# Copyright 2015 Open Source Robotics Foundation, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from ament_pep257.main import main
+import pytest
+
+
+@pytest.mark.linter
+@pytest.mark.pep257
+def test_pep257():
+    rc = main(argv=['.', 'test'])
+    assert rc == 0, 'Found code style errors / warnings'