Code with CircuitPython

Feather and Crickit Prep

We'll be using CircuitPython for this project. Are you new to using CircuitPython? No worries, there is a full getting started guide here.

For more info on the Feather nRF52840 Express, check out this guide.

Adafruit suggests using the Mu editor to edit your code and have an interactive REPL in CircuitPython. You can learn about Mu and its installation in this tutorial.

Follow this guide for instructions on installing the latest release version of CircuitPython for the Feather nRF52840 Express:

Note: This project uses the Bluetooth functionality available in CircuitPython 5.0.0-beta.0 and later, on nRF52840 boards such as the Feather nRF52840 Express and Circuit Playground Bluefruit. Make sure you are using that version or later.

Libraries

You'll also need to add the following libraries for this project. Follow this guide on adding libraries.

Plug your nRF Feather board into your computer via a USB cable. Please be sure the cable is a good power+data cable so the computer can talk to the board.

A new disk should appear in your computer's file explorer/finder called CIRCUITPY. This is the place we'll copy the code and code library. If you can only get a drive named FTHR840BOOT, load CircuitPython per the guide above.

Download the latest CircuitPython libraries to your computer using the green button below. Match the library you get to the version of CircuitPython you are using. Save to your computer's hard drive where you can find it.

With your file explorer/finder, browse to the bundle and open it up. Copy the following folders and files from the library bundle to your CIRCUITPY lib directory you made earlier:

The ones you'll need are:

  • adafruit_ble (folder)
  • adafruit_bluefruit_connect (folder)
  • adafruit_bus_device (folder)
  • adafruit_crickit.mpy (file)
  • adafruit_motor (folder)
  • adafruit_seesaw (folder)

All of the other necessary code is baked into CircuitPython!

CircuitPython Code

Copy the program below and paste it into a new document in Mu. Then, save it from Mu onto your CIRCUITPY flash drive as code.py. As soon as CircuitPython restarts, the servo arm will move into its neutral position and await your Bluetooth connection!

# BLE Crickit Light Switch
# Use with the Adafruit BlueFruit LE Connect app
# Works with CircuitPython 5.0.0-beta.0 and later
# running on an nRF52840 Feather board and Crickit FeatherWing
# micro servo, 3D printed switch actuator

import time

import board
import digitalio

from adafruit_crickit import crickit

from adafruit_ble import BLERadio
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
from adafruit_ble.services.nordic import UARTService

from adafruit_bluefruit_connect.packet import Packet
# Only the packet classes that are imported will be known to Packet.
from adafruit_bluefruit_connect.button_packet import ButtonPacket

# Prep the status LEDs on the Feather
blue_led = digitalio.DigitalInOut(board.BLUE_LED)
red_led = digitalio.DigitalInOut(board.RED_LED)
blue_led.direction = digitalio.Direction.OUTPUT
red_led.direction = digitalio.Direction.OUTPUT

ble = BLERadio()
uart_service = UARTService()
advertisement = ProvideServicesAdvertisement(uart_service)

UP_ANGLE = 180
NEUTRAL_ANGLE = 120
DOWN_ANGLE = 60
crickit.servo_1.angle = NEUTRAL_ANGLE

angle = NEUTRAL_ANGLE  # use to track state

