-
Notifications
You must be signed in to change notification settings - Fork 775
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2653 from jedgarpark/powerwash-controller
first commit powerwasher controller code
- Loading branch information
Showing
1 changed file
with
267 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,267 @@ | ||
# SPDX-FileCopyrightText: 2023 John Park for Adafruit Industries | ||
# SPDX-License-Identifier: MIT | ||
# PowerWash Simulator controller | ||
""" | ||
Hardware: | ||
# QT Py RP2040, BNO055, Wiichuck adapter, Piezo driver on D10 ('MO' pin on silk) | ||
User control: | ||
nozzle heading/roll (sensor is mounted "sideways" in washer handle) = mouse x/y | ||
nozzle tap/shake = next nozzle tip | ||
wii C button (while level) = rotate nozzle tip | ||
wii Z button = trigger water | ||
wii joystick = WASD | ||
wii roll right = change stance stand/crouch/prone | ||
wii roll left = jump | ||
wii pitch up + C button = set target angle offset | ||
wii pitch down = show dirt | ||
wii pitch down + C button = toggle aim mode | ||
""" | ||
|
||
import time | ||
import math | ||
import board | ||
from simpleio import map_range, tone | ||
import adafruit_bno055 | ||
import usb_hid | ||
from adafruit_hid.mouse import Mouse | ||
from adafruit_hid.keycode import Keycode | ||
from adafruit_hid.keyboard import Keyboard | ||
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS | ||
from adafruit_nunchuk import Nunchuk | ||
|
||
# =========================================== | ||
# constants | ||
DEBUG = False | ||
CURSOR = True # use to toggle cursor movment during testing/use | ||
SENSOR_PACKET_FACTOR = 10 # Ratio of BNo055 data packets per Wiichuck packet | ||
HORIZONTAL_RATE = 127 # mouse x speed | ||
VERTICAL_RATE = 63 # mouse y speed | ||
WII_C_KEY_1 = Keycode.R # rotate nozzle | ||
WII_C_KEY_2 = Keycode.C # aim mode | ||
WII_PITCH_UP = 270 # value to trigger wiichuk up state | ||
WII_PITCH_DOWN = 730 # value to trigger wiichuck down state | ||
WII_ROLL_LEFT = 280 # value to trigger wiichuck left state | ||
WII_ROLL_RIGHT = 740 # value to trigger wiichuck right state | ||
TAP_THRESHOLD = 6 # Tap sensitivity threshold; depends on the physical sensor mount | ||
TAP_DEBOUNCE = 0.3 # Time for accelerometer to settle after tap (seconds) | ||
|
||
# =========================================== | ||
# Instantiate I2C interface connection | ||
# i2c = board.I2C() # For board.SCL and board.SDA | ||
i2c = board.STEMMA_I2C() # For the built-in STEMMA QT connection | ||
|
||
# =========================================== | ||
# setup USB HID mouse and keyboard | ||
mouse = Mouse(usb_hid.devices) | ||
keyboard = Keyboard(usb_hid.devices) | ||
layout = KeyboardLayoutUS(keyboard) | ||
|
||
# =========================================== | ||
# wii nunchuk setup | ||
wiichuk = Nunchuk(i2c) | ||
|
||
# =========================================== | ||
# Instantiate the BNo055 sensor | ||
sensor = adafruit_bno055.BNO055_I2C(i2c) | ||
sensor.mode = 0x0C # Set the sensor to NDOF_MODE | ||
|
||
# =========================================== | ||
# beep function | ||
def beep(freq=440, duration=0.2): | ||
"""Play the piezo element for duration (sec) at freq (Hz). | ||
This is a blocking method.""" | ||
tone(board.D10, freq, duration) | ||
|
||
# =========================================== | ||
# debug print function | ||
def printd(line): | ||
"""Prints a string if DEBUG is True.""" | ||
if DEBUG: | ||
print(line) | ||
|
||
# =========================================== | ||
# euclidean distance function | ||
def euclidean_distance(reference, measured): | ||
"""Calculate the Euclidean distance between reference and measured points | ||
in a universe. The point position tuples can be colors, compass, | ||
accelerometer, absolute position, or almost any other multiple value data | ||
set. | ||
reference: A tuple or list of reference point position values. | ||
measured: A tuple or list of measured point position values.""" | ||
# Create list of deltas using list comprehension | ||
deltas = [(reference[idx] - count) for idx, count in enumerate(measured)] | ||
# Resolve squared deltas to a Euclidean difference and return the result | ||
# pylint:disable=c-extension-no-member | ||
return math.sqrt(sum([d ** 2 for d in deltas])) | ||
|
||
# =========================================== | ||
# BNO055 offsets | ||
# Preset the sensor calibration offsets | ||
# User sets this up once for geographic location using `bno055_calibrator.py` in library examples | ||
sensor.offsets_magnetometer = (198, 238, 465) | ||
sensor.offsets_gyroscope = (-2, 0, -1) | ||
sensor.offsets_accelerometer = (-28, -5, -29) | ||
printd(f"offsets_magnetometer set to: {sensor.offsets_magnetometer}") | ||
printd(f"offsets_gyroscope set to: {sensor.offsets_gyroscope}") | ||
printd(f"offsets_accelerometer set to: {sensor.offsets_accelerometer}") | ||
|
||
# =========================================== | ||
# controller states | ||
wii_roll_state = 1 # roll left 0, center 1, roll right 2 | ||
wii_pitch_state = 1 # pitch down 0, center 1, pitch up 2 | ||
wii_last_roll_state = 1 | ||
wii_last_pitch_state = 1 | ||
c_button_state = False | ||
z_button_state = False | ||
|
||
sensor_packet_count = 0 # Initialize the BNo055 packet counter | ||
|
||
print("PowerWash controller ready, point at center of screen for initial offset:") | ||
beep(400, 0.1) | ||
beep(440, 0.2) | ||
time.sleep(3) | ||
# The target angle offset used to reorient the wand to point at the display | ||
#pylint:disable=(unnecessary-comprehension) | ||
target_angle_offset = [angle for angle in sensor.euler] | ||
beep(220, 0.4) | ||
print("......reoriented", target_angle_offset) | ||
|
||
|
||
while True: | ||
# =========================================== | ||
# BNO055 | ||
# Get the Euler angle values from the sensor | ||
# The Euler angle limits are: +180 to -180 pitch, +360 to -360 heading, +90 to -90 roll | ||
sensor_euler = sensor.euler | ||
sensor_packet_count += 1 # Increment the BNo055 packet counter | ||
# Adjust the Euler angle values with the target_position_offset | ||
heading, roll, pitch = [ | ||
position - target_angle_offset[idx] for idx, | ||
position in enumerate(sensor_euler) | ||
] | ||
printd(f"heading {heading}, roll {roll}") | ||
# Scale the heading for horizontal movement range | ||
# horizontal_mov = map_range(heading, 220, 260, -30.0, 30.0) | ||
horizontal_mov = int(map_range(heading, -16, 16, HORIZONTAL_RATE*-1, HORIZONTAL_RATE)) | ||
printd(f"mouse x: {horizontal_mov}") | ||
|
||
# Scale the roll for vertical movement range | ||
vertical_mov = int(map_range(roll, 9, -9, VERTICAL_RATE*-1, VERTICAL_RATE)) | ||
printd(f"mouse y: {vertical_mov}") | ||
if CURSOR: | ||
mouse.move(x=horizontal_mov) | ||
mouse.move(y=vertical_mov) | ||
|
||
# =========================================== | ||
# sensor packet ratio | ||
# Read the wiichuck every "n" times the BNo055 is read | ||
if sensor_packet_count >= SENSOR_PACKET_FACTOR: | ||
sensor_packet_count = 0 # Reset the BNo055 packet counter | ||
|
||
# =========================================== | ||
# wiichuck joystick | ||
joy_x, joy_y = wiichuk.joystick | ||
printd(f"joystick = {wiichuk.joystick}") | ||
if joy_x < 25: | ||
keyboard.press(Keycode.A) | ||
else: | ||
keyboard.release(Keycode.A) | ||
|
||
if joy_x > 225: | ||
keyboard.press(Keycode.D) | ||
else: | ||
keyboard.release(Keycode.D) | ||
|
||
if joy_y > 225: | ||
keyboard.press(Keycode.W) | ||
else: | ||
keyboard.release(Keycode.W) | ||
|
||
if joy_y < 25: | ||
keyboard.press(Keycode.S) | ||
else: | ||
keyboard.release(Keycode.S) | ||
|
||
# =========================================== | ||
# wiichuck accel | ||
wii_roll, wii_pitch, wii_az = wiichuk.acceleration | ||
printd(f"roll:, {wii_roll}, pitch:, {wii_pitch}") | ||
if wii_roll <= WII_ROLL_LEFT: | ||
wii_roll_state = 0 | ||
if wii_last_roll_state != 0: | ||
keyboard.press(Keycode.SPACE) # jump | ||
wii_last_roll_state = 0 | ||
elif WII_ROLL_LEFT < wii_roll < WII_ROLL_RIGHT: # centered | ||
wii_roll_state = 1 | ||
if wii_last_roll_state != 1: | ||
keyboard.release(Keycode.LEFT_CONTROL) | ||
keyboard.release(Keycode.SPACE) | ||
wii_last_roll_state = 1 | ||
else: | ||
wii_roll_state = 2 | ||
if wii_last_roll_state != 2: | ||
keyboard.press(Keycode.LEFT_CONTROL) # change stance | ||
wii_last_roll_state = 2 | ||
|
||
if wii_pitch <= WII_PITCH_UP: # up used as modifier | ||
wii_pitch_state = 0 | ||
if wii_last_pitch_state != 0: | ||
beep(freq=660) | ||
wii_last_pitch_state = 0 | ||
elif WII_PITCH_UP < wii_pitch < WII_PITCH_DOWN: # level | ||
wii_pitch_state = 1 | ||
if wii_last_pitch_state != 1: | ||
wii_last_pitch_state = 1 | ||
else: | ||
wii_pitch_state = 2 # down sends command and is modifier | ||
if wii_last_pitch_state != 2: | ||
keyboard.send(Keycode.TAB) | ||
beep(freq=110) | ||
wii_last_pitch_state = 2 | ||
|
||
# =========================================== | ||
# wiichuck buttons | ||
if wii_pitch_state == 0: # button use when wiichuck is held level | ||
if wiichuk.buttons.C and c_button_state is False: | ||
target_angle_offset = [angle for angle in sensor_euler] | ||
beep() | ||
beep() | ||
c_button_state = True | ||
if not wiichuk.buttons.C and c_button_state is True: | ||
c_button_state = False | ||
|
||
elif wii_pitch_state == 1: # level | ||
if wiichuk.buttons.C and c_button_state is False: | ||
keyboard.press(WII_C_KEY_1) | ||
c_button_state = True | ||
if not wiichuk.buttons.C and c_button_state is True: | ||
keyboard.release(WII_C_KEY_1) | ||
c_button_state = False | ||
|
||
elif wii_pitch_state == 2: # down | ||
if wiichuk.buttons.C and c_button_state is False: | ||
keyboard.press(WII_C_KEY_2) | ||
c_button_state = True | ||
if not wiichuk.buttons.C and c_button_state is True: | ||
keyboard.release(WII_C_KEY_2) | ||
c_button_state = False | ||
|
||
if wiichuk.buttons.Z and z_button_state is False: | ||
mouse.press(Mouse.LEFT_BUTTON) | ||
z_button_state = True | ||
if not wiichuk.buttons.Z and z_button_state is True: | ||
mouse.release(Mouse.LEFT_BUTTON) | ||
z_button_state = False | ||
|
||
# =========================================== | ||
# BNO055 tap detection | ||
# Detect a single tap on any axis of the BNo055 accelerometer | ||
accel_sample_1 = sensor.acceleration # Read one sample | ||
accel_sample_2 = sensor.acceleration # Read the next sample | ||
if euclidean_distance(accel_sample_1, accel_sample_2) >= TAP_THRESHOLD: | ||
# The difference between two consecutive samples exceeded the threshold () | ||
# (equivalent to a high-pass filter) | ||
mouse.move(wheel=1) | ||
printd("SINGLE tap detected") | ||
beep() | ||
time.sleep(TAP_DEBOUNCE) # Debounce delay |