Problems controlling LED Ring from Python controller. [SOLVED]

Firmware/software/electronics/mechanics
xpeiro
Beginner
Posts: 9
Joined: Sun Aug 09, 2015 5:12 pm

Problems controlling LED Ring from Python controller. [SOLVED]

Post by xpeiro »

Hi everyone, I'm new to the forum.

I've been able to develop a controller to fly my Crazyflie 2.0 getting the inputs from my HTML5 user interface: using the HTML5 gamepad API, the client gets the inputs from any controller, sends them to the server, where they are stored in a MongoDB database, and the python controller gets the inputs and sets roll, pitch, yaw and thrust using crazyflie.commander.send_setpoint(roll, pitch, yaw, thrust).

This works fine, it's very responsive and flying the drone is breeze.

However, I'm now trying to set the parameters of the LED Ring using the same python API and I'm running into problems. Here's the code:

Code: Select all

import sys
import time
sys.path.append("crazyflie/lib")

import cflib.crtp
from cflib.crazyflie import Crazyflie
from threading import Thread
import logging

crazyflie = Crazyflie()
logging.basicConfig(level=logging.ERROR)
cflib.crtp.init_drivers(enable_debug_driver=False)

def connected(link_uri):
    print "Connected"
    crazyflie.param.set_value("ring.headlightEnable", str(1))
    time.sleep(5)
    crazyflie.param.set_value("ring.headlightEnable", str(0))
    time.sleep(5)       
    crazyflie.param.set_value("ring.headlightEnable", str(1))

def param_updated_callback(name, value):
    print("Update Callback:"+str(name)+" "+str(value))

def connection_failed(link_uri, msg):
    print "Connection failed"

def connection_lost(link_uri, msg):
    print "Connection lost"

def disconnected(link_uri):
    print "Disconnected"

print "Scanning interfaces for Crazyflies..."
available = cflib.crtp.scan_interfaces()
if len(available) >= 1:
    print "Crazyflies found:"
    print str(available)
    link = available[0][0]
    for i in xrange(1, len(available)):
        if "80" in available[i][0]:
            link = available[i][0]
    print("Connecting to:" + link)
    crazyflie.connected.add_callback(connected)
    crazyflie.disconnected.add_callback(disconnected)
    crazyflie.connection_failed.add_callback(connection_failed)
    crazyflie.connection_lost.add_callback(connection_lost)
    crazyflie.param.add_update_callback("ring", "headlightEnable", param_updated_callback)
    crazyflie.open_link(link)
else:
    print "No Crazyflies found"
As you can see, it's pretty simple:

Establishes a connection and once connected turns the headlight on, waits 5 seconds, turns it off and after another 5 seconds, turns it back on.

The problem is, it seems like it only registers the first param.set_value. The headlight turns on and never turns off. The param update callback outputs the following:

Code: Select all

Connected
Update Callback:ring.headlightEnable 1
Update Callback:ring.headlightEnable 1
Update Callback:ring.headlightEnable 1
Update Callback:ring.headlightEnable 1
Implying that all param.set_values had value 1, which isn't true, and there's an extra param.set_values to boot.

I've tested the LED ring using cfclient, and it works fine, the headlights turn on and off without a hitch.

Does anyone have an idea of what I'm missing here? Any help is much appreciated. Thanks in advance.
Last edited by xpeiro on Wed Aug 12, 2015 12:22 pm, edited 1 time in total.
chad
Expert
Posts: 555
Joined: Sun Sep 28, 2014 12:54 am
Location: New York, USA
Contact:

Re: Problems controlling LED Ring from Python controller.

Post by chad »

Hi! Welcome to the forum!! Looks a cool system you've set up.

Just quickly off the top of my head, could it be that you're passing a string to set_value instead of a number so that it ends up always being interpreted as a positive value (i.e. 1)?

Code: Select all

crazyflie.param.set_value("ring.headlightEnable", str(0))
versus

Code: Select all

crazyflie.param.set_value("ring.headlightEnable", 0)
Crazyflier - my CF journal...
4x Crazyflie Nano (1.0) 10-DOF + NeoPixel Ring mod.
3x Crazyflie 2.0 + Qi Charger and LED Decks.
Raspberry Pi Ground Control.
Mac OS X Dev Environment.
Walkera Devo7e, ESky ET6I, PS3 and iOS Controllers.
xpeiro
Beginner
Posts: 9
Joined: Sun Aug 09, 2015 5:12 pm

Re: Problems controlling LED Ring from Python controller.

Post by xpeiro »

Hi, thanks! It's part my final year project for university, a universal user interface for robots in HTML5. It has virtual joysticks, live video/audio streaming, voice commands, gamepad support, custom input/output... I've got it to control several robots, but the crazyflie is definitely the most fun :). If anyone is interested, this is the project: https://github.com/xpeiro/HTML5RUI

