├── requirements.txt ├── start.sh ├── .gitignore ├── python-sudo.sh ├── pwmaudio.sh ├── pwmaudio.service ├── rockinggame.service ├── lib ├── listener.py ├── led.py ├── audio.py ├── button.py ├── ledStrip.py ├── audioMixer.py ├── LSM6DS3.py ├── LSM6DS3_ALT.py ├── LSM6DS3_CONST.py └── ledAnimations.py ├── start.py ├── rockinggame ├── LICENSE ├── controller ├── lsmController.py ├── imuController.py └── ledController.py ├── gpio_alt.c ├── README.md ├── rockingMotorcycle.py └── test.py /requirements.txt: -------------------------------------------------------------------------------- 1 | rpi_ws281x -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /venv/* 3 | /audio/* -------------------------------------------------------------------------------- /python-sudo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sudo /usr/bin/python3 "$@" -------------------------------------------------------------------------------- /pwmaudio.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | /usr/local/bin/gpio_alt -p 13 -f 0 4 | /usr/local/bin/gpio_alt -p 18 -f 5 5 | -------------------------------------------------------------------------------- /pwmaudio.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=PWM Audio Service 3 | 4 | [Service] 5 | ExecStart=/home/pi/rockingMotorcycle/pwmaudio.sh 6 | StandardOutput=null 7 | 8 | [Install] 9 | WantedBy=multi-user.target 10 | Alias=pwmaudio.service 11 | -------------------------------------------------------------------------------- /rockinggame.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Rocking Motorcycle game 3 | DefaultDependencies=no 4 | 5 | [Service] 6 | Type=idle 7 | ExecStart=/usr/bin/python3 /home/pi/rockingMotorcycle/start.py 8 | 9 | [Install] 10 | WantedBy=sysinit.target -------------------------------------------------------------------------------- /lib/listener.py: -------------------------------------------------------------------------------- 1 | class Listener: 2 | def __init__(self): 3 | self.listener = None 4 | 5 | def setListener(self, listener): 6 | self.listener = listener 7 | 8 | def removeListener(self): 9 | self.listener = None 10 | -------------------------------------------------------------------------------- /start.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | from rockingMotorcycle import RockingMotorcycleGame 3 | 4 | if __name__ == "__main__": 5 | print("Start game. Rock on!!!") 6 | 7 | game = RockingMotorcycleGame(startVolume=90, changeDelta=1000, limit=0.2, imuAddress=0x1d) 8 | 9 | try: 10 | game.run() 11 | except KeyboardInterrupt: 12 | game.stop() 13 | print("Stopped game") 14 | -------------------------------------------------------------------------------- /lib/led.py: -------------------------------------------------------------------------------- 1 | import RPi.GPIO as GPIO 2 | 3 | from lib.listener import Listener 4 | 5 | 6 | class LED(Listener): 7 | 8 | def __init__(self, pin): 9 | Listener.__init__(self) 10 | 11 | self.pin = pin 12 | 13 | self.state = GPIO.LOW 14 | 15 | GPIO.setup(pin, GPIO.OUT) 16 | 17 | def on(self): 18 | self.state = GPIO.HIGH 19 | self.update() 20 | 21 | def off(self): 22 | self.state = GPIO.LOW 23 | self.update() 24 | 25 | def update(self): 26 | self.notifyState() 27 | GPIO.output(self.pin, self.state) 28 | 29 | def isOn(self): 30 | return self.state == GPIO.HIGH 31 | 32 | def notifyState(self): 33 | if self.listener is None: 34 | return 35 | 36 | self.listener(self.state) 37 | -------------------------------------------------------------------------------- /rockinggame: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # /etc/init.d/rockinggame 3 | 4 | ### BEGIN INIT INFO 5 | # Provides: rockgame 6 | # Required-Start: $remote_fs $syslog 7 | # Required-Stop: $remote_fs $syslog 8 | # Default-Start: 2 3 4 5 9 | # Default-Stop: 0 1 6 10 | # Short-Description: Simple script to start rocking motorcycle at boot 11 | # Description: RockingMotorcylce. 12 | ### END INIT INFO 13 | 14 | # If you want a command to always run, put it here 15 | 16 | # Carry out specific functions when asked to by the system 17 | case "$1" in 18 | start) 19 | echo "Starting rocking motorcycle game" 20 | # run application you want to start 21 | sudo python3 /home/pi/rockingMotorcycle/start.py & 22 | ;; 23 | stop) 24 | echo "Stopping rocking motorcycle game" 25 | # kill application you want to stop 26 | killall start.py 27 | ;; 28 | *) 29 | echo "Usage: /etc/init.d/rockgame {start|stop}" 30 | exit 1 31 | ;; 32 | esac 33 | 34 | exit 0 35 | 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 newtask 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/audio.py: -------------------------------------------------------------------------------- 1 | import alsaaudio 2 | 3 | 4 | class Audio: 5 | def __init__(self, startVolume=50): 6 | self.mixer = alsaaudio.Mixer('PCM') 7 | self.setVolume(startVolume) 8 | self.lastVolume = self.getVolume() 9 | self.isMute = False 10 | 11 | def setVolume(self, percent): 12 | self.mixer.setvolume(percent) 13 | 14 | def getVolume(self): 15 | return self.mixer.getvolume()[0] 16 | 17 | def unmute(self): 18 | self.setVolume(self.lastVolume) 19 | self.isMute = False 20 | return self.lastVolume 21 | 22 | def mute(self): 23 | self.lastVolume = self.getVolume() 24 | self.setVolume(0) 25 | self.isMute = True 26 | 27 | def volumeUp(self): 28 | if self.isMute: 29 | self.unmute() 30 | 31 | vol = self.mixer.getvolume()[0] 32 | vol += 10 33 | 34 | if vol >= 100: 35 | vol = 100 36 | 37 | self.setVolume(vol) 38 | 39 | return vol 40 | 41 | def volumeDown(self): 42 | if self.isMute: 43 | self.unmute() 44 | 45 | vol = self.mixer.getvolume()[0] 46 | vol -= 10 47 | 48 | if vol <= 0: 49 | vol = 0 50 | 51 | self.setVolume(vol) 52 | -------------------------------------------------------------------------------- /controller/lsmController.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import time 3 | 4 | from lsm303d import LSM303D 5 | 6 | from lib.listener import Listener 7 | 8 | 9 | class LSMController(threading.Thread, Listener): 10 | 11 | def __init__(self, address=0x1d, intervalMs=100, limit=100): 12 | Listener.__init__(self) 13 | threading.Thread.__init__(self) 14 | 15 | self.isRunning = True 16 | self.lsm = LSM303D(address) 17 | self.interval = intervalMs / 1000 18 | self.limit = limit 19 | 20 | self.lastAccl = self.lsm.accelerometer() 21 | 22 | def hasChanged(self): 23 | a = self.lsm.accelerometer() 24 | 25 | d = [] 26 | 27 | for i in range(0, 3): 28 | dv = a[i] - self.lastAccl[i] 29 | 30 | if dv > self.limit or dv < -self.limit: 31 | # print("dy", dy) 32 | self.lastAccl = a 33 | return True 34 | 35 | d.append(dv) 36 | 37 | 38 | # print(("{:+06.2f} : {:+06.2f} : {:+06.2f} " ).format(*d)) 39 | # print("d", d) 40 | return False 41 | 42 | def stop(self): 43 | self.isRunning = False 44 | 45 | def run(self): 46 | while self.isRunning: 47 | if self.listener is not None and self.hasChanged(): 48 | self.listener() 49 | 50 | time.sleep(self.interval) 51 | -------------------------------------------------------------------------------- /controller/imuController.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import time 3 | 4 | from lib.LSM6DS3 import LSM6DS3 5 | from lib.listener import Listener 6 | 7 | 8 | class IMUController(threading.Thread, Listener): 9 | 10 | def __init__(self, address=0x6a, intervalMs=100, limit=100): 11 | Listener.__init__(self) 12 | threading.Thread.__init__(self) 13 | 14 | self.isRunning = True 15 | self.lsm = LSM6DS3(address) 16 | self.interval = intervalMs / 1000 17 | self.limit = limit 18 | 19 | self.lastGyroX = self.lsm.readRawGyroX() 20 | self.lastGyroY = self.lsm.readRawGyroY() 21 | self.lastGyroZ = self.lsm.readRawGyroZ() 22 | 23 | def hasGyroXChanged(self): 24 | 25 | cx = self.lsm.readRawGyroX() 26 | dx = cx - self.lastGyroX 27 | print("dx", dx) 28 | if dx > self.limit or dx < -self.limit: 29 | #print("dx", dx) 30 | self.lastGyroX = cx 31 | return True 32 | 33 | return False 34 | 35 | def hasGyroYChanged(self): 36 | 37 | cy = self.lsm.readRawGyroY() 38 | dy = cy - self.lastGyroY 39 | print("dy", dy) 40 | if dy > self.limit or dy < -self.limit: 41 | #print("dy", dy) 42 | self.lastGyroX = cy 43 | return True 44 | 45 | return False 46 | 47 | def hasGyroZChanged(self): 48 | 49 | cz = self.lsm.readRawGyroZ() 50 | dz = cz - self.lastGyroZ 51 | print("dz", dz) 52 | if dz > self.limit or dz < -self.limit: 53 | #print("dz", dz) 54 | self.lastGyroZ = cz 55 | return True 56 | 57 | return False 58 | 59 | def hasChanged(self): 60 | return self.hasGyroXChanged() or self.hasGyroYChanged() or self.hasGyroZChanged() 61 | 62 | def stop(self): 63 | self.isRunning = False 64 | 65 | def run(self): 66 | while self.isRunning: 67 | if self.listener is not None and self.hasChanged(): 68 | self.listener() 69 | 70 | time.sleep(self.interval) 71 | -------------------------------------------------------------------------------- /lib/button.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import RPi.GPIO as GPIO 4 | 5 | from lib.listener import Listener 6 | 7 | 8 | class Button(Listener): 9 | NORMAL_PRESS = 0 10 | LONG_PRESS = 1 11 | 12 | def __init__(self, pin, longPressTime=1000): 13 | 14 | Listener.__init__(self) 15 | 16 | self.RELEASED = 0 17 | self.PRESSED = 1 18 | self.IDLE = 2 19 | 20 | self.state = self.RELEASED 21 | 22 | self.longPressTime = longPressTime 23 | self.pressedTime = 0 24 | 25 | self.pin = pin 26 | 27 | GPIO.setup(pin, GPIO.IN) 28 | 29 | def loop(self): 30 | 31 | currentState = GPIO.input(self.pin) 32 | 33 | # check if button was pressed before 34 | if self.state is self.PRESSED: 35 | 36 | # check if the user is doing a long press 37 | if currentState is self.PRESSED: 38 | if self.get_millis() - self.pressedTime > self.longPressTime: 39 | self.state = self.IDLE 40 | self.pressedTime = 0 41 | self.notifyLongPressed() 42 | return 43 | 44 | else: # RELEASED 45 | self.pressedTime = 0 46 | self.notifyNormalPressed() 47 | 48 | elif self.state is self.RELEASED: 49 | if currentState is self.PRESSED: 50 | self.pressedTime = self.get_millis() 51 | else: 52 | self.pressedTime = 0 53 | elif self.state is self.IDLE and currentState is self.PRESSED: 54 | # keep IDLE state as long the used presses the button 55 | return 56 | 57 | self.state = currentState 58 | 59 | def notifyLongPressed(self): 60 | if self.listener is None: 61 | return 62 | 63 | self.listener(Button.LONG_PRESS) 64 | 65 | def notifyNormalPressed(self): 66 | if self.listener is None: 67 | return 68 | 69 | self.listener(Button.NORMAL_PRESS) 70 | 71 | def get_millis(self): 72 | return int(round(time.time() * 1000)) 73 | -------------------------------------------------------------------------------- /lib/ledStrip.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import time 3 | 4 | from neopixel import * 5 | from lib.ledAnimations import LEDAnimation 6 | 7 | 8 | class LEDStrip(threading.Thread): 9 | LED_FREQ_HZ = 800000 # LED signal frequency in hertz (usually 800khz) 10 | LED_DMA = 10 # DMA channel to use for generating signal (try 10) 11 | LED_BRIGHTNESS = 255 # Set to 0 for darkest and 255 for brightest 12 | LED_INVERT = False # True to invert the signal (when using NPN transistor level shift) 13 | LED_CHANNEL = 0 # set to '1' for GPIOs 13, 19, 41, 45 or 53 14 | 15 | def __init__(self, pin=21, ledCount=30): 16 | super(LEDStrip, self).__init__() 17 | 18 | self.LED_PIN = pin # 10 spi 12 pwm GPIO pin connected to the pixels (18 uses PWM!). 19 | self.LED_COUNT = ledCount # Number of LED pixels. 20 | self.isRunning = False 21 | self.animation = None 22 | 23 | # Create NeoPixel object with appropriate configuration. 24 | self.strip = Adafruit_NeoPixel(self.LED_COUNT, self.LED_PIN, self.LED_FREQ_HZ, self.LED_DMA, self.LED_INVERT, 25 | self.LED_BRIGHTNESS, self.LED_CHANNEL) 26 | 27 | # Initialize the library (must be called once before other functions). 28 | self.strip.begin() 29 | 30 | def start(self): 31 | print("Start") 32 | self.isRunning = True 33 | super(LEDStrip, self).start() 34 | 35 | def getAnimation(self): 36 | return self.animation 37 | 38 | def setAnimation(self, animation: LEDAnimation): 39 | print("Set animation", animation) 40 | 41 | if self.animation is not None: 42 | self.animation.stop() 43 | 44 | print("Start new animation") 45 | self.animation = animation 46 | self.animation.setStrip(self.strip) 47 | self.animation.start() 48 | 49 | def run(self): 50 | while self.isRunning: 51 | if self.animation is not None: 52 | self.animation.loop() 53 | else: 54 | time.sleep(0.1) 55 | 56 | def stop(self): 57 | if self.animation is not None: 58 | self.animation.stop() 59 | 60 | self.isRunning = False 61 | -------------------------------------------------------------------------------- /controller/ledController.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import time 3 | 4 | 5 | class LEDTask: 6 | repeat = 0 7 | onDuration = 0 8 | offDuration = 0 9 | 10 | def __init__(self, repeat, onDuration, offDuration): 11 | self.repeat = repeat 12 | self.onDuration = onDuration 13 | self.offDuration = offDuration 14 | 15 | 16 | class LEDController(threading.Thread): 17 | 18 | def __init__(self, led): 19 | threading.Thread.__init__(self) 20 | 21 | self.cancelTask = False 22 | self.taskIsRunning = False 23 | self.led = led 24 | self.minWaitTime = 0.1 25 | self.currentTask = None 26 | self.isRunning = False 27 | 28 | def start(self): 29 | self.isRunning = True 30 | super(LEDController, self).start() 31 | 32 | def blink(self, repeat, onMillis, offMillis): 33 | self.cancelTask = True 34 | 35 | # wait until current task is finished 36 | while self.taskIsRunning: 37 | time.sleep(self.minWaitTime) 38 | 39 | self.cancelTask = False 40 | self.currentTask = LEDTask(repeat, onMillis / 1000, offMillis / 1000) 41 | 42 | def sleep(self, sec): 43 | 44 | millis = int(sec * 10) 45 | for i in range(millis): 46 | if self.cancelTask: 47 | return 48 | time.sleep(self.minWaitTime) 49 | 50 | def stop(self): 51 | self.cancelTask = True 52 | self.isRunning = False 53 | 54 | def run(self): 55 | while self.isRunning: 56 | if self.currentTask is not None: 57 | self.taskIsRunning = True 58 | 59 | for i in range(self.currentTask.repeat): 60 | self.led.on() 61 | 62 | self.sleep(self.currentTask.onDuration) 63 | 64 | self.led.off() 65 | 66 | if self.cancelTask: 67 | break 68 | 69 | self.sleep(self.currentTask.offDuration) 70 | 71 | if self.cancelTask: 72 | break 73 | 74 | self.taskIsRunning = False 75 | 76 | self.currentTask = None 77 | 78 | # prevent restarting directly 79 | time.sleep(self.minWaitTime) 80 | -------------------------------------------------------------------------------- /lib/audioMixer.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import time 3 | 4 | import pygame 5 | 6 | 7 | class SoundQueueItem: 8 | def __init__(self): 9 | self.id = None 10 | self.loop = False 11 | 12 | 13 | class AudioMixer(threading.Thread): 14 | def __init__(self): 15 | super().__init__() 16 | pygame.mixer.init() 17 | 18 | self.sounds = {} 19 | self.channels = [] 20 | 21 | self.numChannels = 2 22 | self.curChannelId = 0 23 | 24 | self.listener = None 25 | 26 | self.queuedSounds = [] 27 | 28 | for i in range(0, self.numChannels): 29 | channel = pygame.mixer.Channel(i) 30 | self.channels.append(channel) 31 | 32 | self.running = True 33 | self.start() 34 | 35 | def stop(self): 36 | self.running = False 37 | 38 | for channel in self.channels: 39 | channel.stop() 40 | 41 | def setListener(self, listener): 42 | self.listener = listener 43 | 44 | def run(self): 45 | while self.running: 46 | if len(self.queuedSounds) > 0: 47 | 48 | isBusy = False 49 | 50 | for channel in self.channels: 51 | if channel.get_busy() == 1: 52 | isBusy = True 53 | 54 | # print("Is busy: {}".format(isBusy)) 55 | if isBusy is False: 56 | item = self.queuedSounds.pop(0) 57 | self.playSound(item.id, item.loop) 58 | 59 | time.sleep(0.1) 60 | 61 | def addSound(self, id, path): 62 | self.sounds[id] = pygame.mixer.Sound(path) 63 | 64 | def getNextChannel(self): 65 | nextChannelId = self.getNextChannelId() 66 | return self.channels[nextChannelId] 67 | 68 | def playSound(self, id, loop=False, fadeTime=1000): 69 | print("Play sound: {}, loop: {}, fadeTime: {}".format(id, loop, fadeTime)) 70 | 71 | nextChannelId = self.getNextChannelId() 72 | 73 | curChannel = self.channels[self.curChannelId] 74 | nextChannel = self.channels[nextChannelId] 75 | 76 | if curChannel.get_busy(): 77 | curChannel.fadeout(fadeTime) 78 | 79 | nextChannel.play(self.sounds[id], -1 if loop else 0) 80 | 81 | if self.listener is not None: 82 | self.listener(id) 83 | 84 | self.curChannelId = nextChannelId 85 | 86 | def queue(self, id, loop): 87 | item = SoundQueueItem() 88 | item.id = id 89 | item.loop = loop 90 | 91 | self.queuedSounds.append(item) 92 | 93 | def clearQueue(self): 94 | while len(self.queuedSounds) > 0: 95 | self.queuedSounds.pop() 96 | 97 | def stopSound(self): 98 | curChannel = self.channels[self.curChannelId] 99 | curChannel.stop() 100 | nextChannel = self.channels[self.getNextChannelId()] 101 | nextChannel.stop() 102 | 103 | def getNextChannelId(self): 104 | nextChannelId = self.curChannelId + 1 105 | 106 | if nextChannelId >= self.numChannels: 107 | nextChannelId = 0 108 | 109 | return nextChannelId 110 | -------------------------------------------------------------------------------- /gpio_alt.c: -------------------------------------------------------------------------------- 1 | /* 2 | Utility to switch Raspberry-Pi GPIO pin functions 3 | Tim Giles 01/04/2013 4 | 5 | Usage: 6 | $ gpio_alt -p PIN_NUMBER -f ALT_NUMBER 7 | 8 | Based on RPi code from Dom and Gert, 15-Feb-2013, 9 | and Gnu getopt() example 10 | */ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #define BCM2708_PERI_BASE 0x20000000 20 | #define GPIO_BASE (BCM2708_PERI_BASE + 0x200000) /* GPIO controller */ 21 | #define PAGE_SIZE (4*1024) 22 | #define BLOCK_SIZE (4*1024) 23 | 24 | int mem_fd; 25 | void *gpio_map; 26 | volatile unsigned *gpio; 27 | void setup_io(); 28 | 29 | // GPIO setup macros. Always use INP_GPIO(x) before using OUT_GPIO(x) or SET_GPIO_ALT(x,y) 30 | #define INP_GPIO(g) *(gpio+((g)/10)) &= ~(7<<(((g)%10)*3)) 31 | #define OUT_GPIO(g) *(gpio+((g)/10)) |= (1<<(((g)%10)*3)) 32 | #define SET_GPIO_ALT(g,a) *(gpio+(((g)/10))) |= (((a)<=3?(a)+4:(a)==4?3:2)<<(((g)%10)*3)) 33 | 34 | #define GPIO_SET *(gpio+7) // sets bits which are 1 ignores bits which are 0 35 | #define GPIO_CLR *(gpio+10) // clears bits which are 1 ignores bits which are 0 36 | 37 | 38 | 39 | int main (int argc, char **argv) { 40 | int opt, flag, n_pin, n_alt; 41 | flag=0; 42 | 43 | while ((opt = getopt (argc, argv, "hp:f:")) != -1) { 44 | switch (opt) { 45 | case 'h': 46 | break; 47 | case 'p': 48 | n_pin = atoi(optarg); flag |= 0b0001; break; 49 | case 'f': 50 | n_alt = atoi(optarg); flag |= 0b0010; break; 51 | case '?': 52 | // getopt() prints error messages, so don't need to repeat them here 53 | return 1; 54 | default: 55 | abort (); 56 | } 57 | } 58 | 59 | if (flag != 0b0011) { 60 | fprintf (stderr, "Usage:\n$ gpio_alt -p PIN_NUM -f FUNC_NUM\n"); 61 | return 1; 62 | } 63 | 64 | setup_io(); // Set up gpi pointer for direct register access 65 | INP_GPIO(n_pin); // Always use INP_GPIO(x) before using SET_GPIO_ALT(x,y) 66 | SET_GPIO_ALT(n_pin, n_alt); 67 | 68 | printf("Set pin %i to alternative-function %i\n", n_pin, n_alt); 69 | 70 | return 0; 71 | } 72 | 73 | 74 | 75 | void setup_io() { 76 | /* open /dev/mem */ 77 | if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) { 78 | printf("can't open /dev/mem \n"); 79 | exit(-1); 80 | } 81 | 82 | /* mmap GPIO */ 83 | gpio_map = mmap( 84 | NULL, //Any adddress in our space will do 85 | BLOCK_SIZE, //Map length 86 | PROT_READ|PROT_WRITE,// Enable reading & writting to mapped memory 87 | MAP_SHARED, //Shared with other processes 88 | mem_fd, //File to map 89 | GPIO_BASE //Offset to GPIO peripheral 90 | ); 91 | 92 | close(mem_fd); //No need to keep mem_fd open after mmap 93 | 94 | if (gpio_map == MAP_FAILED) { 95 | printf("mmap error %d\n", (int)gpio_map);//errno also set! 96 | exit(-1); 97 | } 98 | 99 | // Always use volatile pointer! 100 | gpio = (volatile unsigned *)gpio_map; 101 | } -------------------------------------------------------------------------------- /lib/LSM6DS3.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | import Adafruit_GPIO.I2C as I2C 4 | 5 | from .LSM6DS3_CONST import * 6 | 7 | address = 0x6b 8 | 9 | 10 | class LSM6DS3: 11 | i2c = None 12 | tempvar = 0 13 | 14 | def __init__(self, address=0x6b, debug=0, pause=0.8): 15 | self.i2c = I2C.get_i2c_device(address) 16 | self.address = address 17 | 18 | self.setupAccl() 19 | self.setupGyro() 20 | 21 | # setup gyro 22 | 23 | self.accel_center_x = self.i2c.readS16(LSM6DS3_XG_OUT_X_L_XL) 24 | self.accel_center_y = self.i2c.readS16(LSM6DS3_XG_OUT_Y_L_XL) 25 | self.accel_center_z = self.i2c.readS16(LSM6DS3_XG_OUT_Z_L_XL) 26 | 27 | def setupAccl(self): 28 | dataToWrite = 0 # Start Fresh! 29 | dataToWrite |= 0x03 # set at 50hz, bandwidth 30 | dataToWrite |= 0x00 # 2g accel range 31 | dataToWrite |= 0x10 # 13hz ODR 32 | self.i2c.write8(0X10, dataToWrite) # writeRegister(LSM6DS3_ACC_GYRO_CTRL2_G, dataToWrite) 33 | 34 | def setupGyro(self): 35 | # Gyro setup 36 | 37 | gy_odr = '13HZ' 38 | gy_full_scale_dps = '2000' 39 | 40 | tmp = self.i2c.readU8(LSM6DS3_XG_CTRL2_G) 41 | tmp &= ~LSM6DS3_G_ODR['MASK'] 42 | tmp |= LSM6DS3_G_ODR[gy_odr] 43 | tmp &= ~LSM6DS3_G_FS['MASK'] 44 | tmp |= LSM6DS3_G_FS[gy_full_scale_dps] 45 | self.i2c.write8(LSM6DS3_XG_CTRL2_G, tmp) 46 | 47 | # Axis selection 48 | gy_axis_en = 'XYZ' 49 | 50 | tmp = self.i2c.readU8(LSM6DS3_XG_CTRL10_C) 51 | tmp &= ~LSM6DS3_G_AXIS_EN['MASK'] 52 | tmp |= LSM6DS3_G_AXIS_EN['X'] if 'X' in gy_axis_en else 0 53 | tmp |= LSM6DS3_G_AXIS_EN['Y'] if 'Y' in gy_axis_en else 0 54 | tmp |= LSM6DS3_G_AXIS_EN['Z'] if 'Z' in gy_axis_en else 0 55 | self.i2c.write8(LSM6DS3_XG_CTRL10_C, tmp) 56 | 57 | def readRawAccelX(self): 58 | output = self.i2c.readS16(LSM6DS3_XG_OUT_X_L_XL) 59 | return output 60 | 61 | def readRawAccelY(self): 62 | output = self.i2c.readS16(LSM6DS3_XG_OUT_Y_L_XL) 63 | return output 64 | 65 | def readRawAccelZ(self): 66 | output = self.i2c.readS16(LSM6DS3_XG_OUT_Z_L_XL) 67 | return output 68 | 69 | def calcAnglesXY(self): 70 | # Using x y and z from accelerometer, calculate x and y angles 71 | x_val = 0 72 | y_val = 0 73 | z_val = 0 74 | result = 0 75 | 76 | x2 = 0 77 | y2 = 0 78 | z2 = 0 79 | x_val = self.readRawAccelX() - self.accel_center_x 80 | y_val = self.readRawAccelY() - self.accel_center_y 81 | z_val = self.readRawAccelZ() - self.accel_center_z 82 | 83 | x2 = x_val * x_val 84 | y2 = y_val * y_val 85 | z2 = z_val * z_val 86 | 87 | result = math.sqrt(y2 + z2) 88 | if (result != 0): 89 | result = x_val / result 90 | accel_angle_x = math.atan(result) 91 | return accel_angle_x 92 | 93 | def readRawGyroX(self): 94 | output = self.i2c.readS16(0X22) 95 | return output 96 | 97 | def readRawGyroY(self): 98 | output = self.i2c.readS16(0X22) 99 | return output 100 | 101 | def readRawGyroZ(self): 102 | output = self.i2c.readS16(0X26) 103 | return output 104 | 105 | def readFloatGyroX(self): 106 | output = self.calcGyro(self.readRawGyroX()) 107 | return output 108 | 109 | def calcGyroXAngle(self): 110 | temp = 0 111 | temp += self.readFloatGyroX() 112 | if (temp > 3 or temp < 0): 113 | self.tempvar += temp 114 | return self.tempvar 115 | 116 | def calcGyro(self, rawInput): 117 | gyroRangeDivisor = 245 / 125 # 500 is the gyro range (DPS) 118 | output = rawInput * 4.375 * (gyroRangeDivisor) / 1000 119 | return output 120 | -------------------------------------------------------------------------------- /lib/LSM6DS3_ALT.py: -------------------------------------------------------------------------------- 1 | import Adafruit_GPIO.I2C as I2C 2 | 3 | from .LSM6DS3_CONST import * 4 | 5 | 6 | class ACCGYRO(): 7 | WHO_IAM_ANSWER = 0x69 8 | WHO_IAM_REG = 0xF 9 | 10 | def __init__(self, dev_selector=0x6b): 11 | self.i2c = I2C.get_i2c_device(dev_selector) 12 | 13 | # enable autoincrement 14 | tmp = self.i2c.readU8(LSM6DS3_XG_CTRL3_C) 15 | tmp |= LSM6DS3_XG_IF_INC 16 | self.i2c.write8(LSM6DS3_XG_CTRL3_C, tmp) 17 | # Disable FIFO 18 | self.i2c.write8(LSM6DS3_XG_FIFO_CTRL5, 19 | LSM6DS3_XG_FIFO_MODE['BYPASS'] | LSM6DS3_XG_FIFO_ODR['NA']) 20 | 21 | def set_multi_byte(self, addr): 22 | ''' Multi byte read is configured in register CTRL3''' 23 | return addr 24 | 25 | def temperature(self): 26 | t = self.i2c.readS16(LSM6DS3_TEMP_OUT_L) 27 | return 25.0 + t / 16.0 28 | 29 | 30 | class ACCEL(ACCGYRO): 31 | 32 | def ctrl(self, g_odr='13HZ', g_full_scale_g='2G', g_axis_en="XYZ"): 33 | g_odr = g_odr.upper() 34 | g_full_scale_g = g_full_scale_g.upper() 35 | g_axis_en = g_axis_en.upper() 36 | 37 | tmp = self.i2c.readU8(LSM6DS3_XG_CTRL1_XL) 38 | tmp &= ~LSM6DS3_XL_ODR['MASK'] 39 | tmp |= LSM6DS3_XL_ODR[g_odr] 40 | tmp &= ~LSM6DS3_XL_FS['MASK'] 41 | tmp |= LSM6DS3_XL_FS[g_full_scale_g] 42 | self.i2c.write8(LSM6DS3_XG_CTRL1_XL, tmp) 43 | 44 | tmp = self.i2c.readU8(LSM6DS3_XG_CTRL9_XL) 45 | tmp &= ~LSM6DS3_XL_AXIS_EN['MASK'] 46 | tmp |= LSM6DS3_XL_AXIS_EN['X'] if 'X' in g_axis_en else 0 47 | tmp |= LSM6DS3_XL_AXIS_EN['Y'] if 'Y' in g_axis_en else 0 48 | tmp |= LSM6DS3_XL_AXIS_EN['Z'] if 'Z' in g_axis_en else 0 49 | self.i2c.write8(LSM6DS3_XG_CTRL9_XL, tmp) 50 | 51 | def xyz(self): 52 | ''' 53 | Returns the acceleration in 10^(-3) g. 54 | 1 g = 9.81 m/s^2 55 | ''' 56 | # Get raw data 57 | x = self.i2c.readS16(LSM6DS3_XG_OUT_X_L_XL) 58 | y = self.i2c.readS16(LSM6DS3_XG_OUT_Y_L_XL) 59 | z = self.i2c.readS16(LSM6DS3_XG_OUT_Z_L_XL) 60 | # Get sensitivity 61 | sens = (self.i2c.readU8(LSM6DS3_XG_CTRL1_XL) & LSM6DS3_XL_FS['MASK']) 62 | sens = (1 << (sens >> LSM6DS3_XL_FS['SHIFT'])) * 0.061 63 | return (x * sens, y * sens, z * sens) 64 | 65 | 66 | class GYRO(ACCGYRO): 67 | 68 | def ctrl(self, gy_odr='13HZ', gy_full_scale_dps='2000', 69 | gy_axis_en='XYZ'): 70 | gy_odr = gy_odr.upper() 71 | gy_full_scale_dps = gy_full_scale_dps.upper() 72 | gy_axis_en = gy_axis_en.upper() 73 | # Gyro setup 74 | tmp = self.i2c.readU8(LSM6DS3_XG_CTRL2_G) 75 | tmp &= ~LSM6DS3_G_ODR['MASK'] 76 | tmp |= LSM6DS3_G_ODR[gy_odr] 77 | tmp &= ~LSM6DS3_G_FS['MASK'] 78 | tmp |= LSM6DS3_G_FS[gy_full_scale_dps] 79 | self.i2c.write8(LSM6DS3_XG_CTRL2_G, tmp) 80 | # Axis selection 81 | tmp = self.i2c.readU8(LSM6DS3_XG_CTRL10_C) 82 | tmp &= ~LSM6DS3_G_AXIS_EN['MASK'] 83 | tmp |= LSM6DS3_G_AXIS_EN['X'] if 'X' in gy_axis_en else 0 84 | tmp |= LSM6DS3_G_AXIS_EN['Y'] if 'Y' in gy_axis_en else 0 85 | tmp |= LSM6DS3_G_AXIS_EN['Z'] if 'Z' in gy_axis_en else 0 86 | self.i2c.write8(LSM6DS3_XG_CTRL10_C, tmp) 87 | 88 | def xyz(self): 89 | # Get raw data 90 | x = self.i2c.readS16(LSM6DS3_XG_OUT_X_L_G) 91 | y = self.i2c.readS16(LSM6DS3_XG_OUT_Y_L_G) 92 | z = self.i2c.readS16(LSM6DS3_XG_OUT_Z_L_G) 93 | # Get sensitivity 94 | sens = (self.i2c.readU8(LSM6DS3_XG_CTRL2_G) & LSM6DS3_G_FS['MASK']) 95 | if sens == LSM6DS3_G_FS['125']: 96 | sens = 4.375 97 | elif sens == LSM6DS3_G_FS['245']: 98 | sens = 8.75 99 | elif sens == LSM6DS3_G_FS['500']: 100 | sens = 17.50 101 | elif sens == LSM6DS3_G_FS['1000']: 102 | sens = 35.0 103 | elif sens == LSM6DS3_G_FS['2000']: 104 | sens = 70.0 105 | else: 106 | raise Exception("Unknown gyro sensitivity 0x%02x" % (sens)) 107 | return (x * sens, y * sens, z * sens) 108 | 109 | 110 | class LSM6DS3_alt(): 111 | 112 | def __init__(self, dev_selector): 113 | # Setup defaults on gyro and accel 114 | self.accel = ACCEL(dev_selector) 115 | self.accel.ctrl() 116 | self.gyro = GYRO(dev_selector) 117 | self.gyro.ctrl() 118 | 119 | def temperature(self): 120 | return self.accel.temperature() 121 | -------------------------------------------------------------------------------- /lib/LSM6DS3_CONST.py: -------------------------------------------------------------------------------- 1 | LSM6DS3_XG_CTRL3_C = (0x12) 2 | LSM6DS3_XG_CTRL4_C = (0x13) 3 | LSM6DS3_XG_CTRL5_C = (0x14) 4 | LSM6DS3_XG_CTRL10_C = (0x19) 5 | LSM6DS3_XG_CTRL2_G = (0x11) 6 | LSM6DS3_XG_CTRL6_G = (0x15) 7 | LSM6DS3_XG_CTRL7_G = (0x16) 8 | LSM6DS3_XG_CTRL1_XL = (0x10) 9 | LSM6DS3_XG_CTRL8_XL = (0x17) 10 | LSM6DS3_XG_CTRL9_XL = (0x18) 11 | 12 | LSM6DS3_TEMP_OUT_L = (0x20) 13 | 14 | LSM6DS3_XG_IF_INC = (0x04) 15 | LSM6DS3_XG_IF_INC_MASK = (0x04) 16 | 17 | LSM6DS3_XG_FIFO_CTRL5 = (0x0A) 18 | LSM6DS3_XG_FIFO_MODE = {} 19 | LSM6DS3_XG_FIFO_MODE['BYPASS'] = (0x00) # BYPASS Mode. FIFO turned off 20 | 21 | # FIFO Mode. Stop collecting data when FIFO is full 22 | LSM6DS3_XG_FIFO_MODE['FIFO'] = (0x01) 23 | # CONTINUOUS mode until trigger is deasserted, then FIFO mode 24 | LSM6DS3_XG_FIFO_MODE['CONTINUOUS_THEN_FIFO'] = (0x03) 25 | # BYPASS mode until trigger is deasserted, then CONTINUOUS mode 26 | LSM6DS3_XG_FIFO_MODE['BYPASS_THEN_CONTINUOUS'] = (0x04) 27 | # CONTINUOUS mode. If the FIFO is full the new sample overwrite the older one 28 | LSM6DS3_XG_FIFO_MODE['CONTINUOUS_OVERWRITE'] = (0x05) 29 | LSM6DS3_XG_FIFO_MODE['MASK'] = (0x07) 30 | 31 | LSM6DS3_XG_FIFO_ODR = {} 32 | LSM6DS3_XG_FIFO_ODR['NA'] = (0x00) # FIFO ODR NA 33 | LSM6DS3_XG_FIFO_ODR['10HZ'] = (0x08) # FIFO ODR 10Hz 34 | LSM6DS3_XG_FIFO_ODR['25HZ'] = (0x10) # FIFO ODR 25Hz 35 | LSM6DS3_XG_FIFO_ODR['50HZ'] = (0x18) # FIFO ODR 50Hz 36 | LSM6DS3_XG_FIFO_ODR['100HZ'] = (0x20) # FIFO ODR 100Hz 37 | LSM6DS3_XG_FIFO_ODR['200HZ'] = (0x28) # FIFO ODR 200Hz 38 | LSM6DS3_XG_FIFO_ODR['400HZ'] = (0x30) # FIFO ODR 400Hz 39 | LSM6DS3_XG_FIFO_ODR['800HZ'] = (0x38) # FIFO ODR 800Hz 40 | LSM6DS3_XG_FIFO_ODR['1600HZ'] = (0x40) # FIFO ODR 1600Hz 41 | LSM6DS3_XG_FIFO_ODR['3300HZ'] = (0x48) # FIFO ODR 3300Hz 42 | LSM6DS3_XG_FIFO_ODR['6600HZ'] = (0x50) # FIFO ODR 6600Hz 43 | LSM6DS3_XG_FIFO_ODR['MASK'] = (0x78) 44 | 45 | LSM6DS3_XG_OUT_X_L_G = (0x22) 46 | LSM6DS3_XG_OUT_Y_L_G = (0x24) 47 | LSM6DS3_XG_OUT_Z_L_G = (0x26) 48 | LSM6DS3_G_ODR = {} 49 | LSM6DS3_G_ODR['PD'] = (0x00) # Output Data Rate: Power-down 50 | LSM6DS3_G_ODR['13HZ'] = (0x10) # Output Data Rate: 13 Hz 51 | LSM6DS3_G_ODR['26HZ'] = (0x20) # Output Data Rate: 26 Hz 52 | LSM6DS3_G_ODR['52HZ'] = (0x30) # Output Data Rate: 52 Hz 53 | LSM6DS3_G_ODR['104HZ'] = (0x40) # Output Data Rate: 104 Hz 54 | LSM6DS3_G_ODR['208HZ'] = (0x50) # Output Data Rate: 208 Hz 55 | LSM6DS3_G_ODR['416HZ'] = (0x60) # Output Data Rate: 416 Hz 56 | LSM6DS3_G_ODR['833HZ'] = (0x70) # Output Data Rate: 833 Hz 57 | LSM6DS3_G_ODR['1K66HZ'] = (0x80) # Output Data Rate: 1.66 kHz 58 | LSM6DS3_G_ODR['MASK'] = (0xF0) 59 | 60 | LSM6DS3_G_FS = {} 61 | LSM6DS3_G_FS['125'] = (0x02) # Full scale: 125 dps*/ 62 | LSM6DS3_G_FS['245'] = (0x00) # Full scale: 245 dps*/ 63 | LSM6DS3_G_FS['500'] = (0x04) # Full scale: 500 dps */ 64 | LSM6DS3_G_FS['1000'] = (0x08) # Full scale: 1000 dps */ 65 | LSM6DS3_G_FS['2000'] = (0x0C) # Full scale: 2000 dps */ 66 | LSM6DS3_G_FS['MASK'] = (0x0E) 67 | LSM6DS3_G_FS['SHIFT'] = 1 68 | 69 | LSM6DS3_G_AXIS_EN = {} 70 | LSM6DS3_G_AXIS_EN['X'] = (0x08) 71 | LSM6DS3_G_AXIS_EN['Y'] = (0x10) 72 | LSM6DS3_G_AXIS_EN['Z'] = (0x20) 73 | LSM6DS3_G_AXIS_EN['MASK'] = (0x38) 74 | 75 | LSM6DS3_XG_OUT_X_L_XL = (0x28) 76 | LSM6DS3_XG_OUT_Y_L_XL = (0x2A) 77 | LSM6DS3_XG_OUT_Z_L_XL = (0x2C) 78 | LSM6DS3_XL_ODR = {} 79 | LSM6DS3_XL_ODR['PD'] = (0x00) # Output Data Rate: Power-down*/ 80 | LSM6DS3_XL_ODR['13HZ'] = (0x10) # Output Data Rate: 13 Hz*/ 81 | LSM6DS3_XL_ODR['26HZ'] = (0x20) # Output Data Rate: 26 Hz*/ 82 | LSM6DS3_XL_ODR['52HZ'] = (0x30) # Output Data Rate: 52 Hz */ 83 | LSM6DS3_XL_ODR['104HZ'] = (0x40) # Output Data Rate: 104 Hz */ 84 | LSM6DS3_XL_ODR['208HZ'] = (0x50) # Output Data Rate: 208 Hz */ 85 | LSM6DS3_XL_ODR['416HZ'] = (0x60) # Output Data Rate: 416 Hz */ 86 | LSM6DS3_XL_ODR['833HZ'] = (0x70) # Output Data Rate: 833 Hz */ 87 | LSM6DS3_XL_ODR['1K66HZ'] = (0x80) # Output Data Rate: 1.66 kHz */ 88 | LSM6DS3_XL_ODR['3K33HZ'] = (0x90) # Output Data Rate: 3.33 kHz */ 89 | LSM6DS3_XL_ODR['6K66HZ'] = (0xA0) # Output Data Rate: 6.66 kHz */ 90 | LSM6DS3_XL_ODR['MASK'] = (0xF0) 91 | 92 | LSM6DS3_XL_FS = {} 93 | LSM6DS3_XL_FS['2G'] = (0x00) # Full scale: +- 2g */ 94 | LSM6DS3_XL_FS['4G'] = (0x08) # Full scale: +- 4g */ 95 | LSM6DS3_XL_FS['8G'] = (0x0C) # Full scale: +- 8g */ 96 | LSM6DS3_XL_FS['16G'] = (0x04) # Full scale: +- 16g */ 97 | LSM6DS3_XL_FS['MASK'] = (0x0C) 98 | LSM6DS3_XL_FS['SHIFT'] = 3 99 | 100 | LSM6DS3_XL_AXIS_EN = {} 101 | LSM6DS3_XL_AXIS_EN['X'] = (0x08) 102 | LSM6DS3_XL_AXIS_EN['Y'] = (0x10) 103 | LSM6DS3_XL_AXIS_EN['Z'] = (0x20) 104 | LSM6DS3_XL_AXIS_EN['MASK'] = (0x38) 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rockingMotorcycle 2 | An accelerometer triggered sound and light generator for rocking motorcycles. Developed with python and a raspberry pi zero 3 | 4 | ## Todo 5 | 6 | - ~~Add button that changes the volume on a single press and restarts the app on a long press~~ 7 | - ~~Add a led strip that has at least two modes~~: 8 | - ~~Idle mode: Fading in and out~~ 9 | - ~~LED chase where the speed can be changed~~ 10 | - ~~Add a status led which display the status of the app and gives feedback when pressing the button~~ 11 | - Add a audio system that can play audio files in a loop and switch to another track immediately 12 | - Combine everything in a game with the following gameplay 13 | 14 | 15 | | Mode | Audio | LED | Trigger | 16 | | ------------- | ------------- | ------------- | ------------- | 17 | | STARTING | Motorcycle starts engine | Bright flash | when app starts | 18 | | IDLE | Motorcycle is neutral gear | slow fading | after STARTING or BREAKING mode| 19 | | ACCELERATE| Motorcycle is geeting more speed | LED chase is starting and getting faster |Accelerometer detects movement | 20 | | RUNNING | Motorcycle is driving | LED chase at high speed | Accelerometer continues detecting movement | 21 | | DEACCELERATE | Motorcycle is slowing down | LED chase is slowing down | Accelerometer does detect less movement | 22 | 23 | # How to install rockingMototcycle 24 | Check if you have python3 installed 25 | 26 | # links 27 | https://www.stuffaboutcode.com/2012/06/raspberry-pi-run-program-at-start-up.html 28 | https://raspberrypi.stackexchange.com/questions/37920/how-do-i-set-up-networking-wifi-static-ip-address/74428#74428 29 | 30 | # Install guide 31 | ## Setup pi 32 | 1. Flash a new raspbian lite 33 | 2. Before putting the sdcard into the raspi, create a empty file "ssh" 34 | 3. Create a new file "wpa_supplicant.conf" with the wifi settings: 35 | - ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev 36 | network={ 37 | ssid="" 38 | psk="" 39 | key_mgmt=WPA-PSK 40 | } 41 | 42 | ## Install Adafruit_Python_GPIO 43 | - sudo apt-get update 44 | - sudo apt-get install build-essential python3-pip python3-dev python3-smbus python3-setuptools git -y 45 | - git clone https://github.com/adafruit/Adafruit_Python_GPIO.git 46 | - cd Adafruit_Python_GPIO 47 | - sudo python3 setup.py install 48 | - Activate the i2c bus with the raspi-config tool: 49 | - sudo raspi-config 50 | - select Interfacing Options 51 | - Select I2C 52 | - Select yes and ok 53 | - check imu address: 54 | - sudo i2cdetect -y 1 55 | 56 | # Install Lib for alternative 6dof sensor lsm303d 57 | - sudo pip3 install lsm303d 58 | 59 | # Install NeoPixel 60 | - cd ~ 61 | - sudo apt-get install gcc make scons swig -y 62 | - git clone https://github.com/jgarff/rpi_ws281x 63 | - cd rpi_ws281x/ 64 | - sudo scons 65 | - cd python 66 | - sudo pip3 install rpi_ws281x 67 | - sudo python3 setup.py build 68 | - sudo python3 setup.py install 69 | 70 | 71 | 72 | ## Installation of Wiring pi 73 | - cd ~ 74 | - git clone git://git.drogon.net/wiringPi 75 | - cd wiringPi 76 | - ./build 77 | 78 | - Test Wiring pi 79 | - gpio -v 80 | - gpio readall 81 | - cd ~/rockingMotorcycle 82 | - gcc -o gpio_alt gpio_alt.c 83 | - sudo chown root:root gpio_alt 84 | - sudo chmod u+s gpio_alt 85 | - sudo mv gpio_alt /usr/local/bin/ 86 | - enable audio on rapsi: 87 | - sudo raspi-config 88 | - Advanced options 89 | - Audio 90 | - Force 3.5mm (Headphone) 91 | - Finish 92 | - test audio: 93 | - aplay audio/h_start.wav 94 | - set audio volume via alsamixer 95 | - set gpio alt automatically: 96 | - sudo chmod +x /home/pi/rockingMotorcycle/pwmaudio.sh 97 | - sudo cp /home/pi/rockingMotorcycle/pwmaudio.service /lib/systemd/system/pwmaudio.service 98 | - sudo systemctl enable pwmaudio.service 99 | - sudo systemctl start pwmaudio.service 100 | 101 | # Install pygame 102 | - sudo python3 -m pip install -U pygame --user 103 | - sudo apt-get install libsdl1.2debian libasound2-dev libsdl-mixer1.2 python-alsaaudio -y 104 | - cd ~ 105 | - git clone https://github.com/larsimmisch/pyalsaaudio.git 106 | - cd pyalsaaudio/ 107 | - sudo python3 setup.py build 108 | - sudo python3 setup.py install 109 | 110 | # install rocking game 111 | - sudo apt-get install python3-rpi.gpio daemon -y 112 | 113 | - sudo cp /home/pi/rockingMotorcycle/rockinggame.service /lib/systemd/system/rockinggame.service 114 | - sudo chmod 644 /lib/systemd/system/rockinggame.service 115 | - sudo systemctl daemon-reload 116 | - sudo systemctl enable rockinggame.service 117 | or 118 | - sudo cp /home/pi/rockingMotorcycle/rockinggame /etc/init.d/rockinggame 119 | - sudo chmod 755 /etc/init.d/rockinggame 120 | - sudo update-rc.d rockinggame defaults -------------------------------------------------------------------------------- /rockingMotorcycle.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Game for a awesome rocking motorcycle with lights, sound and motionsensor 3 | ''' 4 | import time 5 | from os import path 6 | 7 | import RPi.GPIO as GPIO 8 | 9 | from controller.imuController import IMUController 10 | from controller.ledController import LEDController 11 | from controller.lsmController import LSMController 12 | from lib.audio import Audio 13 | from lib.audioMixer import AudioMixer 14 | from lib.button import Button 15 | from lib.led import LED 16 | from lib.ledAnimations import TheaterChaseAnimation, LEDAnimation, RGBColor, FadeCycleAnimation, ColorSetAnimation 17 | from lib.ledStrip import LEDStrip 18 | 19 | 20 | class RockingMotorcycleGame: 21 | SOUND_START = "start" # 11 sec 22 | SOUND_STOP = "stop" # 6 sev 23 | SOUND_SPEEDUP = "speedUp" # 13 sec 24 | SOUND_IDLE = "idle" # 40 sec 25 | SOUND_DRIVE = "drive" # 22 sec 26 | 27 | MODE_START = 0 28 | MODE_DRIVE = 1 29 | MODE_STOP = 2 30 | 31 | LED_SPEED = 100 32 | 33 | def __init__(self, pinBTN=27, pinLED=17, startVolume=70, imuAddress=0x6a, limit=350, changeDelta=1000): 34 | GPIO.setmode(GPIO.BCM) 35 | 36 | led = LED(pinLED) 37 | 38 | self.ledStrip = LEDStrip() 39 | self.audio = Audio(startVolume) 40 | 41 | self.mixer = AudioMixer() 42 | self.initAudioMixer() 43 | 44 | self.btn = Button(pinBTN) 45 | self.btn.setListener(self.audioBtnListener) 46 | 47 | self.ledControl = LEDController(led) 48 | self.ledControl.start() 49 | 50 | self.changeDelta = changeDelta 51 | self.currentMode = -1 52 | 53 | # self.imuController = IMUController(imuAddress, limit=limit) 54 | self.imuController = LSMController(imuAddress, limit=limit) 55 | self.initImuController() 56 | 57 | self.animDrive = TheaterChaseAnimation(LEDAnimation.COLOR_CYAN, 20, 50, 15, True) 58 | self.animIdle = FadeCycleAnimation(RGBColor(0, 0, 0), RGBColor(0, 80, 0), 25, 20) 59 | 60 | self.ledStrip = LEDStrip() 61 | self.ledStrip.start() 62 | 63 | self.speed = 0 64 | self.lastLoop = self.getMillis() 65 | 66 | def initImuController(self): 67 | self.imuChangeTime = 0 68 | self.imuController.setListener(self.imuListener) 69 | self.imuController.start() 70 | 71 | def initAudioMixer(self): 72 | appPath = path.dirname(path.abspath(__file__)) 73 | audioFolder = path.join(appPath, "audio") 74 | 75 | self.mixer.addSound(self.SOUND_START, path.join(audioFolder, "h_start.wav")) 76 | self.mixer.addSound(self.SOUND_STOP, path.join(audioFolder, "h_stop.wav")) 77 | self.mixer.addSound(self.SOUND_SPEEDUP, path.join(audioFolder, "h_speedup.wav")) 78 | self.mixer.addSound(self.SOUND_IDLE, path.join(audioFolder, "h_idle.wav")) 79 | self.mixer.addSound(self.SOUND_DRIVE, path.join(audioFolder, "h_drive.wav")) 80 | 81 | self.mixer.setListener(self.mixerListener) 82 | 83 | def run(self): 84 | # indicate start 85 | self.ledControl.blink(2, 100, 100) 86 | 87 | while True: 88 | self.btn.loop() 89 | 90 | mode = self.getMode(self.imuChangeTime) 91 | 92 | accl = self.getAcceleration(mode, self.lastLoop) 93 | 94 | self.speed += accl 95 | 96 | if self.speed < 0: 97 | self.speed = 0 98 | elif self.speed > 100: 99 | self.speed = 100 100 | 101 | self.playLEDAnimation(mode, self.speed) 102 | 103 | self.playSound(mode) 104 | 105 | self.lastLoop = self.getMillis() 106 | 107 | self.currentMode = mode 108 | 109 | time.sleep(0.1) 110 | 111 | def mixerListener(self, id): 112 | return 113 | 114 | def playLEDAnimation(self, mode: int, speed: int): 115 | curAnim = self.ledStrip.getAnimation() 116 | 117 | if mode == self.MODE_START or speed <= 5: 118 | if curAnim != self.animIdle: 119 | self.ledStrip.setAnimation(self.animIdle) 120 | else: 121 | if curAnim is None or curAnim != self.animDrive: 122 | curAnim = self.animDrive 123 | self.ledStrip.setAnimation(curAnim) 124 | 125 | percentage = (100 - speed) / 100 126 | color = LEDAnimation.wheel(int(80 * (1 - percentage))) 127 | self.animDrive.update(color, int(10 + 200 * percentage), curAnim.iterations, 128 | curAnim.distance) 129 | 130 | def stop(self): 131 | self.mixer.stop() 132 | self.ledControl.stop() 133 | self.ledStrip.setAnimation(ColorSetAnimation(LEDAnimation.COLOR_BLACK)) 134 | time.sleep(1) 135 | self.ledStrip.stop() 136 | self.imuController.stop() 137 | GPIO.cleanup() 138 | 139 | def imuListener(self): 140 | self.imuChangeTime = self.getMillis() 141 | 142 | def getMillis(self): 143 | return int(round(time.time() * 1000)) 144 | 145 | def audioBtnListener(self, mode): 146 | vol = self.audio.getVolume() 147 | 148 | if mode is Button.NORMAL_PRESS: 149 | if vol == 100: 150 | self.audio.setVolume(70) 151 | vol = 70 152 | else: 153 | vol = self.audio.volumeUp() 154 | 155 | else: 156 | if self.audio.isMute: 157 | vol = self.audio.unmute() 158 | else: 159 | self.audio.mute() 160 | vol = 0 161 | 162 | if vol >= 70: 163 | self.ledControl.blink(int(vol / 10) - 6, self.LED_SPEED, self.LED_SPEED) 164 | else: 165 | self.ledControl.blink(1, 1000, self.LED_SPEED) 166 | 167 | def getMode(self, imuChangeTime: int): 168 | if imuChangeTime == 0: 169 | return self.MODE_START 170 | elif self.getMillis() - imuChangeTime < self.changeDelta: 171 | return self.MODE_DRIVE 172 | else: 173 | return self.MODE_STOP 174 | 175 | def playSound(self, mode: int): 176 | 177 | if self.currentMode == mode: 178 | return 179 | 180 | self.mixer.clearQueue() 181 | 182 | if mode == self.MODE_START: 183 | self.mixer.playSound(self.SOUND_START, False, 0) 184 | self.mixer.queue(self.SOUND_IDLE, True) 185 | elif mode == self.MODE_DRIVE: 186 | self.mixer.playSound(self.SOUND_SPEEDUP, False) 187 | self.mixer.queue(self.SOUND_DRIVE, True) 188 | elif mode == self.MODE_STOP: 189 | self.mixer.playSound(self.SOUND_STOP, False) 190 | self.mixer.queue(self.SOUND_IDLE, True) 191 | 192 | def getAcceleration(self, mode: int, lastLoop: int): 193 | 194 | accl = 0 195 | 196 | if mode != self.MODE_START: 197 | # get time between loops 198 | diff = self.getMillis() - lastLoop 199 | 200 | speedChange = (diff / 1000) * 5 # 17 kmh per sec 201 | 202 | if mode == self.MODE_DRIVE: 203 | # decreases speed 204 | accl = speedChange 205 | 206 | elif mode == self.MODE_STOP: 207 | # increase speed 208 | accl -= 2 * speedChange 209 | 210 | return accl 211 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import time 4 | 5 | import RPi.GPIO as GPIO 6 | 7 | from controller.imuController import IMUController 8 | from controller.ledController import LEDController 9 | from controller.lsmController import LSMController 10 | from lib.LSM6DS3 import LSM6DS3 11 | from lib.LSM6DS3_ALT import LSM6DS3_alt 12 | from lib.audio import Audio 13 | from lib.audioMixer import AudioMixer 14 | from lib.button import Button 15 | from lib.led import LED 16 | from lib.ledAnimations import TheaterChaseAnimation, LEDAnimation, ColorWipeAnimation, RainbowAnimation, \ 17 | RainbowCycleAnimation, TheaterChaseRainbowAnimation, ColorSetAnimation, FadeAnimation, RGBColor, FadeCycleAnimation 18 | from lib.ledStrip import LEDStrip 19 | 20 | GPIO.setmode(GPIO.BCM) 21 | 22 | pinLED = 17 23 | pinBTN = 27 24 | 25 | 26 | def printResult(result): 27 | print("Result: {}".format(result)) 28 | 29 | 30 | def ledTest(): 31 | print("Start LED test") 32 | 33 | led = LED(pinLED) 34 | led.setListener(printResult) 35 | try: 36 | 37 | while True: 38 | led.on() 39 | time.sleep(1) 40 | led.off() 41 | time.sleep(1) 42 | except KeyboardInterrupt: 43 | print("Cancel led test") 44 | 45 | 46 | def buttonTest(): 47 | print("Start button test") 48 | 49 | button = Button(pinBTN) 50 | button.setListener(printResult) 51 | try: 52 | 53 | while True: 54 | button.loop() 55 | except KeyboardInterrupt: 56 | print("Cancel button test") 57 | 58 | 59 | def buttonLedTest(): 60 | print("Start Button-LED test") 61 | 62 | led = LED(pinLED) 63 | button = Button(pinBTN) 64 | lc = LEDController(led) 65 | lc.start() 66 | 67 | def buttonListner(mode): 68 | printResult(mode) 69 | if mode is Button.NORMAL_PRESS: 70 | lc.blink(3, 0.5, 0.5) 71 | else: 72 | lc.blink(1, 3, 0) 73 | 74 | button.setListener(buttonListner) 75 | try: 76 | while True: 77 | button.loop() 78 | except KeyboardInterrupt: 79 | print("Cancel button-LED test") 80 | 81 | lc.stop() 82 | 83 | 84 | animIndex = -1 85 | 86 | 87 | def ledStripTest(): 88 | print("Start led strip test") 89 | ledStrip = LEDStrip() 90 | 91 | animations = [] 92 | 93 | 94 | animations.append(ColorSetAnimation(LEDAnimation.COLOR_BLACK)) 95 | animations.append(TheaterChaseAnimation(LEDAnimation.COLOR_YELLOW, 100, 50, 15, True)) 96 | animations.append(TheaterChaseAnimation(LEDAnimation.COLOR_CYAN, 100, 50, 15)) 97 | 98 | 99 | animations.append(TheaterChaseAnimation(LEDAnimation.COLOR_BLUE, 500, 10, 15)) 100 | animations.append(TheaterChaseAnimation(LEDAnimation.COLOR_GREEN, 50, 10, 15)) 101 | animations.append(TheaterChaseAnimation(LEDAnimation.COLOR_RED, 50, 50, 15)) 102 | animations.append(TheaterChaseAnimation(LEDAnimation.COLOR_MAGENTA, 20, 50, 15)) 103 | animations.append(TheaterChaseAnimation(LEDAnimation.COLOR_CYAN, 20, 50, 15)) 104 | animations.append(TheaterChaseAnimation(LEDAnimation.COLOR_YELLOW, 20, 100, 15)) 105 | animations.append(FadeCycleAnimation(RGBColor(0, 0, 0), RGBColor(255, 255, 255), 25, int(1000 / 200))) 106 | animations.append(FadeAnimation(RGBColor(255, 0, 0), RGBColor(0, 255, 0), 50, int(1000 / 50))) 107 | animations.append(FadeAnimation(RGBColor(0, 255, 0), RGBColor(0, 0, 255), 50, int(1000 / 50))) 108 | animations.append(RainbowAnimation()) 109 | animations.append(TheaterChaseAnimation(LEDAnimation.COLOR_WHITE)) 110 | animations.append(ColorWipeAnimation(LEDAnimation.COLOR_RED)) 111 | animations.append(RainbowCycleAnimation()) 112 | animations.append(TheaterChaseRainbowAnimation()) 113 | animations.append(ColorSetAnimation(LEDAnimation.COLOR_BLACK)) 114 | 115 | ledStrip.start() 116 | 117 | def setNextAnimation(): 118 | global animIndex 119 | animIndex += 1 120 | if animIndex >= len(animations): 121 | animIndex = 0 122 | 123 | a = animations[animIndex] 124 | ledStrip.setAnimation(a) 125 | 126 | def onButtonClick(mode): 127 | if mode is Button.NORMAL_PRESS: 128 | setNextAnimation() 129 | 130 | else: 131 | a = ColorWipeAnimation(LEDAnimation.COLOR_BLACK) 132 | ledStrip.setAnimation(a) 133 | 134 | button = Button(pinBTN) 135 | button.setListener(onButtonClick) 136 | setNextAnimation() 137 | 138 | try: 139 | while True: 140 | button.loop() 141 | except KeyboardInterrupt: 142 | print("Cancel button-LED test") 143 | 144 | ledStrip.stop() 145 | 146 | 147 | def imuTest(): 148 | print("Start imu test") 149 | lsm = LSM6DS3(0x6a) 150 | try: 151 | while True: 152 | acclX = lsm.readRawAccelX() 153 | acclY = lsm.readRawAccelX() 154 | acclZ = lsm.readRawAccelX() 155 | gyroX = lsm.readRawGyroX() 156 | gyroY = lsm.readRawGyroY() 157 | gyroZ = lsm.readRawGyroZ() 158 | print("accl {} ".format((acclX, acclY, acclZ))) 159 | print("gyro {} ".format((gyroX, gyroY, gyroZ))) 160 | print("angle {} {}".format(lsm.calcAnglesXY(), lsm.calcGyroXAngle())) 161 | 162 | time.sleep(0.2) 163 | except KeyboardInterrupt: 164 | print("Cancel imu test") 165 | 166 | 167 | def imuTestAlt(): 168 | print("Start imu test") 169 | lsm_alt = LSM6DS3_alt(0x6a) 170 | try: 171 | while True: 172 | accl = lsm_alt.accel.xyz() 173 | gyro = lsm_alt.gyro.xyz() 174 | print("acclx {} ".format(accl)) 175 | print("gyro {} ".format(gyro)) 176 | # print("temp {} ".format(lsm_alt.temperature())) 177 | 178 | time.sleep(0.2) 179 | except KeyboardInterrupt: 180 | print("Cancel imu test") 181 | 182 | 183 | def imuControllerTest(): 184 | print("Start imu controller test") 185 | 186 | def onChange(): 187 | print("has changed") 188 | 189 | imuController = IMUController(0x6a, limit=1000) 190 | imuController.setListener(onChange) 191 | imuController.start() 192 | 193 | try: 194 | while True: 195 | time.sleep(0.2) 196 | except KeyboardInterrupt: 197 | print("Cancel imu controller test") 198 | 199 | 200 | def audioTest(): 201 | print("Start audio test") 202 | audio = Audio(70) 203 | 204 | def onPressed(mode): 205 | vol = audio.getVolume() 206 | if mode is Button.NORMAL_PRESS: 207 | if vol == 100: 208 | audio.setVolume(70) 209 | else: 210 | audio.volumeUp() 211 | 212 | else: 213 | if audio.isMute: 214 | audio.unmute() 215 | else: 216 | audio.mute() 217 | 218 | print("volume {}".format(audio.getVolume())) 219 | 220 | btn = Button(pinBTN) 221 | btn.setListener(onPressed) 222 | 223 | try: 224 | print(audio.getVolume()) 225 | print(audio.setVolume(20)) 226 | print(audio.getVolume()) 227 | 228 | while True: 229 | btn.loop() 230 | except KeyboardInterrupt: 231 | print("Cancel audio test") 232 | 233 | 234 | def lsm303dTest(): 235 | import time 236 | from lsm303d import LSM303D 237 | 238 | lsm = LSM303D(0x1d) 239 | 240 | 241 | while True: 242 | t = lsm.temperature() 243 | m = lsm.magnetometer() 244 | a = lsm.accelerometer() 245 | # t_raw = lsm._lsm303d.values['TEMPERATURE'] 246 | # print("{:04.1f} {:016b}".format(t, t_raw)) 247 | 248 | values = list(m) + list(a) 249 | 250 | print(("{:+06.2f} : {:+06.2f} : {:+06.2f} " * 2).format(*values)) 251 | # print(("{:+06.2f} : {:+06.2f} : {:+06.2f} " ).format(*a)) 252 | # print(a) 253 | 254 | time.sleep(1.0 / 25) 255 | 256 | def lsmControllerTest(): 257 | def onChanged(): 258 | print("changed") 259 | 260 | lsm = LSMController(limit=0.2) 261 | lsm.setListener(onChanged) 262 | lsm.start() 263 | 264 | 265 | 266 | def audioMixerTest(): 267 | print("Start audio mixer test") 268 | 269 | import os 270 | from os import path 271 | 272 | appPath = os.path.dirname(os.path.abspath(__file__)) 273 | audioFolder = path.join(appPath, "audio") 274 | 275 | audio = Audio(70) 276 | mixer = AudioMixer() 277 | 278 | mixer.addSound("start", path.join(audioFolder, "h_start.wav")) 279 | mixer.addSound("stop", path.join(audioFolder, "h_stop.wav")) 280 | mixer.addSound("speedUp", path.join(audioFolder, "h_speedup.wav")) 281 | mixer.addSound("idle", path.join(audioFolder, "h_idle.wav")) 282 | mixer.addSound("drive", path.join(audioFolder, "h_drive.wav")) 283 | 284 | 285 | def onPressed(mode): 286 | if mode is Button.NORMAL_PRESS: 287 | mixer.clearQueue() 288 | mixer.playSound("speedUp", False, 300) 289 | mixer.queue("drive", True) 290 | else: 291 | mixer.clearQueue() 292 | mixer.playSound("stop", False, 300) 293 | mixer.queue("idle", True) 294 | 295 | btn = Button(pinBTN) 296 | btn.setListener(onPressed) 297 | 298 | mixer.playSound("start", False, 0) 299 | mixer.queue("idle", True) 300 | 301 | try: 302 | while True: 303 | btn.loop() 304 | except KeyboardInterrupt: 305 | print("Cancel audio mixer test") 306 | 307 | mixer.stop() 308 | 309 | 310 | print("Start test units. Use ctrl+c to stop current test.") 311 | 312 | # audioTest() 313 | # audioMixerTest() 314 | # ledStripTest() 315 | lsmControllerTest() 316 | # lsm303dTest() 317 | # 318 | #imuControllerTest() 319 | # imuTest() 320 | # imuTestAlt() 321 | # ledTest() 322 | # buttonTest() 323 | # buttonLedTest() 324 | 325 | # Cleanup GPIO settings 326 | GPIO.cleanup() 327 | 328 | print("All tests done") 329 | -------------------------------------------------------------------------------- /lib/ledAnimations.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from neopixel import * 4 | 5 | 6 | class RGBColor(): 7 | def __init__(self, r, g, b, w=0): 8 | self.r = r 9 | self.g = g 10 | self.b = b 11 | self.w = w 12 | 13 | def toColor(self): 14 | return Color(self.g, self.r, self.b, self.w) 15 | 16 | 17 | class LEDAnimation: 18 | COLOR_GREEN = Color(255, 0, 0) # green, red, blue 19 | COLOR_RED = Color(0, 255, 0) 20 | COLOR_BLUE = Color(0, 0, 255) 21 | COLOR_MAGENTA = Color(0, 255, 255) 22 | COLOR_YELLOW = Color(255, 255, 0) 23 | COLOR_CYAN = Color(255, 0, 255) 24 | 25 | COLOR_WHITE = Color(255, 255, 255) 26 | COLOR_BLACK = Color(0, 0, 0) 27 | 28 | def __init__(self, name="LEDAnimation", wait_ms=50): 29 | self.name = name 30 | self.isRunning = False 31 | self.isStopped = True 32 | self.wait_ms = wait_ms 33 | self.strip = None 34 | 35 | @staticmethod 36 | def wheel(pos): 37 | """Generate rainbow colors across 0-255 positions.""" 38 | if pos < 85: 39 | return Color(255 - pos * 3, pos * 3, 0) 40 | elif pos < 170: 41 | pos -= 85 42 | return Color(0, 255 - pos * 3, pos * 3) 43 | else: 44 | pos -= 170 45 | return Color(pos * 3, 0, 255 - pos * 3) 46 | 47 | def isValid(self): 48 | return self.isStopped is False and self.strip is not None 49 | 50 | def loop(self): 51 | if self.isValid() is False: 52 | return 53 | 54 | self.isRunning = True 55 | 56 | print("loop") 57 | self.setColor(self.COLOR_WHITE) 58 | time.sleep(1) 59 | self.setColor(self.COLOR_BLACK) 60 | time.sleep(1) 61 | 62 | self.isRunning = False 63 | 64 | def setStrip(self, strip: Adafruit_NeoPixel): 65 | self.strip = strip 66 | 67 | def stop(self): 68 | self.isStopped = True 69 | 70 | while self.isRunning: 71 | time.sleep(0.1) 72 | 73 | def start(self): 74 | self.isStopped = False 75 | 76 | def setColor(self, color): 77 | for i in range(self.strip.numPixels()): 78 | for i in range(self.strip.numPixels()): 79 | self.strip.setPixelColor(i, color) 80 | self.strip.show() 81 | 82 | def __str__(self): 83 | return self.name 84 | 85 | 86 | class FadeAnimation(LEDAnimation): 87 | 88 | def __init__(self, c1, c2, iterations: int = 10, wait_ms: int = 50): 89 | super().__init__("Fade", wait_ms) 90 | 91 | self.iterations = iterations 92 | self.c1 = c1 93 | self.c2 = c2 94 | 95 | def fade(self, c1, c2): 96 | for i in range(1, self.iterations + 1): 97 | 98 | if self.isStopped: 99 | break 100 | 101 | color = self.calcColor(c1, c2, i) 102 | for j in range(self.strip.numPixels()): 103 | self.strip.setPixelColor(j, color) 104 | self.strip.show() 105 | 106 | time.sleep(self.wait_ms / 1000.0) 107 | 108 | def loop(self): 109 | if self.isValid() is False: 110 | return 111 | 112 | self.isRunning = True 113 | 114 | print("fade") 115 | 116 | self.fade(self.c1, self.c2) 117 | 118 | self.isRunning = False 119 | 120 | def calcColor(self, c1, c2, i): 121 | r = int(((c1.r * (self.iterations - i)) + (c2.r * i)) / self.iterations) 122 | g = int(((c1.g * (self.iterations - i)) + (c2.g * i)) / self.iterations) 123 | b = int(((c1.b * (self.iterations - i)) + (c2.b * i)) / self.iterations) 124 | w = int(((c1.w * (self.iterations - i)) + (c2.w * i)) / self.iterations) 125 | 126 | return Color(g, r, b, w) 127 | 128 | 129 | class FadeCycleAnimation(FadeAnimation): 130 | 131 | def __init__(self, c1, c2, iterations: int = 10, wait_ms: int = 50): 132 | super().__init__(c1, c2, iterations, wait_ms) 133 | 134 | self.name = "Fade cycle" 135 | 136 | def loop(self): 137 | if self.isValid() is False: 138 | return 139 | 140 | self.isRunning = True 141 | 142 | # print("fad cyclee") 143 | 144 | self.fade(self.c1, self.c2) 145 | self.fade(self.c2, self.c1) 146 | 147 | self.isRunning = False 148 | 149 | 150 | class ColorSetAnimation(LEDAnimation): 151 | def __init__(self, color: Color, wait_ms: int = 50): 152 | super().__init__("Color set", wait_ms) 153 | 154 | self.color = color 155 | 156 | def loop(self): 157 | if self.isValid() is False: 158 | return 159 | 160 | self.isRunning = True 161 | 162 | # print("color", self.color) 163 | 164 | self.setColor(self.color) 165 | time.sleep(1) 166 | 167 | self.isRunning = False 168 | 169 | 170 | class TheaterChaseAnimation(LEDAnimation): 171 | def __init__(self, color: Color, wait_ms: int = 50, iterations: int = 10, distance: int = 3, splitAndInvert=False): 172 | super().__init__("Theater Chase", wait_ms) 173 | 174 | self.splitAndInvert = splitAndInvert 175 | self.update(color, wait_ms, iterations, distance) 176 | 177 | def update(self, color: Color, wait_ms: int, iterations: int, distance: int): 178 | self.wait_ms = wait_ms 179 | self.color = color 180 | self.iterations = iterations 181 | self.distance = distance 182 | 183 | def loop(self): 184 | if self.isValid() is False: 185 | return 186 | 187 | self.isRunning = True 188 | 189 | # print("theaterChase") 190 | 191 | """Movie theater light style chaser animation.""" 192 | for j in range(self.iterations): 193 | if self.isStopped: 194 | break 195 | 196 | for q in range(self.distance): 197 | if self.isStopped: 198 | break 199 | 200 | numPixels = self.strip.numPixels() 201 | 202 | if self.splitAndInvert is False: 203 | for i in range(0, numPixels, self.distance): 204 | self.strip.setPixelColor(i + q, self.color) 205 | else: 206 | halfNumPixels = int(numPixels / 2) 207 | 208 | for i in range(0, halfNumPixels, self.distance): 209 | self.strip.setPixelColor(i + q, self.color) 210 | for i in range(numPixels, halfNumPixels, -self.distance): 211 | self.strip.setPixelColor(numPixels - (numPixels - i + q) - 1, self.color) 212 | 213 | self.strip.show() 214 | 215 | time.sleep(self.wait_ms / 1000.0) 216 | 217 | if self.splitAndInvert is False: 218 | for i in range(0, self.strip.numPixels(), self.distance): 219 | self.strip.setPixelColor(i + q, 0) 220 | else: 221 | halfNumPixels = int(numPixels / 2) 222 | 223 | for i in range(0, halfNumPixels, self.distance): 224 | self.strip.setPixelColor(i + q, 0) 225 | for i in range(numPixels, halfNumPixels, -self.distance): 226 | self.strip.setPixelColor(numPixels - (numPixels - i + q) - 1, 0) 227 | 228 | self.isRunning = False 229 | 230 | 231 | class TheaterChaseRainbowAnimation(LEDAnimation): 232 | def __init__(self, wait_ms: int = 50, iterations: int = 10): 233 | super().__init__("Theater Chase Rainbow", wait_ms) 234 | 235 | self.iterations = iterations 236 | 237 | def loop(self): 238 | if self.isValid() is False: 239 | return 240 | 241 | self.isRunning = True 242 | 243 | # print("theaterChaseRainbow") 244 | 245 | """Rainbow movie theater light style chaser animation.""" 246 | for j in range(256): 247 | for q in range(3): 248 | if self.isStopped: 249 | break 250 | 251 | for i in range(0, self.strip.numPixels(), 3): 252 | self.strip.setPixelColor(i + q, self.wheel((i + j) % 255)) 253 | self.strip.show() 254 | 255 | time.sleep(self.wait_ms / 1000.0) 256 | 257 | if self.isStopped: 258 | break 259 | 260 | for i in range(0, self.strip.numPixels(), 3): 261 | self.strip.setPixelColor(i + q, 0) 262 | 263 | self.isRunning = False 264 | 265 | 266 | class RainbowAnimation(LEDAnimation): 267 | def __init__(self, wait_ms: int = 50, iterations: int = 10): 268 | super().__init__("Reainbow", wait_ms) 269 | 270 | self.iterations = iterations 271 | 272 | def loop(self): 273 | if self.isValid() is False: 274 | return 275 | 276 | self.isRunning = True 277 | 278 | # print("rainbow") 279 | 280 | """Draw rainbow that fades across all pixels at once.""" 281 | for j in range(256 * self.iterations): 282 | if self.isStopped: 283 | break 284 | 285 | for i in range(self.strip.numPixels()): 286 | self.strip.setPixelColor(i, self.wheel((i + j) & 255)) 287 | self.strip.show() 288 | 289 | time.sleep(self.wait_ms / 1000.0) 290 | 291 | self.isRunning = False 292 | 293 | 294 | class RainbowCycleAnimation(LEDAnimation): 295 | def __init__(self, wait_ms: int = 50, iterations: int = 10): 296 | super().__init__("Reainbow cycle", wait_ms) 297 | 298 | self.iterations = iterations 299 | 300 | def loop(self): 301 | if self.isValid() is False: 302 | return 303 | 304 | self.isRunning = True 305 | 306 | # print("rainbowCycle") 307 | 308 | """Draw rainbow that uniformly distributes itself across all pixels.""" 309 | for j in range(256 * self.iterations): 310 | if self.isStopped: 311 | break 312 | 313 | for i in range(self.strip.numPixels()): 314 | self.strip.setPixelColor(i, self.wheel((int(i * 256 / self.strip.numPixels()) + j) & 255)) 315 | 316 | self.strip.show() 317 | time.sleep(self.wait_ms / 1000.0) 318 | 319 | self.isRunning = False 320 | 321 | 322 | class ColorWipeAnimation(LEDAnimation): 323 | def __init__(self, color: Color, wait_ms: int = 50, iterations: int = 10): 324 | super().__init__("Color wipe", wait_ms) 325 | 326 | self.color = color 327 | 328 | def loop(self): 329 | 330 | if self.isValid() is False: 331 | return 332 | 333 | self.isRunning = True 334 | 335 | # print("colorWipe") 336 | 337 | """Wipe color across display a pixel at a time.""" 338 | for i in range(self.strip.numPixels()): 339 | if self.isStopped: 340 | break 341 | 342 | self.strip.setPixelColor(i, self.color) 343 | self.strip.show() 344 | 345 | time.sleep(self.wait_ms / 1000.0) 346 | 347 | self.isRunning = False 348 | --------------------------------------------------------------------------------