sumorobot-firmware/hal.py

328 lines
10 KiB
Python
Raw Normal View History

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
from machine import Pin, PWM, ADC, time_pulse_us
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)
2019-02-10 20:07:38 +00:00
# LED sensor feedback
self.sensor_feedback = True
2018-07-11 19:14:45 +00:00
# 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)
2019-02-10 20:07:38 +00:00
# Scope with sensor data
self.sensor_scope = dict()
# WiFi connection
self.is_wifi_connected = False
# Python and Blockly code
self.python_code = ""
self.blockly_code = ""
self.compiled_python_code = ""
2018-07-11 19:14:45 +00:00
# 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))
2019-04-14 14:39:19 +00:00
self.bat_charge = Pin(25, Pin.IN)
2018-07-11 19:14:45 +00:00
# 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
2019-04-14 14:25:28 +00:00
return round((time_pulse_us(self.echo, 1, 30000) / 2) / 29.1, 2)
2018-01-11 18:35:09 +00:00
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
def calibrate_line_value(self):
2018-07-11 19:14:45 +00:00
# Read the line sensor values
self.config["left_line_value"] = self.adc_line_left.read()
self.config["right_line_value"] = self.adc_line_right.read()
# Update the config file
with open("config.part", "w") as config_file:
config_file.write(ujson.dumps(self.config))
os.rename("config.part", "config.json")
2019-02-10 20:07:38 +00:00
# Function to update line threshold calibration and write it to the config file
2019-02-10 20:07:38 +00:00
def set_line_threshold(self, value):
# Read the line sensor values
self.config["left_line_threshold"] = value
self.config["right_line_threshold"] = value
2018-07-11 19:14:45 +00:00
# Update the config file
with open("config.part", "w") as config_file:
2018-11-13 20:36:35 +00:00
config_file.write(ujson.dumps(self.config))
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)
2019-02-10 20:07:38 +00:00
def update_sensor_feedback(self):
if self.sensor_feedback:
# Execute to see LED feedback for sensors
self.is_opponent()
self.is_line(LEFT)
self.is_line(RIGHT)
def update_sensor_scope(self):
self.sensor_scope = dict(
left_line = self.get_line(LEFT),
right_line = self.get_line(RIGHT),
opponent = self.get_opponent_distance(),
2019-04-14 14:39:19 +00:00
battery_charge = self.bat_charge.value(),
2019-02-10 20:07:38 +00:00
battery_voltage = self.get_battery_voltage()
)
def get_python_code(self):
return dict(
type = "python_code",
val = self.python_code
)
def get_blockly_code(self):
return dict(
type = "blockly_code",
val = self.blockly_code
)
def get_sensor_scope(self):
temp = self.sensor_scope
temp['type'] = "sensor_scope"
return temp
def get_line_scope(self):
return dict(
type = "line_scope",
left_line_value = self.config["left_line_value"],
right_line_value = self.config["right_line_value"],
left_line_threshold = self.config["left_line_threshold"],
right_line_threshold = self.config["right_line_threshold"]
)
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
return
2018-01-11 18:35:09 +00:00
else:
sleep_ms(50)