I did a bit of research on the forum before posting and found this post viewtopic.php?f=6&t=1152&p=6353#p6353, where it passes the value as a string:

Code: Select all

self.helper.cf.param.set_value("ring.headlightEnable", str(state))
.

also in lib/cfclient/ui/tabs/FlightTab.py , from the python client repo:

Code: Select all

self._led_ring_headlight.clicked.connect(
                    lambda enabled:
                    self.helper.cf.param.set_value("ring.headlightEnable",
                                                   str(enabled)))
So I just assumed that was the case. Anyhow, I tested the code using numbers, without str(), and it didn't do anything, so I think that's not the issue.

Thanks for answering in any case!
chad
Expert
Posts: 555
Joined: Sun Sep 28, 2014 12:54 am
Location: New York, USA
Contact:

Re: Problems controlling LED Ring from Python controller.

Post by chad »

OK! I fiddled with the code this time instead of just spouting off guesses. I think the problem is still as I stated - str(0) being interpreted as a positive value. But, the fix is not to remove the str() conversion but to use "True" and "False" rather than 1 and 0.

So, try this. I think it will work for you:

Code: Select all

crazyflie.param.set_value("ring.headlightEnable", str(True))
time.sleep(5)
crazyflie.param.set_value("ring.headlightEnable", str(False))
time.sleep(5)       
crazyflie.param.set_value("ring.headlightEnable", str(True))
Let us know if that solves it.
Crazyflier - my CF journal...
4x Crazyflie Nano (1.0) 10-DOF + NeoPixel Ring mod.
3x Crazyflie 2.0 + Qi Charger and LED Decks.
Raspberry Pi Ground Control.
Mac OS X Dev Environment.
Walkera Devo7e, ESky ET6I, PS3 and iOS Controllers.
xpeiro
Beginner
Posts: 9
Joined: Sun Aug 09, 2015 5:12 pm

Re: Problems controlling LED Ring from Python controller.

Post by xpeiro »

Thanks Chad. I tried using str(True) and str(False), and the result was the same as using str(1) and str(0). With numbers not casted to string, the headlight didn't do anything. With booleans or numbers casted to string, the result was the same: The headlight turns on, doesn't turn off and the callback outputs the same thing (notice value = 1, so at some point True is converted to 1):

Code: Select all

Update Callback:ring.headlightEnable 1
Update Callback:ring.headlightEnable 1
Update Callback:ring.headlightEnable 1
Update Callback:ring.headlightEnable 1
I'm really baffled by what's happening here. I really appreciate the help!
chad
Expert
Posts: 555
Joined: Sun Sep 28, 2014 12:54 am
Location: New York, USA
Contact:

Re: Problems controlling LED Ring from Python controller.

Post by chad »

So here's what I did to test... Maybe this will give us some insight:

1) I have a clone of the latest on GitHub for crazyflie-clients-python on develop branch.

2) In: crazyflie-clients-python/lib/cfclient/ui/tabs/FlightTab.py, I added the following at line 328 (in def connected):

Code: Select all

self.helper.cf.param.set_value("ring.headlightEnable", str(True))
time.sleep(5)
self.helper.cf.param.set_value("ring.headlightEnable", str(False))
time.sleep(5)       
self.helper.cf.param.set_value("ring.headlightEnable", str(True))
I had to change crazyflie.param to self.helper.cf.param since that's the object available in the FlightTab class. I figured this mimics what you're doing with having the light go on and off after "connected" is called.

3) I actually got an error from the time.sleep calls here likely because of the way time is imported in the FlightTab.py.

Code: Select all

Traceback (most recent call last):
  File "crazyflie-clients-python/lib/cfclient/ui/tabs/FlightTab.py", line 329, in connected
    time.sleep(5)
AttributeError: 'builtin_function_or_method' object has no attribute 'sleep'
OK. Easy fix. Change line 40:

Code: Select all

#from time import time
import time
4) Try it out with time.sleep fixed and it works. Actually, I get an error from LEDTab.py on the last toggle, but the headlight things still seems to work (read: I didn't look into this error).

5) It actually works this way with either str(True) or str(1) - the suggestion from my last post was a red herring. I must have done the time.sleep fix in between my attempts. I'm worthless on weekends. ;)

Anyway, does this help isolate anything for you?
Crazyflier - my CF journal...
4x Crazyflie Nano (1.0) 10-DOF + NeoPixel Ring mod.
3x Crazyflie 2.0 + Qi Charger and LED Decks.
Raspberry Pi Ground Control.
Mac OS X Dev Environment.
Walkera Devo7e, ESky ET6I, PS3 and iOS Controllers.
xpeiro
Beginner
Posts: 9
Joined: Sun Aug 09, 2015 5:12 pm

Re: Problems controlling LED Ring from Python controller.

Post by xpeiro »

Hey Chad, thanks! I've tried the changes to FlightTab.py, and sure enough, it works fine. Turns on and off with the 5 seconds interval.

The first thing I tried when encountering the problem was using cfclient to do the toggle, and as I said in the first post it works flawlessly.

This is an indication that there's no issue with the param.set_value calls themselves, but that some additional, previous configuration is required, that I'm failing to do in my controller, and is being done in cfclient.

I've added

Code: Select all

if self.helper.cf.mem.ow_search(vid=0xBC, pid=0x01):
before the calls, but I'm guessing all that does is check if the LED Ring is available, and therefore, the result is the same: headlight on, doesn't turn off.

Would you mind trying my code directly? Just need to modify line 3 with the path to your lib directory and run it:

Code: Select all

import sys
import time
sys.path.append("crazyflie/lib")

import cflib.crtp
from cflib.crazyflie import Crazyflie
from threading import Thread
import logging

crazyflie = Crazyflie()
logging.basicConfig(level=logging.ERROR)
cflib.crtp.init_drivers(enable_debug_driver=False)

def connected(link_uri):
    print "Connected"
    if crazyflie.mem.ow_search(vid=0xBC, pid=0x01):
        crazyflie.param.set_value("ring.headlightEnable", str(True))
        time.sleep(5)
        crazyflie.param.set_value("ring.headlightEnable", str(False))
        time.sleep(5)       
        crazyflie.param.set_value("ring.headlightEnable", str(True))

def param_updated_callback(name, value):
    print("Update Callback:"+str(name)+" "+str(value))

def connection_failed(link_uri, msg):
    print "Connection failed"

def connection_lost(link_uri, msg):
    print "Connection lost"

def disconnected(link_uri):
    print "Disconnected"

print "Scanning interfaces for Crazyflies..."
available = cflib.crtp.scan_interfaces()
if len(available) >= 1:
    print "Crazyflies found:"
    print str(available)
    link = available[0][0]
    for i in xrange(1, len(available)):
        if "80" in available[i][0]:
            link = available[i][0]
    print("Connecting to:" + link)
    crazyflie.connected.add_callback(connected)
    crazyflie.disconnected.add_callback(disconnected)
    crazyflie.connection_failed.add_callback(connection_failed)
    crazyflie.connection_lost.add_callback(connection_lost)
    crazyflie.param.add_update_callback("ring", "headlightEnable", param_updated_callback)
    crazyflie.open_link(link)
else:
    print "No Crazyflies found"
That way we can discard that the actual code is wrong, instead of just incomplete. Thanks again!
chad
Expert
Posts: 555
Joined: Sun Sep 28, 2014 12:54 am
Location: New York, USA
Contact:

Re: Problems controlling LED Ring from Python controller.

Post by chad »

Well... This is very curious indeed! I ran your code 7 times without changing anything. 2 of those times the light turned off. It did not go ON, OFF, ON however...

So the results were:

5x (stays on):
Update Callback:ring.headlightEnable 1
Update Callback:ring.headlightEnable 1
Update Callback:ring.headlightEnable 1
Update Callback:ring.headlightEnable 1

2x (turns off):
Update Callback:ring.headlightEnable 1
Update Callback:ring.headlightEnable 1
Update Callback:ring.headlightEnable 1
Update Callback:ring.headlightEnable 0