print("BLE Light Switch")
print("Use Adafruit Bluefruit app to connect")
while True:
    blue_led.value = False
    ble.start_advertising(advertisement)

    while not ble.connected:
        # Wait for a connection.
        pass
    blue_led.value = True  # turn on blue LED when connected
    while ble.connected:
        if uart_service.in_waiting:
            # Packet is arriving.
            red_led.value = False  # turn off red LED
            packet = Packet.from_stream(uart_service)
            if isinstance(packet, ButtonPacket) and packet.pressed:
                red_led.value = True  # blink to show a packet has been received
                if packet.button == ButtonPacket.UP and angle != UP_ANGLE:  # UP button pressed
                    angle = NEUTRAL_ANGLE - 45  # set anticipation angle, opposite of goal angle
                    for a in range(angle, UP_ANGLE+1, 1):  # anticipation angle, ramp to goal angle
                        crickit.servo_1.angle = a
                    time.sleep(0.1)  # wait a moment
                    crickit.servo_1.angle = NEUTRAL_ANGLE  # then return to neutral angle
                    angle = UP_ANGLE  # set state to prevent redundant hits
                elif packet.button == ButtonPacket.DOWN and angle != DOWN_ANGLE:  # DOWN button
                    angle = NEUTRAL_ANGLE + 45
                    for a in range(angle, DOWN_ANGLE-1, -1):
                        crickit.servo_1.angle = a
                    time.sleep(0.1)  # wait a moment
                    crickit.servo_1.angle = NEUTRAL_ANGLE  # then return to neutral angle
                    angle = DOWN_ANGLE  # set state to prevent redundant hits

How the Program Works

The first part of the program sets up a UARTServer, which is a BLE service that wirelessly sends streams of characters between a BLE device and a client computer (which could be a phone or tablet). The program then starts advertising its UART service to anyone listening. The client computer receives the advertisement, and tells the user it's available. The user can then choose to connect as a client of the service.

One a connection is established, the program code waits for incoming command packets from the client, specifically a ButtonPacket. When one arrives, the program either adjusts sees which button has been pressed: up-arrow or down-arrow.

Servo Motion

The servo will initially go to a neutral, half-way position of 120°.

In order to create a nice motion with the servo arm that can stylishly flip the switch, and still allow manual use of it, we'll do a few things when the button packet is received.

First, we'll anticipate the motion. This means that if the goal is to move the switch up, first the arm will swing back 45° away from the neutral position of 120°, sort of like pulling your arm back before a throw, or squatting down before a jump.

Then, we'll rotate to the goal angle of 180° for up or 60° for down. Rather than jump immediately to the goal value, the code moves the servo one degree at a time in the specified direction, until it hits the numerical limit. This gives us a slower, smoother motion.

Once the goal angle is reached, we pause for a brief moment and then return the arm to the neutral angle.

Control the Light Switch with the Bluefruit LE Connect App

Download the App

To control the servo and NeoPixels from a client device (phone or tablet), you use the Adafruit Bluefruit LE Connect App. Install it from the Apple App Store or Google Play App Store.

Connect to Feather

With the Crickit plugged into DC power and turned on with its on-board power switch, the Feather will also get power and automatically start running your code. Then start up the Bluefruit LE Connect app, and make sure it's in Central Mode (left button on the bottom).  When you start the app, you should see a device named CIRCU or CIRCUITPY. If there's a long list of devices, you can shorten it by turning on the "Must have UART Service" switch.

To connect to the Light Switch, touch the Connect button. You should see "Connecting" and then "Discovering Services".

Troubleshooting

If you don't see CIRCU or CIRCUITPY right away, try pulling down to refresh. If that doesn't work, try turning Bluetooth off and back on on your phone or tablet and restarting the app.

Device Menu

After you connect, you'll see a menu of how you can interact with the device. Choose Controller.

Controller Menu

After you choose Controller, you'll see another screen with more choices. Control Pad is used for this project.

Move the Servo Arm

Here's the Control Pad screen. Use the Up and Down buttons to move the servo arm up and down. Later, we'll use this to flip the lights on and off!

You should see the arm start in a neutral center position, and then when you press the up ↑ button, the arm will travel down a bit and then fully up to the top of the range chosen in the code, finally returning to its neutral position. The same motion pattern applies in reverse for the down ↓ button.

Next, we'll hook it up to the actual light switch plate!

This guide was first published on Feb 08, 2019. It was last updated on Feb 08, 2019.
This page (Code with CircuitPython) was last updated on May 30, 2020.