# SPDX-FileCopyrightText: 2018 Dave Astels for Adafruit Industries # # SPDX-License-Identifier: MIT """ HalloWing Interactive Cat Toy Adafruit invests time and resources providing this open source code. Please support Adafruit and open source hardware by purchasing products from Adafruit! Written by Dave Astels for Adafruit Industries Copyright (c) 2018 Adafruit Industries Licensed under the MIT license. All text above must be included in any redistribution. """ # pylint: disable=global-statement import time from random import randrange import board import displayio import digitalio import touchio import audioio import audiocore import neopixel # import adafruit_lis3dh #------------------------------------------------------------------------------- # Setup hardware pir = digitalio.DigitalInOut(board.SENSE) pir.direction = digitalio.Direction.INPUT touch_1 = touchio.TouchIn(board.TOUCH1) touch_4 = touchio.TouchIn(board.TOUCH4) audio = audioio.AudioOut(board.SPEAKER) backlight = digitalio.DigitalInOut(board.TFT_BACKLIGHT) backlight.direction = digitalio.Direction.OUTPUT backlight.value = False splash = displayio.Group() board.DISPLAY.root_group = splash # setup neopixel ring pixels = neopixel.NeoPixel(board.EXTERNAL_NEOPIXEL, 24, brightness=.2) pixels.fill((0, 0, 0)) pixels.show() # setup accelerometer # i2c = board.I2C() # uses board.SCL and board.SDA # i2c = board.STEMMA_I2C() # For using the built-in STEMMA QT connector on a microcontroller # lis3dh = adafruit_lis3dh.LIS3DH_I2C(i2c) #------------------------------------------------------------------------------- # Play a wav file def play_wave(filename): wave_file = open(filename, "rb") wave = audiocore.WaveFile(wave_file) audio.play(wave) while audio.playing: pass wave_file.close() #------------------------------------------------------------------------------- # Display an image on the HalloWing TFT screen def show_image(filename): odb = displayio.OnDiskBitmap(filename) face = displayio.TileGrid(odb, pixel_shader=odb.pixel_shader) backlight.value = False splash.append(face) board.DISPLAY.refresh(target_frames_per_second=60) backlight.value = True #------------------------------------------------------------------------------- # Neopixel routines def random_colour(): return (randrange(255), randrange(255), randrange(255)) # Set 6 random pixels to random colours. # Keep track of which are lit so they can be turned off next time twinkle_indices = [0 , 0, 0, 0, 0, 0] def twinkle(): for p in range(6): pixels[twinkle_indices[p]] = (0, 0, 0) twinkle_indices[p] = randrange(len(pixels)) pixels[twinkle_indices[p]] = random_colour() pixels.show() # Fill the ring with a random colour def solid(): pixels.fill(random_colour()) pixels.show() #------------------------------------------------------------------------------- # The state machine ARMED_STATE = 0 ATTRACT_STATE = 1 PLAY_STATE = 2 TOUCHED_STATE = 3 MOVED_STATE = 4 ROCKED_STATE = 5 TIMEOUT_EVENT = 0 MOVE_EVENT = 1 TOUCH_EVENT = 2 ROCK_EVENT = 3 TIMEOUT = 60 timeout_time = 0 current_state = -1 update_function = None update_time = 0 update_interval = 0 def reset_timeout(): global timeout_time timeout_time = time.monotonic() + TIMEOUT def set_update(interval, func): global update_interval, update_time, update_function update_function = func update_interval = interval update_time = time.monotonic() + interval def enter_state(state): global current_state, timeout_time, update_time # print("Entering state {0}".format(state)) current_state = state if state == ARMED_STATE: timeout_time = 0 update_time = 0 backlight.value = False pixels.fill((0,0,0)) pixels.show() elif state == ATTRACT_STATE: splash.pop() show_image(images[1]) # here kitty reset_timeout() set_update(0.1, twinkle) elif state == PLAY_STATE: splash.pop() show_image(images[2]) # good kitty set_update(2.0, solid) elif state == TOUCHED_STATE: reset_timeout() update_time = 0 pixels.fill((128, 0, 0)) pixels.show() play_wave(sounds[randrange(len(sounds))]) enter_state(PLAY_STATE) elif state == MOVED_STATE: enter_state(PLAY_STATE) elif state == ROCKED_STATE: reset_timeout() enter_state(PLAY_STATE) def handle_event(event): # print("Handling event {0}".format(event)) if event == TIMEOUT_EVENT: enter_state(ARMED_STATE) elif event == MOVE_EVENT: if current_state == ARMED_STATE: enter_state(ATTRACT_STATE) elif current_state == PLAY_STATE: enter_state(MOVED_STATE) elif event == TOUCH_EVENT: if current_state in [ARMED_STATE, ATTRACT_STATE, PLAY_STATE]: enter_state(TOUCHED_STATE) elif event == ROCK_EVENT: if current_state in [ARMED_STATE, ATTRACT_STATE, PLAY_STATE]: enter_state(ROCKED_STATE) #------------------------------------------------------------------------------- # Check for event triggers was_moving = False def started_moving(): global was_moving started = False moving_now = pir.value if moving_now: started = not was_moving was_moving = moving_now return started was_touching = False def started_touching(): global was_touching started = False touching_now = touch_1.value or touch_4.value if touching_now: started = not was_touching was_touching = touching_now return started def started_rocking(): return False #------------------------------------------------------------------------------- # Image and sound filenames images = ["please_standby.bmp", "here_kitty.bmp", "good_kitty.bmp"] sounds = ["Cat_Meow_2.wav", "Cat_Meowing.wav", "kitten3.wav", "kitten4.wav"] #------------------------------------------------------------------------------- # Get started and loop, looking for and handling events show_image(images[0]) # waiting display time.sleep(3) arm_time = 0 armed = True enter_state(ARMED_STATE) while True: now = time.monotonic() if update_time > 0 and now > update_time: update_time += update_interval update_function() if timeout_time > 0 and now > timeout_time: handle_event(TIMEOUT_EVENT) elif started_moving(): handle_event(MOVE_EVENT) elif started_touching(): handle_event(TOUCH_EVENT) elif started_rocking(): handle_event(ROCK_EVENT)