├── BlueDotPirate.py ├── README.md ├── BlueDotPirateMixed.py ├── TinyKeyboardSSH.py ├── TinyPirate.py └── TinyBluetoothKeyboard.py /BlueDotPirate.py: -------------------------------------------------------------------------------- 1 | 2 | from bluedot import BlueDot 3 | from signal import pause 4 | from explorerhat import motor 5 | 6 | maxPower = 1.0 7 | 8 | def move(pos): 9 | global maxPower 10 | x_axis = 0.0 11 | y_axis = 0.0 12 | if pos.top: 13 | y_axis= pos.distance 14 | 15 | elif pos.bottom: 16 | y_axis =-pos.distance 17 | elif pos.left: 18 | x_axis =-pos.distance 19 | elif pos.right: 20 | x_axis= pos.distance 21 | 22 | mixer_results = mixer(x_axis, y_axis) 23 | 24 | power_left = ((mixer_results[0]) * 100) 25 | power_right = ((mixer_results[1])* 100) 26 | 27 | print(power_left, power_right) 28 | 29 | motor.one.speed((-power_right * maxPower)) 30 | motor.two.speed(power_left * maxPower) 31 | def stop(): 32 | print("stop") 33 | motor.one.speed(0) 34 | motor.two.speed(0) 35 | 36 | def mixer(inYaw, inThrottle,): 37 | left = inThrottle + inYaw 38 | right = inThrottle - inYaw 39 | scaleLeft = abs(left) 40 | scaleRight = abs(right) 41 | scaleMax = max(scaleLeft, scaleRight) 42 | scaleMax = max(1, scaleMax) 43 | out_left = (constrain(left / scaleMax, -1, 1)) 44 | out_right = (constrain(right / scaleMax, -1, 1)) 45 | results = [out_right, out_left] 46 | return results 47 | 48 | def constrain(val, min_val, max_val): 49 | return min(max_val, max(min_val, val)) 50 | 51 | bd = BlueDot() 52 | 53 | 54 | bd.when_pressed = move 55 | bd.when_moved = move 56 | bd.when_released = stop 57 | 58 | pause() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tiny_4wd 2 | software for the Coretec Tiny 4wd and setup instructions 3 | 4 | 5 | Manual control with a game controller like the Rock Candy 6 | 7 | 1) install the Pimoroni Explorer Python library, instructions can be found on the Explorer page on the Pimoroni website https://shop.pimoroni.com/products/explorer-phat 8 | 9 | 2) next install the Python 'inputs' library at the command line enter 10 | 11 | sudo pip install inputs 12 | 13 | 3) download TinyPirate.py form here by entering at the command line the following 14 | 15 | wget https://raw.githubusercontent.com/Coretec-Robotics/Tiny_4wd/master/TinyPirate.py 16 |   17 | 4) it's best to restart your Raspberry Pi before running TinyPirate.py 18 | 19 | 5) after restarting your Pi enter the following to run TinyPirate.py 20 | 21 |   sudo python ./TinyPirate.py 22 |   23 | 24 | Manual control with the BlueDot Android app 25 | 26 | if you want to install BlueDotPirate.py instead of BlueDotPirateMixed.py replace BlueDotPirateMixed.py with BlueDotPirate.py 27 | 28 | 1) install the Pimoroni Explorer Python library, instructions can be found on the Explorer page on the Pimoroni website https://shop.pimoroni.com/products/explorer-phat 29 | 30 | 2) follow the instutions on the BlueDot website 31 | 32 | http://bluedot.readthedocs.io/en/latest/gettingstarted.html 33 | 34 | 3) download BlueDotPirateMixed.py form here by entering at the command line the following 35 | 36 | wget https://raw.githubusercontent.com/Coretec-Robotics/Tiny_4wd/master/BlueDotPirateMixed.py 37 | 38 | 4) it's best to restart your Raspberry Pi before running BlueDotPirateMixed.py 39 | 40 | 5) after restarting your Pi enter the following to run BlueDotPirateMixed.py 41 | 42 |   sudo python3 ./BlueDotPirateMixed.py 43 | 44 | -------------------------------------------------------------------------------- /BlueDotPirateMixed.py: -------------------------------------------------------------------------------- 1 | from bluedot import BlueDot 2 | from signal import pause 3 | from explorerhat import motor 4 | 5 | maxPower = 1.0 6 | 7 | def pos_to_values(x, y): 8 | left = y if x > 0 else y + x 9 | right = y if x < 0 else y - x 10 | return (clamped(left), clamped(right)) 11 | 12 | def clamped(v): 13 | return max(-1, min(1, v)) 14 | 15 | def drive(): 16 | global maxPower 17 | x_axis = 0.0 18 | y_axis = 0.0 19 | min_power = 0.3 20 | while True: 21 | if bd.is_pressed: 22 | x_axis, y_axis = bd.position.x, bd.position.y 23 | print(x_axis,y_axis) 24 | else: 25 | x_axis, y_axis = 0, 0 26 | 27 | if abs(x_axis) < min_power: 28 | x_axis = 0 29 | 30 | if abs(y_axis) < min_power: 31 | y_axis = 0 32 | 33 | mixer_results = mixer(x_axis, y_axis) 34 | 35 | power_left = ((mixer_results[0]) * 100) 36 | power_right = ((mixer_results[1]) * 100) 37 | 38 | # print(power_left, power_right) 39 | 40 | motor.one.speed((-power_right * maxPower)) 41 | motor.two.speed(power_left * maxPower) 42 | 43 | def mixer(inYaw, inThrottle,): 44 | left = inThrottle + inYaw 45 | right = inThrottle - inYaw 46 | scaleLeft = abs(left) 47 | scaleRight = abs(right) 48 | scaleMax = max(scaleLeft, scaleRight) 49 | scaleMax = max(1, scaleMax) 50 | out_left = (constrain(left / scaleMax, -1, 1)) 51 | out_right = (constrain(right / scaleMax, -1, 1)) 52 | results = [out_right, out_left] 53 | return results 54 | 55 | def constrain(val, min_val, max_val): 56 | return min(max_val, max(min_val, val)) 57 | 58 | bd = BlueDot() 59 | 60 | drive() 61 | 62 | pause() -------------------------------------------------------------------------------- /TinyKeyboardSSH.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: Latin-1 3 | # Load library functions we want 4 | from sys import exit 5 | import atexit 6 | import curses 7 | from explorerhat import motor 8 | 9 | # -------------------------------------------------------------------------------------------------------- 10 | # Info 11 | 12 | # Purpose: Control a Tiny4WD using keyboard keys within a ssh-session 13 | # Editor : Stefan Theurer sttheurer@googlemail.com 14 | # Thanks for the well written recipe on 15 | # https://gpiozero.readthedocs.io/en/stable/recipes.html#keyboard-controlled-robot 16 | 17 | # ------------------------------------------------------------------------------------------ 18 | # Python Libraries 19 | 20 | # You may need to install these Python dependencies: 21 | 22 | # sudo pip3 install explorerhat 23 | 24 | 25 | 26 | # The max speed setting for the motors. 27 | MAX_SPEED=100 28 | 29 | # Depending on which way round the positive and negative wires are connected to the motors, 30 | # one or both of these values may need to be negative, e.g. -MAX_POWER 31 | LEFT_MOTOR_MAX_SPEED_FWD = MAX_SPEED # The top speed that makes the left motors turn in the forward direction 32 | RIGHT_MOTOR_MAX_SPEED_FWD = -MAX_SPEED # The top speed that makes the right motors turn in the forward direction 33 | 34 | # Depending on which side of your robot motors are mounted these might need swapping 35 | left_motor = motor.two # The explorer pHAT 'motor.one' is connected to the robots left hand motors 36 | right_motor = motor.one # The explorer pHAT 'motor.two' is connected to the robots right hand motors 37 | 38 | 39 | # -------------------------------------------------------------------------------------------------------- 40 | # Utilities 41 | 42 | # Run this whenever the script exits 43 | @atexit.register 44 | def cleanup(): 45 | # disable all motors 46 | motor.stop() 47 | print("Bye") 48 | 49 | 50 | # -------------------------------------------------------------------------------------------------------- 51 | # Motor Control 52 | 53 | # You shouldn't need to change these, but if the robot is not going in the direction you expect 54 | # please see the settings section above and adjust the LEFT_MOTOR_MAX_POWER_FWD, RIGHT_MOTOR_MAX_POWER_FWD 55 | # left_motor & right_motor settings there. 56 | 57 | def forward(): 58 | left_motor.speed(LEFT_MOTOR_MAX_SPEED_FWD) 59 | right_motor.speed(RIGHT_MOTOR_MAX_SPEED_FWD) 60 | 61 | def backward(): 62 | left_motor.speed(-LEFT_MOTOR_MAX_SPEED_FWD) 63 | right_motor.speed(-RIGHT_MOTOR_MAX_SPEED_FWD) 64 | 65 | def left(): 66 | left_motor.speed(-LEFT_MOTOR_MAX_SPEED_FWD) 67 | right_motor.speed(RIGHT_MOTOR_MAX_SPEED_FWD) 68 | 69 | def right(): 70 | left_motor.speed(LEFT_MOTOR_MAX_SPEED_FWD) 71 | right_motor.speed(-RIGHT_MOTOR_MAX_SPEED_FWD) 72 | 73 | def stop(): 74 | left_motor.speed(0) 75 | right_motor.speed(0) 76 | 77 | 78 | def end(): 79 | exit(0) 80 | 81 | # -------------------------------------------------------------------------------------------------------- 82 | # Keyboard Actions 83 | 84 | actions = { 85 | # Arrow keys: 86 | curses.KEY_UP: forward, 87 | curses.KEY_DOWN: backward, 88 | curses.KEY_LEFT: left, 89 | curses.KEY_RIGHT: right, 90 | } 91 | 92 | # -------------------------------------------------------------------------------------------------------- 93 | # Main 94 | 95 | def main(window): 96 | next_key = None 97 | 98 | while True: 99 | if next_key is None: 100 | key = window.getch() 101 | else: 102 | key = next_key 103 | next_key = None 104 | if key != -1: 105 | # KEY DOWN 106 | curses.halfdelay(1) 107 | action = actions.get(key) 108 | if action is not None: 109 | action() 110 | next_key = key 111 | while next_key == key: 112 | next_key = window.getch() 113 | # KEY UP 114 | stop() 115 | 116 | curses.wrapper(main) 117 | -------------------------------------------------------------------------------- /TinyPirate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: Latin-1 3 | 4 | # Load library functions we want 5 | 6 | from inputs import get_gamepad 7 | from explorerhat import motor 8 | 9 | 10 | def mixer(inYaw, inThrottle,): 11 | left = inThrottle + inYaw 12 | right = inThrottle - inYaw 13 | scaleLeft = abs(left / 125.0) 14 | scaleRight = abs(right / 125.0) 15 | scaleMax = max(scaleLeft, scaleRight) 16 | scaleMax = max(1, scaleMax) 17 | out_left = int(constrain(left / scaleMax, -125, 125)) 18 | out_right = int(constrain(right / scaleMax, -125, 125)) 19 | results = [out_right, out_left] 20 | return results 21 | 22 | def constrain(val, min_val, max_val): 23 | return min(max_val, max(min_val, val)) 24 | 25 | # Setup 26 | maxPower = 1.0 27 | power_left = 0.0 28 | power_right = 0.0 29 | x_axis = 0.0 30 | y_axis = 0.0 31 | 32 | 33 | try: 34 | print('Press CTRL+C to quit') 35 | 36 | # Loop indefinitely 37 | while True: 38 | 39 | events = get_gamepad() 40 | for event in events: 41 | print(event.code, event.state) 42 | if event.code == "ABS_Y": 43 | if event.state > 130: 44 | print("Backwards") 45 | elif event.state < 125: 46 | print("Forward") 47 | y_axis = event.state 48 | if y_axis > 130: 49 | y_axis = -(y_axis - 130) 50 | elif y_axis < 125: 51 | y_axis = ((-y_axis) + 125) 52 | else: 53 | y_axis = 0.0 54 | print("Y: " + str(-y_axis)) 55 | if event.code == "ABS_Z": 56 | if event.state > 130: 57 | print("Right") 58 | elif event.state < 125: 59 | print("Left") 60 | x_axis = event.state 61 | if x_axis > 130: 62 | x_axis = (x_axis - 130) 63 | elif x_axis < 125: 64 | x_axis = -((-x_axis) + 125) 65 | else: 66 | x_axis = 0.0 67 | print("X: " + str(x_axis)) 68 | 69 | if event.code == "BTN_TL": 70 | if event.state == True: 71 | print("Botton Left") 72 | if event.code == "BTN_TR": 73 | if event.state == True: 74 | print("Botton Right") 75 | if event.code == "BTN_Z": 76 | if event.state == True: 77 | print("Top right") 78 | 79 | if event.code == "BTN_WEST": 80 | if event.state == True: 81 | print("Top left") 82 | 83 | if event.code == "BTN_TL2": 84 | if event.state == True: 85 | print("Select") 86 | 87 | 88 | x_axis = 0 89 | y_axis = 0 90 | if event.code == "ABS_HAT0X": 91 | if event.state == -1: 92 | print("D pad Left") 93 | 94 | elif event.state == 1: 95 | print("D pad Right") 96 | 97 | if event.code == "ABS_HAT0Y": 98 | 99 | if event.state == -1: 100 | 101 | print("D pad Up") 102 | 103 | 104 | elif event.state == 1: 105 | 106 | print("D pad Down") 107 | 108 | 109 | mixer_results = mixer(x_axis, y_axis) 110 | #print (mixer_results) 111 | power_left = int((mixer_results[0] / 125.0)*100) 112 | power_right = int((mixer_results[1] / 125.0)*100) 113 | print("left: " + str(power_left) + " right: " + str(power_right)) 114 | 115 | motor.one.speed((-power_right * maxPower)) 116 | motor.two.speed(power_left * maxPower) 117 | 118 | 119 | 120 | 121 | # print(event.ev_type, event.code, event.state) 122 | 123 | 124 | except KeyboardInterrupt: 125 | 126 | # CTRL+C exit, disable all drives 127 | print("stop") 128 | motor.stop() 129 | print("bye") -------------------------------------------------------------------------------- /TinyBluetoothKeyboard.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: Latin-1 3 | # Load library functions we want 4 | from sys import exit 5 | import atexit 6 | from evdev import InputDevice, list_devices, ecodes 7 | from explorerhat import motor 8 | 9 | # -------------------------------------------------------------------------------------------------------- 10 | # Info 11 | 12 | # Purpose: Control a Tiny4WD using a Bluetooth keyboard or a joypad pretending to be a Bluetooth keyboard 13 | # Author : Wayne Keenan - wayne@thebubbleworks.com 14 | 15 | # ------------------------------------------------------------------------------------------ 16 | # Python Libraries 17 | 18 | # You may need to install these Python dependencies: 19 | 20 | # sudo pip3 install evdev 21 | # sudo pip3 install explorerhat 22 | 23 | # -------------------------------------------------------------------------------------------------------- 24 | # Notes: 25 | 26 | # A Joypad that can pretend to be a keyboard: http://www.8bitdo.com/zero/ 27 | 28 | # The evdev keyboard detection and handling based on an example from gpiozero: 29 | # https://gpiozero.readthedocs.io/en/stable/recipes.html#keyboard-controlled-robot 30 | 31 | 32 | # -------------------------------------------------------------------------------------------------------- 33 | # Bluetooth Keyboard Setup Instructions 34 | 35 | # Pair a Bluetooth keyboard using the default Bluetooth app on the Pi's desktop, it's fairly straight forward. 36 | 37 | # Alternatively, to pair a bluetooth keyboard using a Terminal or via SSH, the steps are: 38 | 39 | # -- Run: 40 | # 41 | # bluetoothctl 42 | 43 | # -- This wil start a 'bluetooth command shell', enter these commands: 44 | 45 | # power on 46 | # agent on 47 | # scan on 48 | 49 | # -- You should eventually, be patient, see something like: [NEW] Device 97:13:70:C5:AA:BB Bluetooth Keyboard 50 | # -- The value 97:13:70:C5:AA:BB will be different and use your value in place of below: 51 | 52 | # connect 53 | # trust 54 | # pair 55 | 56 | # -- You should see something like: [agent] PIN code: 967776 57 | # -- type the code you have on the Bluetooth keyboard and press RETURN and then back in 'bluetooth command shell' 58 | 59 | # connect 60 | 61 | # -- You might have to reenter the connect, trust and pair commands and you may have to start from the begining 62 | # -- To exit type: 63 | 64 | # exit 65 | 66 | 67 | # To reconnect in the future all you need to do is : 68 | 69 | # bluetoothctl 70 | # connect 71 | 72 | # You may have to re-run the connect command if it fails the first few times. 73 | 74 | # If the desktop and bluetooth manager is running you may automatically see a popup window asking to pair, say yes. 75 | 76 | 77 | # -------------------------------------------------------------------------------------------------------- 78 | # Settings 79 | 80 | # Note: This script uses the first keyboard found, which could be USB or Bluetooth. 81 | # So either only have the (bluetooth) keyboard you want connected or adjust KEYBOARD_INDEX, e.g. 1, 2, 3 ... 82 | KEYBOARD_INDEX = 0 83 | 84 | # The max speed setting for the motors. 85 | MAX_SPEED=100 86 | 87 | # Depending on which way round the positive and negative wires are connected to the motors, 88 | # one or both of these values may need to be negative, e.g. -MAX_POWER 89 | LEFT_MOTOR_MAX_SPEED_FWD = MAX_SPEED # The top speed that makes the left motors turn in the forward direction 90 | RIGHT_MOTOR_MAX_SPEED_FWD = MAX_SPEED # The top speed that makes the right motors turn in the forward direction 91 | 92 | # Depending on which side of your robot motors are mounted these might need swapping 93 | left_motor = motor.two # The explorer pHAT 'motor.one' is connected to the robots left hand motors 94 | right_motor = motor.one # The explorer pHAT 'motor.two' is connected to the robots right hand motors 95 | 96 | 97 | # -------------------------------------------------------------------------------------------------------- 98 | # Utilities 99 | 100 | # Run this whenever the script exits 101 | @atexit.register 102 | def cleanup(): 103 | # disable all motors 104 | motor.stop() 105 | print("Bye") 106 | 107 | 108 | def get_keyboard(keyboard_index = 0): 109 | 110 | # Get the list of available input devices 111 | devices = [InputDevice(device) for device in list_devices()] 112 | # Filter out everything that's not a keyboard. Keyboards are defined as any 113 | # device which has keys, and which specifically has keys 1..31 (roughly Esc, 114 | # the numeric keys, the first row of QWERTY plus a few more) and which does 115 | # *not* have key 0 (reserved) 116 | must_have = {i for i in range(1, 32)} 117 | must_not_have = {0} 118 | devices = [ 119 | dev 120 | for dev in devices 121 | for keys in (set(dev.capabilities().get(ecodes.EV_KEY, [])),) 122 | if must_have.issubset(keys) 123 | and must_not_have.isdisjoint(keys) 124 | ] 125 | device = None 126 | if len(devices) > 0: 127 | device = devices[keyboard_index] 128 | 129 | return device 130 | 131 | 132 | # -------------------------------------------------------------------------------------------------------- 133 | # Motor Control 134 | 135 | # You shouldn't need to change these, but if the robot is not going in the direction you expect 136 | # please see the settings section above and adjust the LEFT_MOTOR_MAX_POWER_FWD, RIGHT_MOTOR_MAX_POWER_FWD 137 | # left_motor & right_motor settings there. 138 | 139 | def forward(): 140 | left_motor.speed(LEFT_MOTOR_MAX_SPEED_FWD) 141 | right_motor.speed(RIGHT_MOTOR_MAX_SPEED_FWD) 142 | 143 | def backward(): 144 | left_motor.speed(-LEFT_MOTOR_MAX_SPEED_FWD) 145 | right_motor.speed(-RIGHT_MOTOR_MAX_SPEED_FWD) 146 | 147 | def left(): 148 | left_motor.speed(-LEFT_MOTOR_MAX_SPEED_FWD) 149 | right_motor.speed(RIGHT_MOTOR_MAX_SPEED_FWD) 150 | 151 | def right(): 152 | left_motor.speed(LEFT_MOTOR_MAX_SPEED_FWD) 153 | right_motor.speed(-RIGHT_MOTOR_MAX_SPEED_FWD) 154 | 155 | 156 | def stop(): 157 | left_motor.speed(0) 158 | right_motor.speed(0) 159 | 160 | 161 | def end(): 162 | exit(0) 163 | 164 | # -------------------------------------------------------------------------------------------------------- 165 | # Keyboard Actions 166 | 167 | keypress_actions = { 168 | # Arrow keys: 169 | ecodes.KEY_UP: forward, 170 | ecodes.KEY_DOWN: backward, 171 | ecodes.KEY_LEFT: left, 172 | ecodes.KEY_RIGHT: right, 173 | 174 | # 8BitDo Zero 'keys' 175 | ecodes.KEY_C: forward, 176 | ecodes.KEY_D: backward, 177 | ecodes.KEY_E: left, 178 | ecodes.KEY_F: right, 179 | 180 | # Other commands 181 | ecodes.KEY_ESC: end, 182 | } 183 | 184 | # -------------------------------------------------------------------------------------------------------- 185 | # Main 186 | 187 | try: 188 | print('Press ESCAPE or CTRL+C to quit') 189 | keyboard = get_keyboard(KEYBOARD_INDEX) 190 | 191 | if keyboard: 192 | for event in keyboard.read_loop(): 193 | if event.type == ecodes.EV_KEY and event.code in keypress_actions: 194 | if event.value == 1: # key down 195 | keypress_actions[event.code]() 196 | if event.value == 0: # key up 197 | stop() 198 | else: 199 | print("No keyboard found!") 200 | 201 | except KeyboardInterrupt: 202 | # CTRL+C exit 203 | print("stopping...") 204 | --------------------------------------------------------------------------------