2018-01-13 15:18:50 +00:00
|
|
|
import os
|
2018-01-12 09:24:06 +00:00
|
|
|
import ujson
|
2018-01-11 18:35:09 +00:00
|
|
|
from utime import sleep_us, sleep_ms
|
2018-08-15 06:36:27 +00:00
|
|
|
from machine import Pin, PWM, ADC, time_pulse_us, deepsleep
|
2018-01-10 18:11:06 +00:00
|
|
|
|
2018-01-11 18:35:09 +00:00
|
|
|
# LEDs
|
2018-01-16 17:04:38 +00:00
|
|
|
STATUS = 0
|
|
|
|
OPPONENT = 1
|
2018-01-10 18:11:06 +00:00
|
|
|
LEFT_LINE = 2
|
|
|
|
RIGHT_LINE = 3
|
|
|
|
|
2018-07-11 19:14:45 +00:00
|
|
|
# Directions
|
2018-01-10 18:11:06 +00:00
|
|
|
STOP = 0
|
|
|
|
LEFT = 1
|
|
|
|
RIGHT = 2
|
2018-07-11 19:33:37 +00:00
|
|
|
SEARCH = 3
|
|
|
|
FORWARD = 4
|
|
|
|
BACKWARD = 5
|
2018-01-10 18:11:06 +00:00
|
|
|
|
2018-01-11 18:35:09 +00:00
|
|
|
class Sumorobot(object):
|
2018-07-11 19:14:45 +00:00
|
|
|
# Constructor
|
|
|
|
def __init__(self, config = None):
|
|
|
|
# Config file
|
|
|
|
self.config = config
|
|
|
|
|
|
|
|
# Ultrasonic distance sensor
|
|
|
|
self.echo = Pin(14, Pin.IN)
|
|
|
|
self.trigger = Pin(27, Pin.OUT)
|
|
|
|
|
|
|
|
# Servo PWM-s
|
|
|
|
self.pwm_left = PWM(Pin(15), freq=50, duty=0)
|
|
|
|
self.pwm_right = PWM(Pin(4), freq=50, duty=0)
|
|
|
|
|
|
|
|
# Bottom status LED
|
2018-09-01 07:20:45 +00:00
|
|
|
self.status_led = Pin(self.config["status_led_pin"], Pin.OUT)
|
2018-07-11 19:14:45 +00:00
|
|
|
# Bottom status LED is in reverse polarity
|
|
|
|
self.status_led.value(1)
|
|
|
|
# Sensor LEDs
|
|
|
|
self.opponent_led = Pin(16, Pin.OUT)
|
|
|
|
self.left_line_led = Pin(17, Pin.OUT)
|
|
|
|
self.right_line_led = Pin(12, Pin.OUT)
|
|
|
|
|
|
|
|
# Battery gauge
|
2018-07-11 19:33:37 +00:00
|
|
|
self.bat_status = 4.3
|
|
|
|
self.move_counter = 0
|
2018-07-11 19:14:45 +00:00
|
|
|
self.adc_battery = ADC(Pin(32))
|
|
|
|
|
|
|
|
# The pullups for the phototransistors
|
|
|
|
Pin(19, Pin.IN, Pin.PULL_UP)
|
|
|
|
Pin(23, Pin.IN, Pin.PULL_UP)
|
|
|
|
|
|
|
|
# The phototransistors
|
2018-11-13 20:36:35 +00:00
|
|
|
self.last_line = LEFT
|
2018-07-11 19:14:45 +00:00
|
|
|
self.adc_line_left = ADC(Pin(34))
|
|
|
|
self.adc_line_right = ADC(Pin(33))
|
|
|
|
|
|
|
|
# Set reference voltage to 3.3V
|
|
|
|
self.adc_battery.atten(ADC.ATTN_11DB)
|
|
|
|
self.adc_line_left.atten(ADC.ATTN_11DB)
|
|
|
|
self.adc_line_right.atten(ADC.ATTN_11DB)
|
|
|
|
|
|
|
|
# To smooth out ultrasonic sensor value
|
|
|
|
self.opponent_score = 0
|
|
|
|
|
|
|
|
# For terminating sleep
|
|
|
|
self.terminate = False
|
2018-08-15 06:36:27 +00:00
|
|
|
|
2018-07-11 19:33:37 +00:00
|
|
|
# For search mode
|
|
|
|
self.search = False
|
|
|
|
self.search_counter = 0
|
2018-07-11 19:14:45 +00:00
|
|
|
|
|
|
|
# Memorise previous servo speeds
|
|
|
|
self.prev_speed = {LEFT: 0, RIGHT: 0}
|
|
|
|
|
|
|
|
# Function to set LED states
|
2018-01-11 18:35:09 +00:00
|
|
|
def set_led(self, led, state):
|
2018-07-11 19:14:45 +00:00
|
|
|
# Set the given LED state
|
2018-01-11 18:35:09 +00:00
|
|
|
if led == STATUS:
|
2018-07-11 19:14:45 +00:00
|
|
|
# Status LED is reverse polarity
|
|
|
|
self.status_led.value(0 if state else 1)
|
2018-01-16 17:04:38 +00:00
|
|
|
elif led == OPPONENT:
|
|
|
|
self.opponent_led.value(state)
|
2018-01-11 18:35:09 +00:00
|
|
|
elif led == LEFT_LINE:
|
|
|
|
self.left_line_led.value(state)
|
|
|
|
elif led == RIGHT_LINE:
|
|
|
|
self.right_line_led.value(state)
|
|
|
|
|
2018-07-11 19:14:45 +00:00
|
|
|
# Function to shortly bink status LED
|
|
|
|
def toggle_led(self, timer = None):
|
|
|
|
self.status_led.value(0)
|
|
|
|
sleep_ms(10)
|
|
|
|
self.status_led.value(1)
|
|
|
|
|
|
|
|
# Function to get battery voltage
|
2018-01-11 18:35:09 +00:00
|
|
|
def get_battery_voltage(self):
|
2018-07-11 19:33:37 +00:00
|
|
|
bat = round(self.config["battery_coeff"] * (self.adc_battery.read() * 3.3 / 4096), 2)
|
|
|
|
# When the SumoRobot is not moving
|
|
|
|
if self.prev_speed[LEFT] == 0 and self.prev_speed[RIGHT] == 0:
|
|
|
|
if self.move_counter > 0:
|
|
|
|
self.move_counter -= 1
|
|
|
|
if self.bat_status < bat - 0.20 and self.move_counter == 0:
|
2018-09-01 07:20:45 +00:00
|
|
|
#deepsleep()
|
|
|
|
pass
|
2018-07-11 19:33:37 +00:00
|
|
|
self.bat_status = bat
|
|
|
|
else:
|
|
|
|
self.move_counter = 10
|
|
|
|
return bat
|
2018-01-11 18:35:09 +00:00
|
|
|
|
2018-07-11 19:14:45 +00:00
|
|
|
# Function to get distance (cm) from the object in front of the SumoRobot
|
2018-01-16 17:04:38 +00:00
|
|
|
def get_opponent_distance(self):
|
2018-07-11 19:14:45 +00:00
|
|
|
# Send a pulse
|
2018-01-11 18:35:09 +00:00
|
|
|
self.trigger.value(0)
|
|
|
|
sleep_us(5)
|
|
|
|
self.trigger.value(1)
|
|
|
|
sleep_us(10)
|
|
|
|
self.trigger.value(0)
|
2018-07-11 19:14:45 +00:00
|
|
|
# Wait for the pulse and calculate the distance
|
2018-01-11 18:35:09 +00:00
|
|
|
return (time_pulse_us(self.echo, 1, 30000) / 2) / 29.1
|
|
|
|
|
2018-07-11 19:14:45 +00:00
|
|
|
# Function to get boolean if there is something in front of the SumoRobot
|
2018-09-01 07:20:45 +00:00
|
|
|
def is_opponent(self):
|
2018-07-11 19:14:45 +00:00
|
|
|
# Get the opponent distance
|
2018-01-16 17:04:38 +00:00
|
|
|
self.opponent_distance = self.get_opponent_distance()
|
2018-07-11 19:14:45 +00:00
|
|
|
# When the opponent is close and the ping actually returned
|
|
|
|
if self.opponent_distance < self.config["ultrasonic_distance"] and self.opponent_distance > 0:
|
|
|
|
# When not maximum score
|
2018-01-16 17:04:38 +00:00
|
|
|
if self.opponent_score < 5:
|
2018-07-11 19:14:45 +00:00
|
|
|
# Increase the opponent score
|
2018-01-16 17:04:38 +00:00
|
|
|
self.opponent_score += 1
|
2018-07-11 19:14:45 +00:00
|
|
|
# When no opponent was detected
|
2018-01-11 18:35:09 +00:00
|
|
|
else:
|
2018-07-11 19:14:45 +00:00
|
|
|
# When not lowest score
|
2018-01-16 17:04:38 +00:00
|
|
|
if self.opponent_score > 0:
|
2018-07-11 19:14:45 +00:00
|
|
|
# Decrease the opponent score
|
2018-01-16 17:04:38 +00:00
|
|
|
self.opponent_score -= 1
|
2018-01-11 18:35:09 +00:00
|
|
|
|
2018-07-11 19:14:45 +00:00
|
|
|
# When the sensor saw something more than 2 times
|
2018-01-16 17:04:38 +00:00
|
|
|
opponent = True if self.opponent_score > 2 else False
|
2018-01-11 18:35:09 +00:00
|
|
|
|
2018-07-11 19:14:45 +00:00
|
|
|
# Trigger opponent LED
|
2018-01-16 17:04:38 +00:00
|
|
|
self.set_led(OPPONENT, opponent)
|
2018-01-11 18:35:09 +00:00
|
|
|
|
2018-01-16 17:04:38 +00:00
|
|
|
return opponent
|
2018-01-11 18:35:09 +00:00
|
|
|
|
2018-07-11 19:14:45 +00:00
|
|
|
# Function to update line calibration and write it to the config file
|
2018-01-13 15:18:50 +00:00
|
|
|
def calibrate_line(self):
|
2018-07-11 19:14:45 +00:00
|
|
|
# Read the line sensor values
|
|
|
|
self.config["left_line_threshold"] = self.adc_line_left.read()
|
|
|
|
self.config["right_line_threshold"] = self.adc_line_right.read()
|
|
|
|
# Update the config file
|
2018-01-13 15:18:50 +00:00
|
|
|
with open("config.part", "w") as config_file:
|
2018-11-13 20:36:35 +00:00
|
|
|
config_file.write(ujson.dumps(self.config))
|
2018-01-13 15:18:50 +00:00
|
|
|
os.rename("config.part", "config.json")
|
|
|
|
|
2018-07-11 19:14:45 +00:00
|
|
|
# Function to get light inensity from the phototransistors
|
2018-01-11 18:35:09 +00:00
|
|
|
def get_line(self, dir):
|
2018-09-01 07:20:45 +00:00
|
|
|
# Check if the direction is valid
|
|
|
|
assert dir in (LEFT, RIGHT)
|
2018-01-11 18:35:09 +00:00
|
|
|
|
2018-07-11 19:14:45 +00:00
|
|
|
# Return the given line sensor value
|
2018-01-11 18:35:09 +00:00
|
|
|
if dir == LEFT:
|
|
|
|
return self.adc_line_left.read()
|
|
|
|
elif dir == RIGHT:
|
|
|
|
return self.adc_line_right.read()
|
|
|
|
|
2018-09-01 07:20:45 +00:00
|
|
|
def is_line(self, dir):
|
|
|
|
# Check if the direction is valid
|
|
|
|
assert dir in (LEFT, RIGHT)
|
2018-01-11 18:35:09 +00:00
|
|
|
|
2018-07-11 19:14:45 +00:00
|
|
|
# Return the given line sensor value
|
2018-01-11 18:35:09 +00:00
|
|
|
if dir == LEFT:
|
2018-12-27 07:54:28 +00:00
|
|
|
line = abs(self.get_line(LEFT) - self.config["left_line_value"]) > self.config["left_line_threshold"]
|
2018-01-11 18:35:09 +00:00
|
|
|
self.set_led(LEFT_LINE, line)
|
2018-11-13 20:36:35 +00:00
|
|
|
last_line = LEFT
|
2018-01-11 18:35:09 +00:00
|
|
|
return line
|
|
|
|
elif dir == RIGHT:
|
2018-12-27 07:54:28 +00:00
|
|
|
line = abs(self.get_line(RIGHT) - self.config["right_line_value"]) > self.config["right_line_threshold"]
|
2018-01-11 18:35:09 +00:00
|
|
|
self.set_led(RIGHT_LINE, line)
|
2018-11-13 20:36:35 +00:00
|
|
|
last_line = RIGHT
|
2018-01-11 18:35:09 +00:00
|
|
|
return line
|
|
|
|
|
|
|
|
def set_servo(self, dir, speed):
|
2018-09-01 07:20:45 +00:00
|
|
|
# Check if the direction is valid
|
|
|
|
assert dir in (LEFT, RIGHT)
|
|
|
|
# Check if the speed is valid
|
2018-01-11 18:35:09 +00:00
|
|
|
assert speed <= 100 and speed >= -100
|
|
|
|
|
2018-07-11 19:14:45 +00:00
|
|
|
# When the speed didn't change
|
2018-01-11 18:35:09 +00:00
|
|
|
if speed == self.prev_speed[dir]:
|
|
|
|
return
|
|
|
|
|
2018-07-11 19:14:45 +00:00
|
|
|
# Record the new speed
|
2018-01-11 18:35:09 +00:00
|
|
|
self.prev_speed[dir] = speed
|
|
|
|
|
2018-07-11 19:14:45 +00:00
|
|
|
# Set the given servo speed
|
2018-01-11 18:35:09 +00:00
|
|
|
if dir == LEFT:
|
|
|
|
if speed == 0:
|
|
|
|
self.pwm_left.duty(0)
|
|
|
|
else:
|
2018-07-11 19:14:45 +00:00
|
|
|
# -100 ... 100 to 33 .. 102
|
|
|
|
self.pwm_left.duty(int(33 + self.config["left_servo_tuning"] + speed * 33 / 100))
|
2018-01-11 18:35:09 +00:00
|
|
|
elif dir == RIGHT:
|
|
|
|
if speed == 0:
|
|
|
|
self.pwm_right.duty(0)
|
|
|
|
else:
|
2018-07-11 19:14:45 +00:00
|
|
|
# -100 ... 100 to 33 .. 102
|
|
|
|
self.pwm_right.duty(int(33 + self.config["right_servo_tuning"] + speed * 33 / 100))
|
2018-01-11 18:35:09 +00:00
|
|
|
|
2018-09-01 07:20:45 +00:00
|
|
|
def move(self, dir):
|
|
|
|
# Check if the direction is valid
|
|
|
|
assert dir in (SEARCH, STOP, RIGHT, LEFT, BACKWARD, FORWARD)
|
2018-01-11 18:35:09 +00:00
|
|
|
|
2018-07-11 19:14:45 +00:00
|
|
|
# Go to the given direction
|
2018-01-11 18:35:09 +00:00
|
|
|
if dir == STOP:
|
|
|
|
self.set_servo(LEFT, 0)
|
|
|
|
self.set_servo(RIGHT, 0)
|
|
|
|
elif dir == LEFT:
|
|
|
|
self.set_servo(LEFT, -100)
|
|
|
|
self.set_servo(RIGHT, -100)
|
|
|
|
elif dir == RIGHT:
|
|
|
|
self.set_servo(LEFT, 100)
|
|
|
|
self.set_servo(RIGHT, 100)
|
2018-07-11 19:33:37 +00:00
|
|
|
elif dir == SEARCH:
|
|
|
|
# Change search mode after X seconds
|
|
|
|
if self.search_counter == 50:
|
|
|
|
self.search = not self.search
|
|
|
|
self.search_counter = 0
|
2018-09-01 07:20:45 +00:00
|
|
|
# When in search mode
|
2018-07-11 19:33:37 +00:00
|
|
|
if self.search:
|
2018-09-01 07:20:45 +00:00
|
|
|
# Go forward
|
|
|
|
self.set_servo(LEFT, 100)
|
|
|
|
self.set_servo(RIGHT, -100)
|
2018-11-13 20:36:35 +00:00
|
|
|
elif last_line == RIGHT:
|
2018-09-01 07:20:45 +00:00
|
|
|
# Go left
|
|
|
|
self.set_servo(LEFT, -100)
|
|
|
|
self.set_servo(RIGHT, -100)
|
2018-11-13 20:36:35 +00:00
|
|
|
else:
|
|
|
|
# Go right
|
|
|
|
self.set_servo(LEFT, 100)
|
|
|
|
self.set_servo(RIGHT, 100)
|
2018-07-11 19:33:37 +00:00
|
|
|
# Increase search counter
|
|
|
|
self.search_counter += 1
|
2018-01-11 18:35:09 +00:00
|
|
|
elif dir == FORWARD:
|
|
|
|
self.set_servo(LEFT, 100)
|
|
|
|
self.set_servo(RIGHT, -100)
|
|
|
|
elif dir == BACKWARD:
|
|
|
|
self.set_servo(LEFT, -100)
|
|
|
|
self.set_servo(RIGHT, 100)
|
|
|
|
|
2018-09-01 07:20:45 +00:00
|
|
|
def sleep(self, delay):
|
2018-07-11 19:14:45 +00:00
|
|
|
# Check for valid delay
|
2018-01-11 18:35:09 +00:00
|
|
|
assert delay > 0
|
|
|
|
|
2018-07-11 19:14:45 +00:00
|
|
|
# Split the delay into 50ms chunks
|
2018-01-11 18:35:09 +00:00
|
|
|
for j in range(0, delay, 50):
|
2018-07-11 19:14:45 +00:00
|
|
|
# Check for forceful termination
|
2018-01-11 18:35:09 +00:00
|
|
|
if self.terminate:
|
2018-07-11 19:14:45 +00:00
|
|
|
# Terminate the delay
|
2018-01-12 09:09:01 +00:00
|
|
|
return
|
2018-01-11 18:35:09 +00:00
|
|
|
else:
|
|
|
|
sleep_ms(50)
|