-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Swarm Control & Broadcasting #8
Comments
This would be a great add-on and is also the missing feature before the Crazyswarm can switch to this library.
|
Thanks for the guidance! I actually had some time to play around with the code over the weekend and I figured out what I was doing wrong—I was repurposing the existing As soon as I set safelink to |
Awesome - thanks for the update! One word of warning though: For crazyflie-link-cpp, the goal is to always go through the
I prefer option 3. It would be good to get some input from @ataffanel, who co-designed the interface of this library. |
That's good to know, because I was planning on doing exactly that actually, adding a Btw, can you explain the concern around |
The |
Interesting, I didn't realize that's how it was intended to be used—it does take a raw data pointer and a buffer length like you said, but so do Either way though, I sounds like it would be at best a minor optimization for me, and testing with I ended up using |
Thanks - I left a review. As stated earlier, we'll need @ataffanel input for the URI design. I think |
Hi, Thanks for the great work, it will be awesome to finally get broadcast in the python cflib and to unify Crazyswarm and the cflib link! Between the two URI, I can think of two uri scheme that would work for me:
|
I think we had more than two options in total:
I actually also like using a new protocol for the reasons Arnaud stated. Perhaps |
The "bc" was an unnecessary shortening. I like both So my vote would go for |
Just an note that I thought I should leave here for future reference, in case anyone's looking at this thread later: I've been doing some testing with the broadcast feature, and while it works great, it seems it's also important to maintain a unicast connection, with autoping enabled, for each of the copters in the swarm, in order to allow them to keep sending packets back and flush their send queues. I had to do a bit of debugging with the firmware to figure this out, but it seems that because the firmware is designed to not drop packets, and most operations send back an ack packet after processing the incoming packet, if no sends are happening, the copters can can get "backed up", and they will eventually stop processing incoming packets. |
@mikendu Do you have an example that shows the issue with the congested queues? I am afraid that this might be a bug (and even a regression) and we should file github issue in the official firmware to investigate. |
Yep, here's the example code I was testing with (slightly hacky): import struct
import time
from enum import Enum
from cflib.crtp.cflinkcppdriver import CfLinkCppDriver
from cflib.crtp.radiodriver import Crazyradio
from cflib.crtp.crtpstack import CRTPPacket
from cflib.crtp.crtpstack import CRTPPort
from cflib.crazyflie import HighLevelCommander
from cflib.crazyflie.param import WRITE_CHANNEL
class RingEffect(Enum):
OFF = 0
FADE_EFFECT = 14
TIMING_EFFECT = 17
# Hard coding TOC param ids for now, based on the current firmware
class ParameterID(Enum):
RING_EFFECT = 181 # uint8_t, <B
FADE_COLOR = 190 # uint32_t, <L
FADE_TIME = 191 # float, <f
class LightController:
def __init__(self, cf):
self.cf = cf
def set_effect(self, effect):
if not isinstance(effect, RingEffect):
raise ValueError("Invalid effect given: " + str(effect))
packet = self._effect_change(effect.value)
self.cf.send_packet(packet)
def set_color(self, r, g, b, time = 0.0):
color = (int(r) << 16) | (int(g) << 8) | int(b)
color_packet = self._fade_color(color)
time_packet = self._fade_time(time)
self.cf.send_packet(color_packet)
self.cf.send_packet(time_packet)
def _fade_time(self, duration):
packet = CRTPPacket()
packet.set_header(CRTPPort.PARAM, WRITE_CHANNEL)
packet.data = struct.pack('<H', int(ParameterID.FADE_TIME.value))
packet.data += struct.pack('<f', float(duration))
return packet
def _fade_color(self, color):
packet = CRTPPacket()
packet.set_header(CRTPPort.PARAM, WRITE_CHANNEL)
packet.data = struct.pack('<H', int(ParameterID.FADE_COLOR.value))
packet.data += struct.pack('<L', int(color))
return packet
def _effect_change(self, effect):
packet = CRTPPacket()
packet.set_header(CRTPPort.PARAM, WRITE_CHANNEL)
packet.data = struct.pack('<H', int(ParameterID.RING_EFFECT.value))
packet.data += struct.pack('<B', int(effect))
return packet
class Broadcaster():
def __init__(self, channel, datarate = Crazyradio.DR_2MPS):
self._validate_channel(channel)
self._validate_datarate(datarate)
self._uri = self._construct_uri(channel, datarate)
self._link = CfLinkCppDriver()
self._is_link_open = False
self.high_level_commander = HighLevelCommander(self)
self.light_controller = LightController(self)
def open_link(self):
if (self.is_link_open()):
raise Exception('Link already open')
print('Connecting to %s' % self._uri)
self._link.connect(self._uri, None, None)
self._is_link_open = True
def close_link(self):
if (self.is_link_open()):
self._link.close()
self._is_link_open = False
def __enter__(self):
self.open_link()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close_link()
def is_link_open(self):
return self._is_link_open
def send_packet(self, packet):
if not self.is_link_open():
raise Exception('Link is not open')
self._link.send_packet(packet)
def __str__(self):
return "BroadcastLink <" + str(self._uri) + ">"
def _construct_uri(self, channel, datarate):
return "radiobroadcast://*/" + str(channel) + "/" + self._get_data_rate_string(datarate)
def _validate_channel(self, channel):
if channel and (isinstance(channel, int) or channel.is_integer()):
if channel >= 0 and channel <= 127:
return
raise ValueError("Invalid channel: " + str(channel))
def _validate_datarate(self, datarate):
if not(datarate == Crazyradio.DR_250KPS or \
datarate == Crazyradio.DR_1MPS or \
datarate == Crazyradio.DR_2MPS):
raise ValueError("Invalid data rate: " + str(datarate))
def _get_data_rate_string(self, datarate):
if datarate == Crazyradio.DR_250KPS:
return '250K'
elif datarate == Crazyradio.DR_1MPS:
return '1M'
elif datarate == Crazyradio.DR_2MPS:
return '2M'
link = Broadcaster(55)
link.open_link()
link.light_controller.set_color(255, 0, 0, 0.5)
time.sleep(0.5)
link.light_controller.set_color(0, 0, 0, 0.5)
time.sleep(0.5)
link.light_controller.set_color(0, 255, 0, 0.5)
time.sleep(0.5)
link.light_controller.set_color(0, 0, 0, 0.5)
time.sleep(0.5)
link.light_controller.set_color(0, 0, 255, 0.5)
time.sleep(0.5)
link.light_controller.set_color(0, 0, 0, 0.5)
time.sleep(1.0)
print("Taking off...")
link.high_level_commander.takeoff(0.5, 3.0)
time.sleep(3.2)
print("Landing...")
link.high_level_commander.land(0.05, 3.0)
time.sleep(3.2)
link.high_level_commander.stop()
time.sleep(1.0)
link.close_link() Basically I was doing some synchronized takeoffs, landings, and LED ring color changes, and I was broadcasting I checked the console output when that happened, and found that the reset was caused by this particular ASSERT in the firmware failing. I thought this was odd, as I was only sending broadcast packets, and didn't see any reason why the queue should be backed up. I realized that the So then that raised the question of why those broadcast packets weren't being processed, and after a fair bit of code reading and debugging, I found what I think is the full story:
I was able to get my example working consistently by just opening a unicast link to each copter in the swarm, and that seemed to mitigate the issue for now. I was thinking about making some modifications to the firmware, either:
Decided against doing either of these, since neither idea seemed ideal, and I had a workaround I could work with, but I hope this helps. |
I made an tichet of the latter issue in the crazyflie firmware (bitcraze/crazyflie-firmware#990). This might be causing more issues that we are dealing with it recently, but we missed this issue in the overal discussion. Thanks for notifying us though! |
This discussion is carried over from this forum post
Most of the context for my project is in the forum post above, but I'll summarize here as well:
I've forked this repository (as well as
crazyflie-lib-python
) with the goal of adding support for broadcasting to multiple drones, similar to the way thecrazyswarm
project works, and using that to build a swarm controller application. I'm currently running into issues where the drones don't seem to be responding to the broadcasted commands I'm attempting to send.As mentioned in the forum post, I'm testing with the following setup:
radio://0/55/2M/E7E7E7E7E7
and one atradio://0/55/2M/E7E7E7E7E8
, both flashed with the latest firmware (including the latest nrf firmware)crazyradio-firmware
I'm trying to broadcast to the drones by opening a link at "broadcast address"
radio://0/55/2M/FFE7E7E7E7
, and sending out high level commander packets for takeoff/land commands, but the drones do not respond to these commands. As I was learning in the forum post, there's a command to enable "noAck" mode for the radio, which I'm likely missing, and opening a link to the broadcast address manually might not be the right way to go about it, so I'm looking for some pointers on what I should be doing to implement this functionality.The text was updated successfully, but these errors were encountered: