NeoPixel Ring Mod – Part 4

NeoPixel Ring Mod – Part 4

My last entry on the NeoPixel ring modification detailed how I “permanently” mounted the ring and the post before that was about how I got the firmware working like I wanted it. Prior to that, my first entry explained what I wanted to do and elaborated on some testing I conducted prior to deciding on what ring to use. This entry wraps my NeoPixel Ring mod up by exposing how I hacked in some controller support.

The controller support was a little tougher than the rest of the modification because I didn’t have any examples to base my changes on. Furthermore, I had to learn the architecture of the Python client but fortunately it’s in Python which is both fun and pretty accessible!

What I’d hoped to do was make it possible to cycle through the various LED “effects” with a controller button (this is what the Bitcraze video demonstrated and it seemed like a good interface). I also wanted everything to be runtime discoverable so there were little or no “hardcoded” dependencies. I wanted the client to work the same whether or not the firmware running supported the NeoPixel ring mod.

I believe I was able to accomplish all of this.


There were six files I needed to edit to get this working. All of the edits can be found on the neopixel_dev branch of my GitHub fork but I’ll give a high-level overview of what I did here. This was an adventure because it gave me the chance to explore the architecture of the Crazyflie python client a bit. I’ll give some special thanks to forum member whoenig for helping me out when I got a little stuck on the cfheadless implementation.

cfclient/config.json (the main client config file):

  • Add a “writiable” variable “ring_effect” initially set to 0 (this will have no effect on the client unless the Crazyflie is running firmware with support for the NeoPixel Ring)

cfclient/ui/FlightTab.py:

  • In the init function, I added a callback using param.add_update_callback to allow the client to set the max ring effect number based on what the Crazyflie firmware says it is. This number is automagically set by the number of “effect” functions placed into the effectsFct array in modules/src/neopixelring.c of the Crazyflie firmware code.
  • Also in the init function, I added a callback to the input device reader object of “helper” to call param.set_value when the ring effect number has changed (i.e., via editing the parameter on the Parameters tab or hitting a controller button mapped to change the value).

cfclient/utils/pygamereader.py (this is where I handle controller button presses):

  • In start_input, I initialize ring_effect to “false” in the data variable. ring_effect here is just a toggle variable for button presses. I could start it out as true or false, it doesn’t really matter.
  • In read_input, I simply flip the state of the button when we get a hit (we’re using a shoulder button in our mapping) it defaults to false (as mentioned above) and a press flips it to true, another swaps it to false again. The true/false values are not critical, it’s just a way to detect subsequent button hits.

cflclient/utils/input.py (this is where the bulk of my changes are made):

  • In the init function, get the default button state (false) from the config file.
  • Also in the init function, I default both the ring effect and max ring effect numbers to 0 (sane defaults in case NeoPixel Ring isn’t supported in the Crazyflie’s currently running firmware).
  • Once again, in the init function, I link up ring_effect_updated variable to Caller().
  • I created a new function (set_ring_effect_max) to set the ring effect max number. This just sets ring effect max to the value (as an int) it receives.
  • In the read_input function, I set the ring effect button state to the value in the input device data (initially false).
  • Still in the read_input function, I added the logic to increment the ring effect value for each button press. When it goes over the max ring effect number, I simply reset it back to 0 (off).
  • Also in the read_input function, I make the callback stating the effect number has been updated (and pass the new number).
  • Finally, still in the read_input function, I capture the “old” button state so I can compare to know if I get another button hit.
  • I only do the above three items in the read_input function if the ring effect max number is bigger than 0. Remember I default it to zero so, if the Crazyflie firmware doesn’t have support for NeoPixel Ring effects, the max value will never be updated rendering all of this functionality non-operational (which is what we want if the firmware doesn’t support it).

/lib/cfheadless.py:

  • I added a new function called _connected to be called once we get notification that the Crazyflie has successfully connected to the radio.
  • In the _connected function, I do a param.request_param_update for any interesting paramemters (i.e.:, ring.neffect and pressure sensor for altitude hold functionality).
  • In connect_crazyflie, I added a param update callback to get the max effect number and set it to our ring_effect_max value in the joystickreader (input) class.
  • Also in connect_crazyflie, I added a callback to our joystick reader object to set the the ring.effect value in the Crazyflie firmware whenever it happens to get updated (i.e.: when we hit the controller button).
  • Finally, in connect_crazyflie, I wired up the new “_connected” function to cf.connected via a callback.

conf/input/*json (mapping the controller button):

  • For whatever controller config you use, add a mapping for “NeoPixel Effect” to the ring_effect key. This needs to be a button input (not an axis). I use PS3 right shoulder button ID: 11.
  • {“name”:”NeoPixel Effect”, “type”:”Input.BUTTON”, “id”:11, “scale”:1.0, “key”:”ring_effect”}

And, ummm, that was it. Well, that completes the NeoPixel Ring mod. There’s still a lot of fun and experimentation to be had by creating new effects though. I’ve played around with changing color based on thrust input. I also want to try to use 3 pixels for each motor to somehow represent the thrust of each. Maybe I can play with displaying data from the magnetometer or barometer (on 10-DOF models). Finally, I thought it might be cool to put a “timer” of sorts to represent flight time via minutes and seconds dots. The sky, as they say, is the limit. 😉

You can find all the changes listed above in the neopixel_dev branch of my cfclient GitHub fork.

2 thoughts on “NeoPixel Ring Mod – Part 4

  1. Super-excellent how-to guide. Not only did you put a ton of work into the project, you really went the extra ten miles on this documentation effort — well done!

Comments are closed.

Comments are closed.