From 82f39e06ba5c635c833054df3cec380907c539f1 Mon Sep 17 00:00:00 2001 From: sfrana <sfrana@iastate.edu> Date: Wed, 25 Oct 2023 22:19:57 +0200 Subject: [PATCH] started crazyflie_connection implementation --- .../CrazyflieProtoConnection.py | 221 +++++++++++++++ cflib_groundstation/SetpointHandler.py | 215 +++++++++++++++ cflib_groundstation/crazyflie_connection.py | 115 +++++++- cflib_groundstation/uCartCommander.py | 258 ++++++++++++++++++ mp4Params.json | 24 +- 5 files changed, 813 insertions(+), 20 deletions(-) create mode 100644 cflib_groundstation/CrazyflieProtoConnection.py create mode 100644 cflib_groundstation/SetpointHandler.py create mode 100644 cflib_groundstation/uCartCommander.py diff --git a/cflib_groundstation/CrazyflieProtoConnection.py b/cflib_groundstation/CrazyflieProtoConnection.py new file mode 100644 index 000000000..b2a0ed79e --- /dev/null +++ b/cflib_groundstation/CrazyflieProtoConnection.py @@ -0,0 +1,221 @@ +""" +Crazyflie Proto Connection handles the actual interaction with the crazyflie. +The only reason it exists is to serve as an intermediary between the frontend +and the crazyflie itself so that it can handle all interactions with the +cflib library and that the GUI doesn't have to. + +If one wanted to slot this GUI into the existing microcart infrastructure, +CrazyflieProtoConnection could be rewritten to interface to frontend +commands, and to connect to the backend, and the crazyflie adapter, +and crazyflie groundstation. + +""" + + +from time import time +from typing import List +import time + +import cflib.crtp +from cflib.crazyflie import Crazyflie +from cflib.crazyflie.syncCrazyflie import SyncCrazyflie +from queue import Queue + +from cflib.crazyflie.log import LogConfig + + +class CrazyflieProtoConnection: + """ + Handles all interactions with cflib. + """ + + def __init__(self): + """ + Initialize the start time, the logging queue which is given to the + plotting window, and set the synchronous crazyflie connection by + default to None. This should be set to the crazyflie when connecting + to one. + """ + + # Get the start time, so that the timestamp returned to the user will + # be in seconds since startup. + self.start_time = time.time() + + self.logging_queue = Queue() + + self.scf = None + self.is_connected = False + self.param_callback_count = 0 + self.logging_configs = [] + + # self.timer = QTimer() + # self.timer.timeout.connect(self.update_plot) + # self.timer.start(50) + + def check_link_is_alive_callback(self): + """ Periodically investigate if the link is alive """ + if self.scf: + if not self.scf.is_link_open(): + self.is_connected = False + + def logging_callback(self, _timestamp, data, _logconf): + """ Whenever data comes in from the logging, it is sent here, + which routes it into our specific format for the logging queue. """ + + timestamp1 = time.time() - self.start_time + + for key in data.keys(): + value_pair = {'timestamp': timestamp1, 'data': data[key], + 'signal': key} + self.logging_queue.put(value_pair) + + def param_set_value(self, group: str, name: str, value: float): + """ Set a crazyflie parameter value. """ + + try: + if self.scf.is_link_open(): + full_name = group + "." + name + cf = self.scf.cf + cf.param.add_update_callback(group=group, name=name, + cb=self.done_setting_param_value) + + cf.param.set_value(full_name, value) + + # Don't return until the parameter is done getting set + while self.param_callback_count < 1: + time.sleep(0.01) + + self.param_callback_count = 0 + except AttributeError: + print("Nothing connected") + + def done_setting_param_value(self, *_args): + """ Callback when done setting a parameter value. """ + print("Done setting param") + self.param_callback_count += 1 + + def param_get_value(self, group: str, name: str): + """ Retrieve parameter value from crazyflie toc. """ + try: + if self.scf.is_link_open(): + return self.scf.cf.param.values[group][name] + + except AttributeError: + pass + return -1.234567890 # 1234567890 should be pretty obvious that + # something has gone wrong. + + def get_logging_toc(self): + """ Retrieve entire logging table of contents. Used in order to + display list in logging tab. """ + + try: + if self.scf.is_link_open(): + tocFull = self.scf.cf.log.toc.toc + toc = [] + for key in tocFull.keys(): + for inner_key in tocFull[key].keys(): + # concatenate group name with parameter name. + full_name = key + "." + inner_key + toc.append(full_name) + + return toc + else: + return [] + except AttributeError: + pass + return [] + + def get_param_toc(self): + """ Get the names of all groups available for parameters on the + crazyflie. Used to populate parameter group list on parameter tab. """ + + try: + if self.scf.is_link_open(): + toc = self.scf.cf.param.values + + return toc + except AttributeError: + pass + return {} + + def add_log_config(self, name: str, period: int, variables: List[str]): + """ Add a logging config. Used from logging tab when refreshing + logging variables. Add callback to route logged data to logging + queue. """ + + print("Name: " + name + ", period: " + str(period) + ", variables: " + + str(variables)) + logging_group = LogConfig(name=name, period_in_ms=period) + + for variable in variables: + logging_group.add_variable(variable, 'float') + + self.logging_configs.append(logging_group) + self.logging_configs[-1].data_received_cb.add_callback( + self.logging_callback) + self.scf.cf.log.add_config(self.logging_configs[-1]) + + def clear_logging_configs(self): + """ Stop logging and clear configuration. Used when refreshing + logging to stop anything that has configured to be logged from + logging. """ + + self.stop_logging() + self.logging_configs = [] + # done refreshing toc is a callback function that is triggered when + # refresh toc is done executing. + self.scf.cf.log.refresh_toc( + self.done_refreshing_toc, self.scf.cf.log._toc_cache) + # Blocks until toc is done refreshing. + while self.param_callback_count < 1: + time.sleep(0.01) + self.param_callback_count = 0 + + # grabs new toc values + self.scf.wait_for_params() + + def done_refreshing_toc(self, *_args): + """ Callback for flow control, increments param callback count to + allow exit of while loop. """ + self.param_callback_count = 1 + + def start_logging(self): + """ Begins logging all configured logging blocks. This is used from + the controls tab when hitting begin logging. """ + for i in range(0, len(self.logging_configs)): + self.logging_configs[i].start() + + def stop_logging(self): + """ Stops logging all configured logging blocks. This is used from + the controls tab when hitting pause logging. """ + for i in range(0, len(self.logging_configs)): + self.logging_configs[i].stop() + + def connect(self, uri: str): + """ + Handles connecting to a crazyflie. Bitcraze has excellent + documentation on how to use the synchronous crazyflie object in order + to send setpoints, set parameters or retrieve logging. + :param uri: Radio channel + """ + cflib.crtp.init_drivers() + self.scf = SyncCrazyflie(uri, cf=Crazyflie(rw_cache='./cache')) + self.scf.open_link() + self.scf.wait_for_params() + self.is_connected = True + + def disconnect(self): + """ Disconnect from crazyflie. """ + print("Disconnect quad") + if self.is_connected: + self.scf.close_link() + self.scf = None + self.is_connected = False + + @staticmethod + def list_available_crazyflies(): + """ Lists crazyflies that are on and within range. """ + cflib.crtp.init_drivers() # run this again just in case you plug the + # dongle in + return cflib.crtp.scan_interfaces() diff --git a/cflib_groundstation/SetpointHandler.py b/cflib_groundstation/SetpointHandler.py new file mode 100644 index 000000000..d8f7e56de --- /dev/null +++ b/cflib_groundstation/SetpointHandler.py @@ -0,0 +1,215 @@ +""" +Setpoint handler holds classes used to send setpoints to the crazyflie proto +connection in an organized fashion. It is used by the setpoint menu found on +the controls tab. + +""" + +from enum import Enum +from PyQt5.QtCore import QTimer +from uCartCommander import Commander +from threading import Semaphore + + +class FlightMode(Enum): + """ Available flight modes found in crazyflie firmware. """ + TYPE_STOP: int = 0 + TYPE_VELOCITY_WORLD: int = 1 + TYPE_ZDISTANCE: int = 2 + altHoldType: int = 4 + TYPE_HOVER: int = 5 + FULL_STATE_TYPE: int = 6 + TYPE_POSITION: int = 7 + # ------ Custom modes added to firmware by uCart ----------- + ATTITUDE_RATE_TYPE: int = 8 + ATTITUDE_TYPE: int = 9 + MIXED_ATTITUDE_TYPE: int = 10 + + +class Setpoint: + """ Used in order to hold setpoint easily. """ + def __init__(self): + self.yaw: float = 0 + self.pitch: float = 0 + self.roll: float = 0 + self.thrust: int = 0 + + +class SetpointHandler: + """ + Setpoint handler is used to send setpoints to the crazyflie proto + connection in an organized manner. It holds direct access to the + crazyflie's commander, which means that whenever connecting to a + crazyflie, that commander must be configured in the setpoint handler. + This commander is intended to by the custom uCartCommander which is a + modified low level setpoint commander. Currently, no modified version of + the high level commander exists. + + """ + + def __init__(self): + """ + Initialize timer, + + :param commander: uCartCommander taken directly from the synchronous + crazyflie. + """ + + self.setpoint = Setpoint() + self.commander = None + + self.setpoint_semaphore = Semaphore(1) + self._flight_mode = FlightMode.TYPE_STOP + + # Send setpoints to crazyflie every 20 ms. + self.timer = QTimer() + self.timer.timeout.connect(self.update) + self.timer.start(20) + + def setCommander(self, commander: Commander): + """ When the crazyflie is not connected, there will be no valid + commander. Set it during runtime. Enables the use of if commander: + on other functions to check if it is valid. """ + self.commander = commander + + def disconnectCommander(self): + """ Set the commander equal to none so that it won't be called when + we are not actually connected to the crazyflie. """ + self.commander = None + + def update(self): + """ If the flight mode is not stopped, send the current setpoint. """ + if self._flight_mode != FlightMode.TYPE_STOP: + self.sendSetpoint() + + def getFlightMode(self): + """ Returns current flight mode. """ + return self._flight_mode + + def setAttitudeMode(self): + """ Safely set to attitude mode. Uses semaphore in case a callback + happens in the middle of the function. """ + self.setpoint_semaphore.acquire() + self._flight_mode = FlightMode.ATTITUDE_TYPE + self.setpoint_semaphore.release() + + def setMixedAttitudeMode(self): + """ Safely set to mixed attitude mode. Uses semaphore in case a + callback happens in the middle of the function. """ + self.setpoint_semaphore.acquire() + self._flight_mode = FlightMode.MIXED_ATTITUDE_TYPE + self.setpoint_semaphore.release() + + def setRateMode(self): + """ Safely set to rate mode. Uses semaphore in case a callback + happens in the middle of the function. """ + self.setpoint_semaphore.acquire() + self._flight_mode = FlightMode.ATTITUDE_RATE_TYPE + self.setpoint_semaphore.release() + + def startFlying(self): + """ Sends an all 0's setpoint which is the convention to tell the + crazyflie to listen for setpoints. """ + if self.commander: + self.commander.start_flying() + + def stopFlying(self): + """ Tells the crazyflie to stop flying. """ + if self.commander: + self.setpoint_semaphore.acquire() + self._flight_mode = FlightMode.TYPE_STOP + + self.commander.send_notify_setpoint_stop(0) + self.setpoint_semaphore.release() + + def setSetpoint(self, yaw: float, pitch: float, roll: float, thrust: float): + """ Safely sets the crazyflie setpoint. Utilizes semaphore to avoid + reading and writing at the same time due to callbacks. """ + self.setpoint_semaphore.acquire() + + self.setpoint.yaw = yaw + self.setpoint.pitch = pitch + self.setpoint.roll = roll + self.setpoint.thrust = thrust + self.sendSetpointUnsafe() + + self.setpoint_semaphore.release() + + def sendSetpoint(self): + """ Uses commander to send setpoints to crazyflie depending upon the + current flight mode. """ + if self.commander: + self.setpoint_semaphore.acquire() + + if self._flight_mode == FlightMode.ATTITUDE_TYPE: + + # scales thrust from 100 for slider control. + thrust = self.setpoint.thrust * 60000 / 100 + print(f"Set attitude: {self.setpoint.yaw}, {self.setpoint.pitch}, " + f"{self.setpoint.roll}, {thrust}") + + self.commander.send_attitude_setpoint( + self.setpoint.yaw, self.setpoint.pitch, self.setpoint.roll, + thrust) + + elif self._flight_mode == FlightMode.ATTITUDE_RATE_TYPE: + + thrust = self.setpoint.thrust * 60000 / 100 + print(f"Set attitude rate: {self.setpoint.yaw}," + f" {self.setpoint.pitch}, " + f"{self.setpoint.roll}, {thrust}") + + self.commander.send_attitude_rate_setpoint( + self.setpoint.yaw, self.setpoint.pitch, self.setpoint.roll, + thrust) + + elif self._flight_mode == FlightMode.MIXED_ATTITUDE_TYPE: + + # scales thrust from 1000 for more fine-grained gamepad control. + thrust = self.setpoint.thrust * 60000 / 1000 + print(f"Set mixed attitude: {self.setpoint.yaw}," + f" {self.setpoint.pitch}, " + f"{self.setpoint.roll}, {thrust}") + + self.commander.send_mixed_attitude_setpoint( + self.setpoint.yaw, self.setpoint.pitch, self.setpoint.roll, + thrust) + + self.setpoint_semaphore.release() + + def sendSetpointUnsafe(self): + """ Exactly the same as send setpoint but no semaphore is used. """ + + print("Unsafe mode activate :)") + if self.commander: + if self._flight_mode == FlightMode.ATTITUDE_TYPE: + # scales thrust from 100 for slider control. + thrust = self.setpoint.thrust * 60000 / 100 + print(f"Set attitude: {self.setpoint.yaw}, {self.setpoint.pitch}, " + f"{self.setpoint.roll}, {self.setpoint.thrust}") + + self.commander.send_attitude_setpoint( + self.setpoint.yaw, self.setpoint.pitch, self.setpoint.roll, + int(thrust)) + + elif self._flight_mode == FlightMode.ATTITUDE_RATE_TYPE: + + thrust = self.setpoint.thrust * 60000 / 100 + print(f"Set attitude rate: {self.setpoint.yaw}," + f" {self.setpoint.pitch}, " + f"{self.setpoint.roll}, {thrust}") + + self.commander.send_attitude_rate_setpoint( + self.setpoint.yaw, self.setpoint.pitch, self.setpoint.roll, + thrust) + + elif self._flight_mode == FlightMode.MIXED_ATTITUDE_TYPE: + # scales thrust from 1000 for more fine-grained gamepad control. + thrust = self.setpoint.thrust * 60000 / 1000 + print(f"Set mixed attitude: {self.setpoint.yaw}," + f" {self.setpoint.pitch}, " + f"{self.setpoint.roll}, {thrust}") + + self.commander.send_mixed_attitude_setpoint( + self.setpoint.yaw, self.setpoint.pitch, self.setpoint.roll, + thrust) diff --git a/cflib_groundstation/crazyflie_connection.py b/cflib_groundstation/crazyflie_connection.py index 01aa89e97..5f3e7d8ca 100644 --- a/cflib_groundstation/crazyflie_connection.py +++ b/cflib_groundstation/crazyflie_connection.py @@ -1,6 +1,72 @@ +""" +crazyflie_connection handles the actual interaction with the crazyflie. +The only reason it exists is to serve as an intermediary between the groundstation +and the crazyflie itself so that it can handle all interactions with the +cflib library. +""" +from time import time +from typing import List +import time + +import cflib.crtp +from cflib.crazyflie import Crazyflie +from cflib.crazyflie.syncCrazyflie import SyncCrazyflie +from queue import Queue + +from cflib.crazyflie.log import LogConfig + + +class CrazyflieConnection: + + """ + Handles all interactions with cflib. + """ + + def __init__(self): + """ + Initialize the start time, the logging queue which is given to the + plotting window, and set the synchronous crazyflie connection by + default to None. This should be set to the crazyflie when connecting + to one. + """ + + # Get the start time, so that the timestamp returned to the user will + # be in seconds since startup. + self.start_time = time.time() + + self.logging_queue = Queue() + + self.scf = None + self.is_connected = False + self.param_callback_count = 0 + self.logging_configs = [] + + # self.timer = QTimer() + # self.timer.timeout.connect(self.update_plot) + # self.timer.start(50) + + def connect(self, uri: str): + """ + Handles connecting to a crazyflie. Bitcraze has excellent + documentation on how to use the synchronous crazyflie object in order + to send setpoints, set parameters or retrieve logging. + :param uri: Radio channel + """ + cflib.crtp.init_drivers() + self.scf = SyncCrazyflie(uri, cf=Crazyflie(rw_cache='./cache')) + self.scf.open_link() + self.scf.wait_for_params() + self.is_connected = True + + def disconnect(self): + """ Disconnect from crazyflie. """ + print("Disconnect quad") + if self.is_connected: + self.scf.close_link() + self.scf = None + self.is_connected = False -class CrazyflieConnection(): def Debug(): raise Exception def PacketLog(): @@ -11,14 +77,45 @@ class CrazyflieConnection(): raise Exception def BeginUpdate(): raise Exception - def OverrideOuput(): #TODO + def OverrideOuput(): + #TODO raise Exception def GetNodeIds(): raise Exception - def SetParam(): #TODO - raise Exception - def GetParam(): #TODO - raise Exception + def SetParam(self, group: str, name: str, value: float): + """ Set a crazyflie parameter value. """ + try: + if self.scf.is_link_open(): + full_name = group + "." + name + cf = self.scf.cf + cf.param.add_update_callback(group=group, name=name, + cb=self.done_setting_param_value) + + cf.param.set_value(full_name, value) + + # Don't return until the parameter is done getting set + while self.param_callback_count < 1: + time.sleep(0.01) + + self.param_callback_count = 0 + except AttributeError: + print("Nothing connected") + + def done_setting_param_value(self, *_args): + """ Callback when done setting a parameter value. """ + print("Done setting param") + self.param_callback_count += 1 + + def GetParam(self, group: str, name: str): + """ Retrieve parameter value from crazyflie toc. """ + try: + if self.scf.is_link_open(): + return self.scf.cf.param.values[group][name] + + except AttributeError: + pass + return -1.234567890 # 1234567890 should be pretty obvious that + # something has gone wrong. def SetSource(): raise Exception def GetSource(): @@ -31,7 +128,9 @@ class CrazyflieConnection(): raise Exception def AddNode(): raise Exception - def GetLogFile(): #TODO + def GetLogFile(): + #TODO raise Exception - def LogBlockCommand(): #TODO + def LogBlockCommand(): + #TODO raise Exception \ No newline at end of file diff --git a/cflib_groundstation/uCartCommander.py b/cflib_groundstation/uCartCommander.py new file mode 100644 index 000000000..f74bc7751 --- /dev/null +++ b/cflib_groundstation/uCartCommander.py @@ -0,0 +1,258 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# || ____ _ __ +# +------+ / __ )(_) /_______________ _____ ___ +# | 0xBC | / __ / / __/ ___/ ___/ __ `/_ / / _ \ +# +------+ / /_/ / / /_/ /__/ / / /_/ / / /_/ __/ +# || || /_____/_/\__/\___/_/ \__,_/ /___/\___/ +# +# Copyright (C) 2011-2013 Bitcraze AB +# +# Crazyflie Nano Quadcopter Client +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. +""" +Used for sending control setpoints to the Crazyflie + +- 4/7/2023 - Austin Beinder uCart 23: + modified to support uCart 22's custom packet types + +""" +import struct +import time + +from cflib.crtp.crtpstack import CRTPPacket +from cflib.crtp.crtpstack import CRTPPort +from cflib.crazyflie.commander import Commander + +__author__ = 'Bitcraze AB' +__all__ = ['Commander'] + +SET_SETPOINT_CHANNEL = 0 +META_COMMAND_CHANNEL = 1 + +TYPE_STOP = 0 +TYPE_VELOCITY_WORLD = 1 +TYPE_ZDISTANCE = 2 +altHoldType = 4 +TYPE_HOVER = 5 +FULL_STATE_TYPE = 6 +TYPE_POSITION = 7 +# Custom modes ----------- +ATTITUDE_RATE_TYPE = 8 +ATTITUDE_TYPE = 9 +MIXED_ATTITUDE_TYPE = 10 # Considering the default commander has a mixed +# attitude setpoint sender, it is unclear to me why this exists, unless it +# didn't exist in the version of the firmware this is based upon. +# ------------------------ + +TYPE_META_COMMAND_NOTIFY_SETPOINT_STOP = 0 + + +class Commander: + """ + Used for sending control setpoints to the Crazyflie + """ + + def __init__(self, crazyflie=None): + """ + Initialize the commander object. By default, the commander is in + +-mode (not x-mode). + """ + self._cf = crazyflie + self._x_mode = False + + def set_client_xmode(self, enabled): + """ + Enable/disable the client side X-mode. When enabled this recalculates + the setpoints before sending them to the Crazyflie. + """ + self._x_mode = enabled + + def send_setpoint(self, roll, pitch, yawrate, thrust): + """ + Send a new control setpoint for roll/pitch/yaw_Rate/thrust to the + copter. + The meaning of these values is depended on the mode of the RPYT + commander in the firmware + Default settings are Roll, pitch, yawrate and thrust + roll, pitch are in degrees + yawrate is in degrees/s + thrust is an integer value ranging from 10001 (next to no power) to + 60000 (full power) + """ + if thrust > 0xFFFF or thrust < 0: + raise ValueError('Thrust must be between 0 and 0xFFFF') + + if self._x_mode: + roll, pitch = 0.707 * (roll - pitch), 0.707 * (roll + pitch) + + pk = CRTPPacket() + pk.port = CRTPPort.COMMANDER + pk.data = struct.pack('<fffH', roll, pitch, yawrate, thrust) + self._cf.send_packet(pk) + print() + + def start_flying(self): + self.send_setpoint(0, 0, 0, 0) + + def send_notify_setpoint_stop(self, remain_valid_milliseconds=0): + """ + Sends a packet so that the priority of the current setpoint to the + lowest non-disabled value, + so any new setpoint regardless of source will overwrite it. + """ + pk = CRTPPacket() + pk.port = CRTPPort.COMMANDER_GENERIC + pk.channel = META_COMMAND_CHANNEL + pk.data = struct.pack('<BI', TYPE_META_COMMAND_NOTIFY_SETPOINT_STOP, + remain_valid_milliseconds) + self._cf.send_packet(pk) + + def send_stop_setpoint(self): + """ + Send STOP setpoint, stopping the motors and (potentially) falling. + """ + pk = CRTPPacket() + pk.port = CRTPPort.COMMANDER_GENERIC + pk.data = struct.pack('<B', TYPE_STOP) + self._cf.send_packet(pk) + + def send_velocity_world_setpoint(self, vx, vy, vz, yawrate): + """ + Send Velocity in the world frame of reference setpoint with yawrate + commands + vx, vy, vz are in m/s + yawrate is in degrees/s + """ + pk = CRTPPacket() + pk.port = CRTPPort.COMMANDER_GENERIC + pk.channel = SET_SETPOINT_CHANNEL + pk.data = struct.pack('<Bffff', TYPE_VELOCITY_WORLD, + vx, vy, vz, yawrate) + self._cf.send_packet(pk) + + def send_zdistance_setpoint(self, roll, pitch, yawrate, zdistance): + """ + Control mode where the height is sent as an absolute setpoint (intended + to be the distance to the surface under the Crazflie), + while giving roll, + pitch and yaw rate commands + roll, pitch are in degrees + yawrate is in degrees/s + zdistance is in meters + """ + pk = CRTPPacket() + pk.port = CRTPPort.COMMANDER_GENERIC + pk.channel = SET_SETPOINT_CHANNEL + pk.data = struct.pack('<Bffff', TYPE_ZDISTANCE, + roll, pitch, yawrate, zdistance) + self._cf.send_packet(pk) + + def send_hover_setpoint(self, vx, vy, yawrate, zdistance): + """ + Control mode where the height is sent as an absolute setpoint (intended + to be the distance to the surface under the Crazflie), while giving x, + y velocity + commands in body-fixed coordinates. + vx, vy are in m/s + yawrate is in degrees/s + zdistance is in meters + """ + pk = CRTPPacket() + pk.port = CRTPPort.COMMANDER_GENERIC + pk.channel = SET_SETPOINT_CHANNEL + pk.data = struct.pack('<Bffff', TYPE_HOVER, + vx, vy, yawrate, zdistance) + self._cf.send_packet(pk) + + def send_position_setpoint(self, x, y, z, yaw): + """ + Control mode where the position is sent as absolute (world) x,y,z + coordinate in + meter and the yaw is the absolute orientation. + x, y, z are in m + yaw is in degrees + """ + pk = CRTPPacket() + pk.port = CRTPPort.COMMANDER_GENERIC + pk.channel = SET_SETPOINT_CHANNEL + pk.data = struct.pack('<Bffff', TYPE_POSITION, + x, y, z, yaw) + self._cf.send_packet(pk) + + # ---------------- Custom changes ---------------------------- + def send_attitude_rate_setpoint(self, yawRate: float, pitchRate: float, + rollRate: float, thrust: float) -> None: + """ + Control mode where the attitude rates are set directly. Rates are in + deg/s. + + :param yawRate: Yaw rotational velocity setpoint + :param pitchRate: Pitch rotational velocity setpoint + :param rollRate: Roll rotational velocity setpoint + :param thrust: Directly set thrust, no PID required + :return: None + """ + pk = CRTPPacket() + pk.port = CRTPPort.COMMANDER_GENERIC + pk.channel = SET_SETPOINT_CHANNEL + pk.data = struct.pack('<Bffff', ATTITUDE_RATE_TYPE, rollRate, + pitchRate, + yawRate, thrust) # todo confirm struct is + # right, I tried to format it using H for the int of thrust and f for + # floats otherwise. Kinda looks like what is above + self._cf.send_packet(pk) + + def send_attitude_setpoint(self, yaw: float, pitch: float, roll: float, + thrust: float) -> None: + """ + Control mode where the attitude angles are set directly. Values are + in deg. + + :param yaw: Yaw rotation angle setpoint + :param pitch: Pitch rotation angle setpoint + :param roll: Roll rotation angle setpoint + :param thrust: Directly set thrust, no PID required + :return: None + """ + pk = CRTPPacket() + pk.port = CRTPPort.COMMANDER_GENERIC + pk.channel = SET_SETPOINT_CHANNEL + pk.data = struct.pack('<Bffff', ATTITUDE_TYPE, roll, pitch, yaw, + thrust) + self._cf.send_packet(pk) # todo confirm struct is right , I tried to + # format it using H for the int of thrust and f for + # floats otherwise. Kinda looks like what is above + + def send_mixed_attitude_setpoint(self, yaw: float, pitch: float, + roll: float, thrust: float) -> None: + """ + Control mode where the attitude angles are set directly, but yaw is + controlled via rate. Values are in deg and deg/s. + + :param yaw: Yaw rotation angular velocity setpoint + :param pitch: Pitch rotation angle setpoint + :param roll: Roll rotation angle setpoint + :param thrust: Directly set thrust, no PID required + :return: None + """ + pk = CRTPPacket() + pk.port = CRTPPort.COMMANDER_GENERIC + pk.channel = SET_SETPOINT_CHANNEL + pk.data = struct.pack('<Bffff', MIXED_ATTITUDE_TYPE, roll, pitch, yaw, + thrust) + self._cf.send_packet(pk) # todo confirm struct is right , I tried to + # format it using H for the int of thrust and f for + # floats otherwise. Kinda looks like what is above diff --git a/mp4Params.json b/mp4Params.json index 67951cbe6..64bb92c23 100644 --- a/mp4Params.json +++ b/mp4Params.json @@ -5,26 +5,26 @@ }, "s_pid_rate": { - "roll_kp" : 12, + "roll_kp" : 250, "roll_ki" : 0, - "roll_kd" : 0, - "pitch_kp": 0, + "roll_kd" : 1.5, + "pitch_kp": 250, "pitch_ki": 0, - "pitch_kd": 0, - "yaw_kp" : 0, + "pitch_kd": 1.5, + "yaw_kp" : 1060, "yaw_ki" : 0, - "yaw_kd" : 0 + "yaw_kd" : 3 }, "s_pid_attitude": { - "roll_kp" : 0, + "roll_kp" : 5, "roll_ki" : 0, - "roll_kd" : 0, - "pitch_kp": 0, + "roll_kd" : 0.2, + "pitch_kp": 5, "pitch_ki": 0, - "pitch_kd": 0, - "yaw_kp" : 0, + "pitch_kd": 0.2, + "yaw_kp" : 100, "yaw_ki" : 0, - "yaw_kd" : 0 + "yaw_kd" : 1.5 } } -- GitLab