So, I decided to do essentially the same thing in crazyflie-clients-python/examples/basicparam.py by putting in the calls to turn on and off the headlight and adding the parameter update callback. Then I ran it 7 times without changing anything. 4 of those times the light turned off, but a couple of them were different (and like your code, none of them worked like it's supposed to):

3x (stays on):
Update Callback:ring.headlightEnable 1
Update Callback:ring.headlightEnable 1
Update Callback:ring.headlightEnable 1
Update Callback:ring.headlightEnable 1

3x (turns off):
Update Callback:ring.headlightEnable 1
Update Callback:ring.headlightEnable 1
Update Callback:ring.headlightEnable 1
Update Callback:ring.headlightEnable 0

1x (turns off):
Update Callback:ring.headlightEnable 1
Update Callback:ring.headlightEnable 1
Update Callback:ring.headlightEnable 0
Update Callback:ring.headlightEnable 1

So... Doesn't work in your code, doesn't work in basicparam.py example, but does work in cfclient... Your code is not wrong but I'm not really sure what's happening in cflcient that isn't in the other two situations. I think this is a little lower level than I've dived and I'm not sure what's up here... :-(

We probably need input from marcus or arnaud on this one.
Crazyflier - my CF journal...
4x Crazyflie Nano (1.0) 10-DOF + NeoPixel Ring mod.
3x Crazyflie 2.0 + Qi Charger and LED Decks.
Raspberry Pi Ground Control.
Mac OS X Dev Environment.
Walkera Devo7e, ESky ET6I, PS3 and iOS Controllers.
xpeiro
Beginner
Posts: 9
Joined: Sun Aug 09, 2015 5:12 pm

Re: Problems controlling LED Ring from Python controller.

Post by xpeiro »

Thanks for all the effort! It is really weird that this doesn't just work, since I'm able to fly the drone perfectly without any other configuration. I'm sure it will end up being something trivial, but I just don't see what I'm missing here.

I thought it might be that I was trying to toggle the headlight too "early" and the crazyflie was still doing some initializing so I tried this modification to the connected callback, adding a 30 second "cooldown" after connection and before toggling the headlight:

Code: Select all

def connected(link_uri):
    print "Connected"
    time.sleep(30)
    print("starting toggle now")
    if crazyflie.mem.ow_search(vid=0xBC, pid=0x01):
        print("toggle on")
        crazyflie.param.set_value("ring.headlightEnable", str(True))
        time.sleep(5)
        print("toggle off")
        crazyflie.param.set_value("ring.headlightEnable", str(False))
        time.sleep(5)
        print("toggle on")
        crazyflie.param.set_value("ring.headlightEnable", str(True))
    print("done")
but no dice. The messages print on time, but the headlight doesn't change accordingly. I'm browsing the cfclient code to see if I can find some config that I'm missing that will make this work, but I've been unsuccessful. Here's hoping someone knows whats up. Thanks again!
xpeiro
Beginner
Posts: 9
Joined: Sun Aug 09, 2015 5:12 pm

Re: Problems controlling LED Ring from Python controller.

Post by xpeiro »

So I found the solution, finally. Here's the process I followed to get to it.

Firstly, the extra callback print out was bugging me. It had to mean that either the lib or the firmware was doing it, it couldn't just be randomly there. In my previous post, I tried to let this callback occur by giving it 30 seconds, using time.sleep(30), but it didn't work. So I commented out all code from the "connected" callback, and sure enough, the headlight callback printed out once the current state of the headlight.

Secondly, I noticed that all the callbacks were printed out in bulk, AFTER all the calls to param.set_value, when they should be appearing as they're called.

Thirdly, I started thinking, "what differentiates my controller and basicparam.py, where it doesn't work, and cfclient where it does?". One of the things is: it has a GUI. And if it has a GUI, it has to have threading or asynchronous processing of some kind, to keep the GUI from locking up.

With these three things, I thought: what if by doing time.sleep(x) I'm hijacking the main thread, not allowing the first headlight callback (done by the lib or the firmware, not by my code) to occurr, which is somehow blocking further changes to the state of the headlight?

To test this, I changed the time.sleep(30) to an asynchronous, 3 second delayed, thread execution:

Code: Select all

# at start of code
import threading
#
def connected(link_uri):    
    print "Connected"
    threading.Timer(3, f).start()
    
def f():
    print("starting toggle")
    if crazyflie.mem.ow_search(vid=0xBC, pid=0x01):
        print("toggle on")
        crazyflie.param.set_value("ring.headlightEnable", str(True))
        time.sleep(5)
        print("toggle off")
        crazyflie.param.set_value("ring.headlightEnable", str(False))
        time.sleep(5)
        print("toggle on")
        crazyflie.param.set_value("ring.headlightEnable", str(True))
    print("done")
And it worked. So then I thought: is it really necessary to wait for that first callback, or is making the calls from a different thread enough? so I tried:

Code: Select all

# at start of code
import thread
#
def connected(link_uri):    
    print "Connected"
    #start new thread, no delay
    thread.start_new_thread( f, ())
    
def f():
    print("starting toggle")
    if crazyflie.mem.ow_search(vid=0xBC, pid=0x01):
        print("toggle on")
        crazyflie.param.set_value("ring.headlightEnable", str(True))
        time.sleep(5)
        print("toggle off")
        crazyflie.param.set_value("ring.headlightEnable", str(False))
        time.sleep(5)
        print("toggle on")
        crazyflie.param.set_value("ring.headlightEnable", str(True))
    print("done")
And it worked. So the solution is to make the calls to param.set_value in a separate thread. At least in my case, this is what solved the problem. If someone has an explanation of why this is, it would be great to know.

Anyways, thanks for all the help Chad, your input helped to put my brain on its way to a solution even though I didn't even realize at first.
Post Reply