From 83dd6bd11423c7881be49c32224ae1bfc2bbce76 Mon Sep 17 00:00:00 2001
From: Stevedan Ogochukwu Omodolor <stevedan.o.omodolor@gmail.com>
Date: Tue, 10 May 2022 17:09:18 +0200
Subject: [PATCH] Positioning fixed

---
 CMakeLists.txt                                |   2 +-
 README.md                                     |   5 +-
 config/omniwheels_config.yaml                 |   2 +
 package.xml                                   |   2 -
 ...ogger_node.py => crazyflie_logger_node.py} | 118 +++++----
 scripts/omni_wheel_controller_node.py         | 230 +++++++++++-------
 6 files changed, 223 insertions(+), 136 deletions(-)
 rename scripts/{crazzyflie_logger_node.py => crazyflie_logger_node.py} (70%)
 mode change 100644 => 100755

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 96da5be..3bdb17f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 2.8.3)
 project(omniwheels_controller)
 
 ## Find catkin and any catkin packages
-find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs genmsg geometry_msgs message_generation tf2)
+find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs genmsg geometry_msgs message_generation)
 add_message_files(
  FILES
  motorCommand.msg
diff --git a/README.md b/README.md
index d45d20f..2d97c02 100644
--- a/README.md
+++ b/README.md
@@ -11,4 +11,7 @@ Doubts that occured during coding
 
 make sure that the most main computer is the ros master and and source the 
 configure_ros_master.bash file 
-the file is in the utills important
\ No newline at end of file
+the file is in the utills important
+
+issue with device permission usb crazyflie 
+https://www.bitcraze.io/documentation/repository/crazyflie-lib-python/master/installation/usb_permissions/
diff --git a/config/omniwheels_config.yaml b/config/omniwheels_config.yaml
index 5f12b34..ac92cd5 100644
--- a/config/omniwheels_config.yaml
+++ b/config/omniwheels_config.yaml
@@ -4,4 +4,6 @@ id: 1
 channel: 90 # change to the correct one
 type: default
 position_offset: 0.11 # ofsset from crazyflie module to the cenyter of the omniwheels
+R: 0.0165
+
 
diff --git a/package.xml b/package.xml
index 4e73a0d..ecce13d 100644
--- a/package.xml
+++ b/package.xml
@@ -27,7 +27,6 @@
   <build_depend>roscpp_serialization</build_depend>
   <build_depend>rostime</build_depend>
   <build_depend>std_msgs</build_depend>
-    <build_depend>tf2</build_depend>
 
   <build_depend>message_generation</build_depend>
   <run_depend>message_runtime</run_depend>
@@ -41,6 +40,5 @@
   <run_depend>roscpp_serialization</run_depend>
   <run_depend>rostime</run_depend>
   <run_depend>std_msgs</run_depend>
-    <run_depend>tf2</run_depend>
 
 </package>
diff --git a/scripts/crazzyflie_logger_node.py b/scripts/crazyflie_logger_node.py
old mode 100644
new mode 100755
similarity index 70%
rename from scripts/crazzyflie_logger_node.py
rename to scripts/crazyflie_logger_node.py
index 1345a4c..68da397
--- a/scripts/crazzyflie_logger_node.py
+++ b/scripts/crazyflie_logger_node.py
@@ -1,9 +1,9 @@
+#!/usr/bin/env python3
 import logging
-import time
-from threading import Timer
-from typing import Set
 import numpy as np
-import matplotlib.pyplot as plt
+from threading import Timer
+
+
 import cflib.crtp  # noqa
 from cflib.crazyflie import Crazyflie
 from cflib.crazyflie.log import LogConfig
@@ -12,6 +12,7 @@ from cflib.crazyflie.syncCrazyflie import SyncCrazyflie
 from cflib.crazyflie.syncLogger import SyncLogger
 from cflib.positioning.position_hl_commander import PositionHlCommander
 from cflib.positioning.motion_commander import MotionCommander
+logging.basicConfig(level=logging.ERROR)
 
 # ros stuff 
 import rospy
@@ -21,8 +22,6 @@ from omniwheels_controller.msg import motorCommand
 from geometry_msgs.msg import Twist
 from geometry_msgs.msg import Pose
 
-# numpy stuff 
-import numpy as np
 
 
 uri = uri_helper.uri_from_env(default='usb://0') # Connection-uri for crazyflie via USB
@@ -51,47 +50,56 @@ class ParameterLogging:
     Simple logging example class that logs the Stabilizer from a supplied
     link uri and disconnects after 5s.
     """
-    period_in_ms = 20
+    period_in_ms = 100
 
-    def __init__(self, link_uri,cf):
+    def __init__(self, link_uri):
         
         # ros stuff
         self._pose_data_ = Pose()
         self._vel_data_ = Twist()
         # create publishers 
-        self._position_pub = rospy.Publisher("/pose", Pose,queue_size=100)
-        self._velocity_pub = rospy.Publisher("/vel", Twist,queue_size=100)
+        self._position_pub = rospy.Publisher("pose", Pose,queue_size=100)
+        self._velocity_pub = rospy.Publisher("vel", Twist,queue_size=100)
         robot_name = rospy.get_param("/name")
+        self._position_offset = rospy.get_param("/position_offset")
+        #
         print("Started " + robot_name + " Logging")
-        # self._cf = Crazyflie(rw_cache='./cache')
-        self._cf = cf
+        self._cf = Crazyflie(rw_cache='./cache')
         print("Connectiong callbacks")
         # Connect some callbacks from the Crazyflie API
         self._cf.connected.add_callback(self._connected)
         self._cf.disconnected.add_callback(self._disconnected)
         self._cf.connection_failed.add_callback(self._connection_failed)
         self._cf.connection_lost.add_callback(self._connection_lost)
+        self.R = rospy.get_param("/R")
+        
+        #try to connected to the crazyflie 
+        self._cf.open_link(link_uri)
        
 
         
 
 
     def _connected(self, link_uri):
-        self.reset_estimator(cf)
+        print("Connected")
+        #self.reset_estimator(self._cf)
         print("Reset estimation complete")
 
         # The definition of the logconfig can be made before connecting
-        self._lg_stab = LogConfig(name='Position value', period_in_ms=self.period_in_ms)
-        self._lg_stab.add_variable('stateEstimateZ.x', 'int16')
-        self._lg_stab.add_variable('stateEstimateZ.y', 'int16')
-        self._lg_stab.add_variable('stateEstimateZ.z', 'int16')
-        self._lg_stab.add_variable('stabilizer.yaw', 'float')
-      
-
-        self._lg_est_vel = LogConfig(name='Velocity value', period_in_ms=self.period_in_ms)
-        self._lg_est_vel.add_variable('stateEstimateZ.vx'.'float')
-        self._lg_est_vel.add_variable('stateEstimateZ.vy', 'float')
-        self._lg_est_vel.add_variable('stateEstimatorZ.rateYaw', 'float')
+        self._lg_stab = LogConfig(name='formation_test_position', period_in_ms=self.period_in_ms)
+        self._lg_stab.add_variable('stateEstimate.yaw')
+        self._lg_stab.add_variable('stateEstimateZ.x')
+        self._lg_stab.add_variable('stateEstimateZ.y')
+        self._lg_stab.add_variable('stateEstimateZ.z')
+        print("Position configuraion finished")
+
+
+        self._lg_est_vel = LogConfig(name='formation_test_velocity', period_in_ms=self.period_in_ms)
+        self._lg_est_vel.add_variable('stateEstimateZ.vx')
+        self._lg_est_vel.add_variable('stateEstimateZ.vy')
+        self._lg_est_vel.add_variable('stateEstimateZ.rateYaw')
+        
+        print("velocity configuraion finished")
 
         try:
             self._cf.log.add_config(self._lg_stab)
@@ -108,15 +116,19 @@ class ParameterLogging:
             # Start the logging
             self._lg_stab.start()
             self._lg_est_vel.start()
+            
+            print("Completed configuration")
         except KeyError as e:
             print('Could not start log configuration,'
                   '{} not found in TOC'.format(str(e)))
         except AttributeError:
             print('Could not add Stabilizer log config, bad configuration.')
         
+        
         # Start a timer to disconnect in 10s
-        # t = Timer(5, self._cf.close_link)
-        # t.start()
+        #t = Timer(5, self._cf.close_link)
+        #t.start()
+        #print("Timer started")
 
 
     def reset_estimator(self):
@@ -180,22 +192,35 @@ class ParameterLogging:
         print('Error when logging %s: %s' % (logconf.name, msg))
 
     def _pos_log_data(self, timestamp, data, logconf):
+        x = data['stateEstimateZ.x']/1000.0
+        y = data['stateEstimateZ.y']/1000.0
+        z = data['stateEstimateZ.z']/1000.0
+        yaw = (data['stateEstimate.yaw'])
+        yaw_ = np.mod(yaw-30, 360);
+
+       
+        quat_val = get_quaternion_from_euler(0,0,np.deg2rad(yaw_))
+        
+
         
-        self._pos_log_data.position.x = data['stateEstimateZ.x']/1000.0
-        self._pos_log_data.position.y = data['stateEstimateZ.y']/1000.0
-        self._pos_log_data.position.z = data['stateEstimateZ.z']/1000.0
-        quat_val = get_quaternion_from_euler(0,0,data['stateEstimate.yaw']);
+        self._pose_data_.orientation.x = quat_val[0]
+        self._pose_data_.orientation.y =quat_val[1]
+        self._pose_data_.orientation.z = quat_val[2]
+        self._pose_data_.orientation.w = quat_val[3] 
+        self._pose_data_.position.x =x+ np.sin(np.deg2rad(yaw))*self._position_offset;
+        self._pose_data_.position.y = y- np.cos(np.deg2rad(yaw))*self._position_offset
+        self._pose_data_.position.z = z;
         
-        self._pos_log_data.orientaton.x = quat_val[0]
-        self._pos_log_data.orientaton.y =quat_val[1]
-        self._pos_log_data.orientaton.z = quat_val[2]
-        self._pos_log_data.orientaton.w = quat_val[3]
-        self.position_pub.publish(self._pos_log_data)
+             
+        self._position_pub.publish(self._pose_data_)
+       
     def _vel_log_data(self, timestamp,data, logconf):
-        self._pos_log_data.linear.x = data['stateEstimateZ.vx']/1000.0
-        self._vel_log_data.linear.y = data['stateEstimateZ.vy']/1000.0
-        self._vel_log_data.angular.z = data['stateEstimateZ.rateYaw']/1000.0
-        self.velocity_pub.publish(self._vel_log_data)
+        self._vel_data_.linear.x = data['stateEstimateZ.vx']/1000.0
+        self._vel_data_.linear.y = data['stateEstimateZ.vy']/1000.0
+        self._vel_data_.angular.z = data['stateEstimateZ.rateYaw']/1000.0
+        self._velocity_pub.publish(self._vel_data_)
+
+        
     def _connection_failed(self, link_uri, msg):
         """Callback when connection initial connection fails (i.e no Crazyflie
         at the specified address)"""
@@ -211,21 +236,20 @@ class ParameterLogging:
         """Callback when the Crazyflie is disconnected (called in all cases)"""
         print('Disconnected from %s' % link_uri)
         self.is_connected = False
+        
+    def close(self):
+        self._cf.close_link()
 
 
 
 if __name__ == '__main__':
-     rospy.init_node("crazyflie_logger_node")
+    rospy.init_node("crazyflie_logger_node")
     # Initialize the low-level drivers
     cflib.crtp.init_drivers()
-    cf=Crazyflie(rw_cache='./cache')
-    le = ParameterLogging(uri, cf)
-    cf=Crazyflie(rw_cache='./cache')
-
     try:
-        le = ParameterLogging(uri, cf)
-   except rospy.ROSInterruptException:
+        le = ParameterLogging(uri)
+        
+    except rospy.ROSInterruptException:
         pass
 
-
  
diff --git a/scripts/omni_wheel_controller_node.py b/scripts/omni_wheel_controller_node.py
index 633c916..4d726ec 100755
--- a/scripts/omni_wheel_controller_node.py
+++ b/scripts/omni_wheel_controller_node.py
@@ -4,101 +4,161 @@ import time
 from std_msgs.msg import String
 from omniwheels_controller.msg import motorCommand
 from geometry_msgs.msg import Twist
-
+from geometry_msgs.msg import Pose
+import math
+import numpy as np
 
 # Dynamixel packages
 from dynamixel.model.xm430_w210_t_r import XM430_W210_T_R
 import dynamixel.channel
 import time
- 
 
+def euler_from_quaternion(x, y, z, w):
+        """
+        Convert a quaternion into euler angles (roll, pitch, yaw)
+        roll is rotation around x in radians (counterclockwise)
+        pitch is rotation around y in radians (counterclockwise)
+        yaw is rotation around z in radians (counterclockwise)
+        """
+        t0 = +2.0 * (w * x + y * z)
+        t1 = +1.0 - 2.0 * (x * x + y * y)
+        roll_x = math.atan2(t0, t1)
+
+        t2 = +2.0 * (w * y - z * x)
+        t2 = +1.0 if t2 > +1.0 else t2
+        t2 = -1.0 if t2 < -1.0 else t2
+        pitch_y = math.asin(t2)
+
+        t3 = +2.0 * (w * z + x * y)
+        t4 = +1.0 - 2.0 * (y * y + z * z)
+        yaw_z = math.atan2(t3, t4)
+
+        return roll_x, pitch_y, yaw_z # in radians
 class omniWheelvelocityController:
-    def __init__(self):
-        # Load the servos 
-        self.servos = self.load_servos()
-        self.motor_command = [0,0,0];
-        self.send_command = False;
-        self.vel_cmd_arrived = False;
-        self.motor_command[0] = 0;
-        self.motor_command[1] = 0;
-        self.motor_command[2] = 0; 
-        self.vel_cmd = Twist()
-        self.vel_cmd.linear.x = 0;
-        self.vel_cmd.linear.y = 0;
-        self.vel_cmd.angular.z = 0;
-        
-        # get ros param value 
-        robot_name = rospy.get_param("/name");
-
-
-      
-        
-        # subscriber 
-        #rospy.Subscriber("motor_command", motorCommand, self.motorVelocityCallback)
-        # todo add grounp ns 
-        rospy.Subscriber("vel_cmd", Twist, self.velocityCommandCallback)
-
-        
-        time.sleep(1) # Wait for connection to work
-        rospy.loginfo("Starting controller: " +  robot_name)
-        
-        # main loop 
-        while not rospy.is_shutdown():
-            
-            
-            if self.vel_cmd_arrived == True:
-                # convert velocity command to motor command 
-                self.motor_command[0] = self.vel_cmd.linear.x
-                self.motor_command[1] = self.vel_cmd.linear.y
-                self.motor_command[2] = self.vel_cmd.angular.z
-                self.vel_cmd_arrived = False
-                
-                
-            else:
+        def __init__(self):
+                # Load the servos
+                self.servos = self.load_servos()
+                self.motor_command = [0,0,0];
+                self.send_command = False;
+                self.vel_cmd_arrived = False;
+                self.motor_command[0] = 0;
+                self.motor_command[1] = 0;
+                self.motor_command[2] = 0;
+                self.vel_cmd = Twist()
+                self.vel_cmd.linear.x = 0;
+                self.vel_cmd.linear.y = 0;
+                self.vel_cmd.angular.z = 0;
+                self._current_vel = Twist()
+                self._current_pose = Pose()
+                self.vel_callback_initiated  = False;
+                self.pose_callback_initiated = False;
+                self.R = 0.165 		# Distance between center of robot and wheels
+                self.a1 = 2*np.pi/3 	# Angle between x axis and first wheel
+                self.a2 = 4*np.pi/3  # Angle between x axis and second wheel
+                self.r = 0.027 # Wheel radius. Has been fudge-factored because the actual velocity of the wheels did not align with the set-points.
+
+
+                # get ros param value
+                robot_name = rospy.get_param("/name");
+
+
+
+
+                # subscriber
+                #rospy.Subscriber("motor_command", motorCommand, self.motorVelocityCallback)
+                # Velocity command node
+                rospy.Subscriber("vel_cmd", Twist, self.velocityCommandCallback)
+                # pose and velocity values
+                ss = rospy.get_param("/name")
+                ss_pose = "/" + ss+"/pose";
+                ss_vel= "/" + ss+"/vel";
+                rospy.Subscriber(ss_pose, Pose, self.poseCallback)
+                rospy.Subscriber(ss_vel, Twist, self.velCallback)
+
+
+
+
+
+                time.sleep(1) # Wait for connection to work
+                rospy.loginfo("Starting controller: " +  robot_name)
                 self.motor_command[0] = 0
                 self.motor_command[1] = 0
                 self.motor_command[2] = 0
-                
-            # Send command to the motors
-            for (j,s) in enumerate(self.servos):
-                s.goal_velocity.write(round(self.motor_command[j]))
-        
+                print("Starting main loop")
+
+                # main loop
+                while not rospy.is_shutdown():
+
+
+                        if self.vel_cmd_arrived == True and self.vel_callback_initiated == True and self.pose_callback_initiated==True:
+                                # compute motor values from velocity command
+                                roll, pitch,yaw = euler_from_quaternion(self._current_pose.orientation.x,self._current_pose.orientation.y,self._current_pose.orientation.z,self._current_pose.orientation.w)
+                                xdot = np.array([1,0,0]);#self._current_vel.linear.x, self._current_vel.linear.y, self._current_vel.angular.z])
+                                ph = self.vel2motor(xdot, 0);
+                                #print(ph)
+                                print(yaw)
+                                self.motor_command[0] = ph[0]
+                                self.motor_command[1] = ph[1]
+                                self.motor_command[2] = ph[2]
+                                self.vel_cmd_arrived = False
+
+                        else:
+                                self.motor_command[0] = 0
+                                self.motor_command[1] = 0
+                                self.motor_command[2] = 0
+
+                        # Send command to the motors
+                        for (j,s) in enumerate(self.servos):
+                                s.goal_velocity.write(round(self.motor_command[j]))
 
-    def load_servos(self):
-        """
-        Servo load function, based on the dynamixel implementation of Anders Blomdell.
-        """
-        # The speed and channel numbers can be found and checked via the dynamixel software "dynamixel wizard".
-        # The device can be found by e.g. lsusb, and is currently adapted for the three-color robots.
-
-        channel = dynamixel.channel.Channel(speed=57600,device='/dev/ttyACM0')
-        servos = [ XM430_W210_T_R(channel, 1),
-                   XM430_W210_T_R(channel, 2),
-                   XM430_W210_T_R(channel, 3) ]
-        for s in servos:
-            s.torque_enable.write(0)
-            print(s.model_number.read(), s.id.read())
-            s.operating_mode.write(1)
-            s.bus_watchdog.write(0) # Clear old watchdog error
-            s.bus_watchdog.write(100) # 2 second timeout
-            s.torque_enable.write(1)
-            
-        return servos
-
-    def motorVelocityCallback(self, data):
-        self.send_command = True
-        self.motor_command[0] = data.data[0];
-        self.motor_command[1] = data.data[1];
-        self.motor_command[2] = data.data[2];
-    def velocityCommandCallback(self,data):
-        self.vel_cmd_arrived = True
-        self.vel_cmd = data
-
-        
+
+        def load_servos(self):
+                """
+                Servo load function, based on the dynamixel implementation of Anders Blomdell.
+                """
+                # The speed and channel numbers can be found and checked via the dynamixel software "dynamixel wizard".
+                # The device can be found by e.g. lsusb, and is currently adapted for the three-color robots.
+
+                channel = dynamixel.channel.Channel(speed=57600,device='/dev/ttyACM0')
+                servos = [ XM430_W210_T_R(channel, 1),
+                XM430_W210_T_R(channel, 2),
+                XM430_W210_T_R(channel, 3) ]
+                for s in servos:
+                        s.torque_enable.write(0)
+                        print(s.model_number.read(), s.id.read())
+                        s.operating_mode.write(1)
+                        s.bus_watchdog.write(0) # Clear old watchdog error
+                        s.bus_watchdog.write(100) # 2 second timeout
+                        s.torque_enable.write(1)
+
+                return servos
+
+        def motorVelocityCallback(self, data):
+                self.send_command = True
+                self.motor_command[0] = data.data[0];
+                self.motor_command[1] = data.data[1];
+                self.motor_command[2] = data.data[2];
+        def velocityCommandCallback(self,data):
+                self.vel_cmd_arrived = True
+                self.vel_cmd = data
+
+        def velCallback(self, data):
+                self._current_vel = data;
+                self.vel_callback_initiated = True;
+
+        def poseCallback(self, data):
+                self._current_pose = data;
+                self.pose_callback_initiated = True;
+        def vel2motor(self,xdot, ang):
+                M = -1/self.r*np.array([[-np.sin(ang), np.cos(ang), self.R ],[-np.sin(ang+self.a1), np.cos(ang+self.a1), self.R],[-np.sin(ang+self.a2), np.cos(ang+self.a2), self.R]])
+                return M.dot(xdot)
+                #return M.dot(xdot)
 
 if __name__ == '__main__':
-    rospy.init_node("omni_wheel_controller_node")
-    try:
-        omniwheel1 = omniWheelvelocityController()
-    except rospy.ROSInterruptException:
-        pass
+        rospy.init_node("omni_wheel_controller_node")
+        try:
+                omniwheel1 = omniWheelvelocityController()
+        except rospy.ROSInterruptException:
+                pass
+
+
-- 
GitLab