############################################################################
# CoderBot, a didactical programmable robot.
# Copyright (C) 2014, 2015 Roberto Previtera
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
############################################################################
import os
import sys
import time
from math import copysign
import logging
import pigpio
import sonar
from hw import mpu
from rotary_encoder.wheelsaxel import WheelsAxel
# GPIO
class GPIO_CODERBOT_V_4():
# motors
PIN_MOTOR_ENABLE = 22
PIN_LEFT_FORWARD = 25
PIN_LEFT_BACKWARD = 24
PIN_RIGHT_FORWARD = 4
PIN_RIGHT_BACKWARD = 17
PIN_PUSHBUTTON = 11
# servo
PIN_SERVO_1 = 9
PIN_SERVO_2 = 10
# sonar
PIN_SONAR_1_TRIGGER = 18
PIN_SONAR_1_ECHO = 7
PIN_SONAR_2_TRIGGER = 18
PIN_SONAR_2_ECHO = 8
PIN_SONAR_3_TRIGGER = 18
PIN_SONAR_3_ECHO = 23
PIN_SONAR_4_TRIGGER = 18
PIN_SONAR_4_ECHO = 13
# encoder
PIN_ENCODER_LEFT_A = 14
PIN_ENCODER_LEFT_B = 6
PIN_ENCODER_RIGHT_A = 15
PIN_ENCODER_RIGHT_B = 12
HAS_ENCODER = False
class GPIO_CODERBOT_V_5():
# motors
PIN_MOTOR_ENABLE = None #22
PIN_LEFT_FORWARD = 17 #25
PIN_LEFT_BACKWARD = 18 # 24
PIN_RIGHT_FORWARD = 22 # 4
PIN_RIGHT_BACKWARD = 23 #17
PIN_PUSHBUTTON = 16 #11
# servo
PIN_SERVO_1 = 19 #9
PIN_SERVO_2 = 26 #10
# sonar
PIN_SONAR_1_TRIGGER = 5 #18
PIN_SONAR_1_ECHO = 27 #7
PIN_SONAR_2_TRIGGER = 5 #18
PIN_SONAR_2_ECHO = 6 #8
PIN_SONAR_3_TRIGGER = 5 #18
PIN_SONAR_3_ECHO = 12 #23
PIN_SONAR_4_TRIGGER = 5 #18
PIN_SONAR_4_ECHO = 13 #23
# encoder
PIN_ENCODER_LEFT_A = 14
PIN_ENCODER_LEFT_B = 15 #6
PIN_ENCODER_RIGHT_A = 24 #15
PIN_ENCODER_RIGHT_B = 25 #12
HAS_ENCODER = True
# PWM
PWM_FREQUENCY = 100 #Hz
PWM_RANGE = 100 #0-100
HW_VERSIONS = {
"4": GPIO_CODERBOT_V_4(),
"5": GPIO_CODERBOT_V_5()
}
class CoderBot(object):
# pylint: disable=too-many-instance-attributes
def __init__(self, motor_trim_factor=1.0, motor_min_power=0, motor_max_power=100, hw_version="5", pid_params=(0.8, 0.1, 0.01, 200, 0.01)):
try:
self._mpu = mpu.AccelGyroMag()
logging.info("MPU available")
except:
logging.info("MPU not available")
self.GPIOS = HW_VERSIONS.get(hw_version, GPIO_CODERBOT_V_5())
self._pin_out = [self.GPIOS.PIN_LEFT_FORWARD, self.GPIOS.PIN_RIGHT_FORWARD, self.GPIOS.PIN_LEFT_BACKWARD, self.GPIOS.PIN_RIGHT_BACKWARD, self.GPIOS.PIN_SERVO_1, self.GPIOS.PIN_SERVO_2]
self.pi = pigpio.pi('localhost')
self.pi.set_mode(self.GPIOS.PIN_PUSHBUTTON, pigpio.INPUT)
self._cb = dict()
self._cb_last_tick = dict()
self._cb_elapse = dict()
self._encoder = self.GPIOS.HAS_ENCODER
self._motor_trim_factor = motor_trim_factor
self._motor_min_power = motor_min_power
self._motor_max_power = motor_max_power
self._twin_motors_enc = WheelsAxel(
self.pi,
enable_pin=self.GPIOS.PIN_MOTOR_ENABLE,
left_forward_pin=self.GPIOS.PIN_LEFT_FORWARD,
left_backward_pin=self.GPIOS.PIN_LEFT_BACKWARD,
left_encoder_feedback_pin_A=self.GPIOS.PIN_ENCODER_LEFT_A,
left_encoder_feedback_pin_B=self.GPIOS.PIN_ENCODER_LEFT_B,
right_forward_pin=self.GPIOS.PIN_RIGHT_FORWARD,
right_backward_pin=self.GPIOS.PIN_RIGHT_BACKWARD,
right_encoder_feedback_pin_A=self.GPIOS.PIN_ENCODER_RIGHT_A,
right_encoder_feedback_pin_B=self.GPIOS.PIN_ENCODER_RIGHT_B,
pid_params=pid_params)
self.motor_control = self._dc_enc_motor
self._cb1 = self.pi.callback(self.GPIOS.PIN_PUSHBUTTON, pigpio.EITHER_EDGE, self._cb_button)
for pin in self._pin_out:
self.pi.set_PWM_frequency(pin, PWM_FREQUENCY)
self.pi.set_PWM_range(pin, PWM_RANGE)
self.sonar = [sonar.Sonar(self.pi, self.GPIOS.PIN_SONAR_1_TRIGGER, self.GPIOS.PIN_SONAR_1_ECHO),
sonar.Sonar(self.pi, self.GPIOS.PIN_SONAR_2_TRIGGER, self.GPIOS.PIN_SONAR_2_ECHO),
sonar.Sonar(self.pi, self.GPIOS.PIN_SONAR_3_TRIGGER, self.GPIOS.PIN_SONAR_3_ECHO),
sonar.Sonar(self.pi, self.GPIOS.PIN_SONAR_4_TRIGGER, self.GPIOS.PIN_SONAR_4_ECHO)]
self._servos = [self.GPIOS.PIN_SERVO_1, self.GPIOS.PIN_SERVO_2]
self.stop()
the_bot = None
def exit(self):
self._cb1.cancel()
if self._encoder:
self._twin_motors_enc.cancel_callback()
for s in self.sonar:
s.cancel()
@classmethod
def get_instance(cls, motor_trim_factor=1.0, motor_max_power=100, motor_min_power=0, hw_version="5", pid_params=(0.8, 0.1, 0.01, 200, 0.01), from_defaults=True):
if not cls.the_bot:
if from_defaults:
raise ValueError("incorrect CoderBot initialisation")
cls.the_bot = CoderBot(motor_trim_factor=motor_trim_factor, motor_max_power= motor_max_power, motor_min_power=motor_min_power, hw_version=hw_version, pid_params=pid_params)
return cls.the_bot
def get_motor_power(self, speed):
return int(copysign(min(max(((self._motor_max_power - self._motor_min_power) * abs(speed) / 100) + self._motor_min_power, self._motor_min_power), self._motor_max_power), speed))
def move(self, speed=100, elapse=None, distance=None):
speed_left = speed * self._motor_trim_factor
speed_right = speed / self._motor_trim_factor
self.motor_control(speed_left=speed_left, speed_right=speed_right, time_elapse=elapse, target_distance=distance)
def turn(self, speed=100, elapse=None, distance=None):
speed_left = speed * self._motor_trim_factor
speed_right = -speed / self._motor_trim_factor
self.motor_control(speed_left=speed_left, speed_right=speed_right, time_elapse=elapse, target_distance=distance)
def turn_angle(self, speed=100, angle=0):
z = self._mpu.get_gyro()[2]
self.turn(speed, elapse=0)
while abs(z - self._mpu.get_gyro()[2]) < angle:
time.sleep(0.05)
logging.info(self._mpu.get_gyro()[2])
self.stop()
def forward(self, speed=100, elapse=None, distance=None):
self.move(speed=speed, elapse=elapse, distance=distance)
def backward(self, speed=100, elapse=None, distance=None):
self.move(speed=-speed, elapse=elapse, distance=distance)
def left(self, speed=100, elapse=-1):
self.turn(speed=-speed, elapse=elapse)
def right(self, speed=100, elapse=-1):
self.turn(speed=speed, elapse=elapse)
def servo(self, servo, angle):
self._servo_control(self._servos[servo], angle)
def get_sonar_distance(self, sonar_id=0):
return self.sonar[sonar_id].get_distance()
def get_mpu_accel(self, axis=None):
acc = self._mpu.get_acc()
if axis is None:
return acc
else:
return int(acc[axis]*100.0)/100.0
def get_mpu_gyro(self, axis=None):
gyro = self._mpu.get_gyro()
if axis is None:
return gyro
else:
return int(gyro[axis]*100.0)/200.0
def get_mpu_heading(self):
hdg = self._mpu.get_hdg()
return int(hdg)
def get_mpu_temp(self):
temp = self._mpu.get_temp()
return int(temp*100.0)/100.0
def _servo_control(self, pin, angle):
duty = ((angle + 90) * 100 / 180) + 25
self.pi.set_PWM_range(pin, 1000)
self.pi.set_PWM_frequency(pin, 50)
self.pi.set_PWM_dutycycle(pin, duty)
def _dc_enc_motor(self, speed_left=100, speed_right=100, time_elapse=None, target_distance=None):
self._twin_motors_enc.control(power_left=self.get_motor_power(speed_left),
power_right=self.get_motor_power(speed_right),
time_elapse=time_elapse,
target_distance=target_distance)
def stop(self):
self._twin_motors_enc.stop()
def is_moving(self):
return self._twin_motors_enc._is_moving
# Distance travelled getter
def distance(self):
return self._twin_motors_enc.distance()
# CoderBot velocity getter
def speed(self):
return self._twin_motors_enc.speed()
# CoderBot direction getter
def direction(self):
return self._twin_motors_enc.speed()
def set_callback(self, gpio, callback, elapse):
self._cb_elapse[gpio] = elapse * 1000
self._cb[gpio] = callback
self._cb_last_tick[gpio] = 0
def sleep(self, elapse):
logging.debug("sleep: %s", str(elapse))
time.sleep(elapse)
def _cb_button(self, gpio, level, tick):
cb = self._cb.get(gpio)
if cb:
elapse = self._cb_elapse.get(gpio)
if level == 0:
self._cb_last_tick[gpio] = tick
elif tick - self._cb_last_tick[gpio] > elapse:
self._cb_last_tick[gpio] = tick
logging.info("pushed: %d, %d", level, tick)
cb()