Initial push

This commit is contained in:
Ano-sys
2026-02-22 23:30:15 +01:00
commit 7a2d858dfb
58 changed files with 9896 additions and 0 deletions
View File
+107
View File
@@ -0,0 +1,107 @@
import RPi.GPIO as GPIO
import time
class AlphaBot2(object):
def __init__(self,ain1=12,ain2=13,ena=6,bin1=20,bin2=21,enb=26):
self.AIN1 = ain1
self.AIN2 = ain2
self.BIN1 = bin1
self.BIN2 = bin2
self.ENA = ena
self.ENB = enb
self.PA = 50
self.PB = 50
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(self.AIN1,GPIO.OUT)
GPIO.setup(self.AIN2,GPIO.OUT)
GPIO.setup(self.BIN1,GPIO.OUT)
GPIO.setup(self.BIN2,GPIO.OUT)
GPIO.setup(self.ENA,GPIO.OUT)
GPIO.setup(self.ENB,GPIO.OUT)
self.PWMA = GPIO.PWM(self.ENA,500)
self.PWMB = GPIO.PWM(self.ENB,500)
self.PWMA.start(self.PA)
self.PWMB.start(self.PB)
self.stop()
def forward(self):
self.PWMA.ChangeDutyCycle(self.PA)
self.PWMB.ChangeDutyCycle(self.PB)
GPIO.output(self.AIN1,GPIO.LOW)
GPIO.output(self.AIN2,GPIO.HIGH)
GPIO.output(self.BIN1,GPIO.LOW)
GPIO.output(self.BIN2,GPIO.HIGH)
def stop(self):
self.PWMA.ChangeDutyCycle(0)
self.PWMB.ChangeDutyCycle(0)
GPIO.output(self.AIN1,GPIO.LOW)
GPIO.output(self.AIN2,GPIO.LOW)
GPIO.output(self.BIN1,GPIO.LOW)
GPIO.output(self.BIN2,GPIO.LOW)
def backward(self):
self.PWMA.ChangeDutyCycle(self.PA)
self.PWMB.ChangeDutyCycle(self.PB)
GPIO.output(self.AIN1,GPIO.HIGH)
GPIO.output(self.AIN2,GPIO.LOW)
GPIO.output(self.BIN1,GPIO.HIGH)
GPIO.output(self.BIN2,GPIO.LOW)
def left(self):
self.PWMA.ChangeDutyCycle(30)
self.PWMB.ChangeDutyCycle(30)
GPIO.output(self.AIN1,GPIO.HIGH)
GPIO.output(self.AIN2,GPIO.LOW)
GPIO.output(self.BIN1,GPIO.LOW)
GPIO.output(self.BIN2,GPIO.HIGH)
def right(self):
self.PWMA.ChangeDutyCycle(30)
self.PWMB.ChangeDutyCycle(30)
GPIO.output(self.AIN1,GPIO.LOW)
GPIO.output(self.AIN2,GPIO.HIGH)
GPIO.output(self.BIN1,GPIO.HIGH)
GPIO.output(self.BIN2,GPIO.LOW)
def setPWMA(self,value):
self.PA = value
self.PWMA.ChangeDutyCycle(self.PA)
def setPWMB(self,value):
self.PB = value
self.PWMB.ChangeDutyCycle(self.PB)
def setMotor(self, left, right):
if((right >= 0) and (right <= 100)):
GPIO.output(self.AIN1,GPIO.HIGH)
GPIO.output(self.AIN2,GPIO.LOW)
self.PWMA.ChangeDutyCycle(right)
elif((right < 0) and (right >= -100)):
GPIO.output(self.AIN1,GPIO.LOW)
GPIO.output(self.AIN2,GPIO.HIGH)
self.PWMA.ChangeDutyCycle(0 - right)
if((left >= 0) and (left <= 100)):
GPIO.output(self.BIN1,GPIO.HIGH)
GPIO.output(self.BIN2,GPIO.LOW)
self.PWMB.ChangeDutyCycle(left)
elif((left < 0) and (left >= -100)):
GPIO.output(self.BIN1,GPIO.LOW)
GPIO.output(self.BIN2,GPIO.HIGH)
self.PWMB.ChangeDutyCycle(0 - left)
if __name__=='__main__':
Ab = AlphaBot2()
Ab.forward()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
GPIO.cleanup()
+166
View File
@@ -0,0 +1,166 @@
import RPi.GPIO as GPIO
import threading
import time
import subprocess
import os, signal, sys
from pid_line_follow1 import *
from AlphaBot2 import AlphaBot2
from TRSensors import TRSensor
Ab = AlphaBot2()
IR = 17
PWM = 50
'''def move_and_stop(func, duration=0.5):
func()
time.sleep(duration)
Ab.stop()'''
def setup_gpio():
"""Initialisiert die GPIO-Pins."""
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(IR, GPIO.IN)
def getkey():
if GPIO.input(IR) == 0:
count = 0
while GPIO.input(IR) == 0 and count < 200: #9ms
count += 1
time.sleep(0.00006)
if(count < 10):
#print("None returned")
return None
count = 0
while GPIO.input(IR) == 1 and count < 80: #4.5ms
count += 1
time.sleep(0.00006)
#print("High-Burst-Length:", count)
if (count < 20):
#print("repeat returned")
return "repeat"
idx = 0
cnt = 0
data = [0,0,0,0]
for i in range(0,32):
count = 0
while GPIO.input(IR) == 0 and count < 15: #0.56ms
count += 1
time.sleep(0.00006)
count = 0
while GPIO.input(IR) == 1 and count < 40: #0: 0.56mx
count += 1 #1: 1.69ms
time.sleep(0.00006)
if count > 7:
data[idx] |= 1<<cnt
if cnt == 7:
cnt = 0
idx += 1
else:
cnt += 1
# print data
if data[0]+data[1] == 0xFF and data[2]+data[3] == 0xFF: #check
print("OK")
return data[2]
return None
def stop_listener():
"""
Deine RemoteAPI muss hier so lange blocken, bis
ein KeyEvent eintrifft und dir den Code 0x43 liefert.
"""
while True:
code = getkey()
if code == 0x43:
os.kill(os.getpid(), signal.SIGINT)
return
setup_gpio()
print('IRremote Test Start ...')
Ab.stop()
last_key = 0
last_key_press_time = 0
current_action = None
try:
while True:
key = getkey()
# 0x43 == line_follow
if key == "repeat":
key = last_key
last_key_press_time = time.time()
elif key is not None:
print("getkey:")
print(hex(key))
last_key = key
last_key_press_time = time.time()
if key is not None:
last_key_press_time = time.time()
if key == 0x18 and current_action != "forward":
Ab.forward()
print("forward")
current_action = "forward"
elif key == 0x08 and current_action != "left":
Ab.left()
print("left")
current_action = "left"
elif key == 0x1c and current_action != "stop":
Ab.stop()
print("stop")
current_action = "stop"
elif key == 0x5a and current_action != "right":
Ab.right()
print("right")
current_action = "right"
elif key == 0x52 and current_action != "backward":
Ab.backward()
print("backward")
current_action = "backward"
elif key == 0x15:
if(PWM + 10 < 101):
PWM += 10
Ab.setPWMA(PWM)
Ab.setPWMB(PWM)
print("PWM:", PWM)
elif key == 0x07:
if(PWM - 10 > -1):
PWM -= 10
Ab.setPWMA(PWM)
Ab.setPWMB(PWM)
print("PWM:", PWM)
elif key == 0x43:
tr = TRSensor()
running = threading.Event()
process = subprocess.Popen(
["python", "pid_line_follow1.py"],
)
# follow_thread = threading.Thread(target=follow, args=(tr, Ab, running), daemon=True)
# follow_thread.start()
key = getkey()
while(key != 0x43):
key = getkey()
# running.set()
process.terminate()
Ab.setPWMA(PWM)
Ab.setPWMB(PWM)
time.sleep(0.05)
else:
if time.time() - last_key_press_time > 0.1 and current_action != "stop":
Ab.stop()
print("stopping bc no key is being pressed")
current_action = "stop"
except KeyboardInterrupt:
GPIO.cleanup()
+153
View File
@@ -0,0 +1,153 @@
# Robot
## Cam-stream
Files:
- cam_stream.py
### Ziel
Kamera läuft und schickt Bilder via seriell an den ESP.
### Voraussetzung
- Die Kamera 🤡
- ESP am Serial-Port `/dev/ttyACM1` (115200 Baud)
### Umsetzung
In Endlosschleife wird ein 640x480-Graustufen-Frame von der Kamera gelesen und als JPEG komprimiert bis die Größe unter 1200 Bytes liegt.\
Danach wird der Frame in JSON-Format über seriell an den ESP gesendet.\
Sollte es zu einem Verbindungsabbruch zwischen Pi und ESP kommen wird automatisch so lange eine Neuverbindung versucht, bis diese Verbindung wieder steht.
### Probleme während der Bearbeitung und ihre Lösungen
Die Kamera wurde nicht konstant erkannt (beim Suchen der Kamera wurde sie manchmal gefunden und manchmal nicht, meistens nicht).\
Eine Neuinstallation von Raspbian hat das Problem gelöst, allerdings wurde diesmal die full-version installiert.
-----------------------
## Line follow
Files:
- AlphaBot2.py
- TRSensors.py
- line_follow.py
### Ziel
Roboter soll in der Lage sein einer Linie auf dem Boden zu folgen,
ohne diesen zu verlieren
### Voraussetzung
Linienbreite deckt 3 der 5 Sensoren ab (die mittleren), äußeren Sensoren müssen den boden sehen.
### Umsetzung
#### erste Idee: (Verworfen)
Von 5 Sensoren müssen die mittleren 3 immer die Linie sehen.\
Mithilfe der äußeren Sensoren werden Korrekturen durchgeführt, damit der Roboter auf der Linie bleibt.
#### zweite Idee:
Es müssen wieder die mittleren 3 Sensoren auf der Linie sein.\
Diesmal wird die Korrektur aber von Sensor 1 und Sensor 3 durchgeführt (Index beginnt bei 0).\
Mithilfe der äußeren Sensoren, soll entschieden werden, ob eine Abzweigung existiert und falls sie existiert wird eine stärkere Korrektur durchgeführt damit der Roboter sich richtig dreht.
### Probleme während der Bearbeitung und ihre Lösungen
#### erste Idee:
Der Roboter hat zu sehr gezappelt und es war schwer zu unterscheiden ob nur leicht korrigiert werden musste oder ob die Linie die Richtung gewechselt hat.
Das Problem sollte durch die zweite Idee gelöst werden.
#### zweite Idee: (Idee wurde vereinfacht auf nur starke Kurven = Roboter muss eine 90° Drehung machen um zu folgen)
Der Roboter interpretiert, je nach Untergrund, Kurven falsch.\
Obwohl keine Kurve vorhanden ist biegt er doch ab.\
Wenn eine Kurve vorhanden ist nimmt er diese nicht korrekt war und bleibt am Ende der Linie stehen.
-------------------
## remote steering
Files:
- AlphaBot2.py
- IRremote.py
### Ziel
Steuerung des kompletten Roboters über Fernbedienung.
Möglich machen:
- Roboter bewegen
- Linienfolgen starten und stoppen + Kalibrierung manuell starten
- (Kameraausrichtung ermöglichen)
### Voraussetzung
Die Fernbedienung 🤡
### Umsetzung Buttons
- play/pause = starten bzw. stoppen des line follows
- 2 = geradeaus fahren
- 4 = nach links drehen um die eigene Achse
- 6 = nach rechts drehen um die eigene Achse
- 8 = rückwärts fahren
### Probleme während der Bearbeitung und ihre Lösungen
Keine Probleme
-------------------
## Daemon
Files: Kein
### Ziel
Automatisches starten des Kamerastreams,
sowie die Steuerung über eine Fernbedienung.
### Umsetzung
1. **Service-Datei anlegen**
Erstelle unter `/etc/systemd/system/` eine Datei namens `<service_name>.service` mit folgendem Inhalt:
```ini
[Unit]
Description=<Kurzbeschreibung des Dienstes>
After=network.target
[Service]
Type=simple
User=<system_user>
WorkingDirectory=<Arbeitsverzeichnis>
ExecStart=<Pfad zu Interpreter> <Pfad zum Skript>
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
```
2. **Systemd neu einlesen**
``sudo systemctl daemon-reload``
3. **Dienst aktivierten (Boot-Start)**
``sudo systemctl enable <service_name>.service``
4. **Dienst sofort starten**
``sudo systemctl start <service_name>.service``
5. **Status und Logs prüfen**
- Status anzeigen:
``sudo systemctl status <service_name>.service``
- Live-Logs:
``sudo journalctl -u <service_name>.service -f``
### Probleme während der Bearbeitung und ihre Lösungen
+200
View File
@@ -0,0 +1,200 @@
#!/usr/bin/python
# -*- coding:utf-8 -*-
import RPi.GPIO as GPIO
import time
CS = 5
Clock = 25
Address = 24
DataOut = 23
Button = 7
class TRSensor(object):
def __init__(self,numSensors = 5):
self.numSensors = numSensors
self.calibratedMin = [0] * self.numSensors
self.calibratedMax = [1023] * self.numSensors
self.last_value = 0
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(Clock,GPIO.OUT)
GPIO.setup(Address,GPIO.OUT)
GPIO.setup(CS,GPIO.OUT)
GPIO.setup(DataOut,GPIO.IN,GPIO.PUD_UP)
GPIO.setup(Button,GPIO.IN,GPIO.PUD_UP)
"""
Reads the sensor values into an array. There *MUST* be space
for as many values as there were sensors specified in the constructor.
Example usage:
unsigned int sensor_values[8];
sensors.read(sensor_values);
The values returned are a measure of the reflectance in abstract units,
with higher values corresponding to lower reflectance (e.g. a black
surface or a void).
"""
def AnalogRead(self):
value = [0]*(self.numSensors+1)
#Read Channel0~channel6 AD value
for j in range(0,self.numSensors+1):
GPIO.output(CS, GPIO.LOW)
for i in range(0,8):
#sent 8-bit Address
if i<4:
if(((j) >> (3 - i)) & 0x01):
GPIO.output(Address,GPIO.HIGH)
else:
GPIO.output(Address,GPIO.LOW)
else:
GPIO.output(Address,GPIO.LOW)
#read MSB 4-bit data
value[j] <<= 1
if(GPIO.input(DataOut)):
value[j] |= 0x01
GPIO.output(Clock,GPIO.HIGH)
GPIO.output(Clock,GPIO.LOW)
for i in range(0,4):
#read LSB 8-bit data
value[j] <<= 1
if(GPIO.input(DataOut)):
value[j] |= 0x01
GPIO.output(Clock,GPIO.HIGH)
GPIO.output(Clock,GPIO.LOW)
#no mean ,just delay
# for i in range(0,6):
# GPIO.output(Clock,GPIO.HIGH)
# GPIO.output(Clock,GPIO.LOW)
time.sleep(0.0001)
GPIO.output(CS,GPIO.HIGH)
for i in range(0,6):
value[i] >>= 2
# print (value[1:])
return value[1:]
"""
Reads the sensors 10 times and uses the results for
calibration. The sensor values are not returned; instead, the
maximum and minimum values found over time are stored internally
and used for the readCalibrated() method.
"""
def calibrate(self):
max_sensor_values = [0]*self.numSensors
min_sensor_values = [0]*self.numSensors
for j in range(0,10):
sensor_values = self.AnalogRead()
for i in range(0,self.numSensors):
# set the max we found THIS time
if((j == 0) or max_sensor_values[i] < sensor_values[i]):
max_sensor_values[i] = sensor_values[i]
# set the min we found THIS time
if((j == 0) or min_sensor_values[i] > sensor_values[i]):
min_sensor_values[i] = sensor_values[i]
# record the min and max calibration values
for i in range(0,self.numSensors):
if(min_sensor_values[i] > self.calibratedMin[i]):
self.calibratedMin[i] = min_sensor_values[i]
if(max_sensor_values[i] < self.calibratedMax[i]):
self.calibratedMax[i] = max_sensor_values[i]
"""
Returns values calibrated to a value between 0 and 1000, where
0 corresponds to the minimum value read by calibrate() and 1000
corresponds to the maximum value. Calibration values are
stored separately for each sensor, so that differences in the
sensors are accounted for automatically.
"""
def readCalibrated(self):
value = 0
#read the needed values
sensor_values = self.AnalogRead()
for i in range (0,self.numSensors):
denominator = self.calibratedMax[i] - self.calibratedMin[i]
if(denominator != 0):
value = (sensor_values[i] - self.calibratedMin[i])* 1000 / denominator
if(value < 0):
value = 0
elif(value > 1000):
value = 1000
sensor_values[i] = value
#print("readCalibrated",sensor_values)
return sensor_values
"""
Operates the same as read calibrated, but also returns an
estimated position of the robot with respect to a line. The
estimate is made using a weighted average of the sensor indices
multiplied by 1000, so that a return value of 0 indicates that
the line is directly below sensor 0, a return value of 1000
indicates that the line is directly below sensor 1, 2000
indicates that it's below sensor 2000, etc. Intermediate
values indicate that the line is between two sensors. The
formula is:
0*value0 + 1000*value1 + 2000*value2 + ...
--------------------------------------------
value0 + value1 + value2 + ...
By default, this function assumes a dark line (high values)
surrounded by white (low values). If your line is light on
black, set the optional second argument white_line to true. In
this case, each sensor value will be replaced by (1000-value)
before the averaging.
"""
def readLine(self, white_line = 0):
sensor_values = self.readCalibrated()
avg = 0
sum = 0
on_line = 0
for i in range(0,self.numSensors):
value = sensor_values[i]
if(white_line):
value = 1000-value
# keep track of whether we see the line at all
if(value > 200):
on_line = 1
# only average in values that are above a noise threshold
if(value > 50):
avg += value * (i * 1000); # this is for the weighted total,
sum += value; #this is for the denominator
if(on_line != 1):
# If it last read to the left of center, return 0.
if(self.last_value < (self.numSensors - 1)*1000/2):
#print("left")
self.last_value = 0
# If it last read to the right of center, return the max.
else:
#print("right")
self.last_value = (self.numSensors - 1)*1000
else:
self.last_value = avg/sum
return self.last_value,sensor_values
# Simple example prints accel/mag data once per second:
if __name__ == '__main__':
TR = TRSensor()
print("TRSensor Example")
while True:
try:
print(TR.AnalogRead())
time.sleep(0.2)
except KeyboardInterrupt:
break
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+98
View File
@@ -0,0 +1,98 @@
"""
Requirements Step by Step:
sudo apt update
sudo apt install libjpeg62-turbo-dev python3-prctl python3-picamera2 python3-libcamera libcamera-apps libcamera-dev
python3 -m venv --system-site-packages env
source env/bin/activate
pip install flask pillow
"""
from flask import Flask, Response
from picamera2 import Picamera2
from PIL import Image
import io
import base64
import serial
import time
import cv2
s = serial.Serial('/dev/ttyACM1', 115200, timeout=1) # serielle verbindung zum ESP aufbauen
time.sleep(2)
'''
Kamera Konfigurieren mit einem Format von 640x480
'''
picam2 = Picamera2()
config = picam2.create_preview_configuration(main={"size": (640, 480)})
picam2.configure(config)
picam2.start()
MAX_RAW = 1200
MAX_B64 = 1200
# TODO: map image to MAX_B64
def encode_image_to_b64_with_limit(img: Image.Image, max_b64: int = 1024, scale_step: float = 0.9, initial_quality: int = 30, min_quality: int = 5) -> str:
quality = initial_quality
working_img = img.copy()
while True:
buf = io.BytesIO()
working_img.save(buf, format="JPEG", quality=quality, optimize=True)
jpeg_bytes = buf.getvalue()
b64 = base64.b64encode(jpeg_bytes).decode("utf-8")
if len(b64) <= max_b64 or (working_img.width < 2 or working_img.height < 2):
return b64
# if len(jpeg_bytes) <= MAX_RAW:
# return jpeg_bytes
new_w = max(1, int(working_img.width * scale_step))
new_h = max(1, int(working_img.height * scale_step))
working_img = working_img.resize((new_w, new_h), Image.LANCZOS)
if quality > min_quality:
quality = max(min_quality, int(quality * scale_step))
def init_serial():
global s
s = None
while s is None:
try:
s = serial.Serial('/dev/ttyACM1', 115200, timeout=1)
except Exception:
time.sleep(1)
continue
def gen():
global s
while True:
arr = picam2.capture_array("main") # Einen frame holen
img = Image.fromarray(arr).convert("L") # Bild in Graustufen umwandeln
# TODO: skip config with 640x480
img = img.resize((640, 480), Image.LANCZOS)
b64 = encode_image_to_b64_with_limit(img, MAX_B64) # Bildqualität reduzieren bis es eine Größe von maximal MAX_B64 (1200) hat
print("Image_b64 size: " + str(len(b64)))
try:
s.write(("{\"type\":\"image\",\"data\":\"" + b64 + "\"}\n").encode("utf-8")) # Frame als JSON-String an den ESP verschicken
# s.write((b64 + "\n").encode("utf-8"))
except Exception:
'''
War das schreiben über seriell nicht möglich wird ein erneuter Verbindungsaufbau versucht bis die Verbindung wieder steht.
Es wird davon ausgegangen das wir die Verbindung verloren haben und deswegen das schreiben nicht möglich war.
'''
init_serial()
time.sleep(0.1)
if __name__ == "__main__":
# app.run(host="0.0.0.0", port=1234, threaded=True)
try:
gen()
except KeyboardInterrupt:
serial.close()
print("Uebertragung beenden")
+177
View File
@@ -0,0 +1,177 @@
#!/usr/bin/env python3
"""
Line following robot for Raspberry Pi 4 using AlphaBot2 and TRSensor
Ohne LED-Visualisierung
"""
import time
import RPi.GPIO as GPIO
from AlphaBot2 import AlphaBot2
from TRSensors import TRSensor
MAX_SPEED = 25
CORRECTION_STRENGTH = 8
EXTRA_CURVE_CORRECTION = 2
LINE_DETECTED_THRESHOLD = 650
# Liste für gleitenden Durchschnitt
last_outer = []
def setup_gpio():
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
def calibrate_sensors(tr, robot):
print("Auto-calibrating sensors...")
for i in range(5):
tr.calibratedMin[i] = 1023
tr.calibratedMax[i] = 0
for _ in range(3):
robot.setPWMA(20)
robot.setPWMB(15)
robot.forward()
for _ in range(30):
raw = tr.AnalogRead()
for i in range(5):
tr.calibratedMin[i] = min(tr.calibratedMin[i], raw[i])
tr.calibratedMax[i] = max(tr.calibratedMax[i], raw[i])
time.sleep(0.02)
robot.backward()
for _ in range(30):
raw = tr.AnalogRead()
for i in range(5):
tr.calibratedMin[i] = min(tr.calibratedMin[i], raw[i])
tr.calibratedMax[i] = max(tr.calibratedMax[i], raw[i])
time.sleep(0.02)
robot.stop()
print("Calibration complete.")
print(" Min:", tr.calibratedMin)
print(" Max:", tr.calibratedMax)
def detect_line_type(tr):
sensor_values = tr.readCalibrated()
center_avg = sum(sensor_values[1:4]) / 3
outer_avg = (sensor_values[0] + sensor_values[4]) / 2
white_line = outer_avg < center_avg
print("Detected line type:", "WHITE on BLACK" if white_line else "BLACK on WHITE")
return white_line
def compute_correction(sensors):
left = sensors[1]
right = sensors[3]
correction = 0
if abs(left - right) < 50:
correction = 0
elif left < LINE_DETECTED_THRESHOLD and right > LINE_DETECTED_THRESHOLD:
correction = -CORRECTION_STRENGTH
elif right < LINE_DETECTED_THRESHOLD and left > LINE_DETECTED_THRESHOLD:
correction = CORRECTION_STRENGTH
if sensors[0] < LINE_DETECTED_THRESHOLD:
correction -= EXTRA_CURVE_CORRECTION
elif sensors[4] < LINE_DETECTED_THRESHOLD:
correction += EXTRA_CURVE_CORRECTION
return correction
def perform_turn_if_needed(sensors, robot, position):
global last_outer
l = sensors[0]
r = sensors[4]
last_outer.append((l, r))
if len(last_outer) > 5:
last_outer.pop(0)
avg_l = sum(x for x, _ in last_outer) / len(last_outer)
avg_r = sum(y for _, y in last_outer) / len(last_outer)
delta = avg_r - avg_l
print(f"Turn-Check: AvgL={avg_l:.0f}, AvgR={avg_r:.0f}, Δ={delta:.0f}, pos={position:.1f}")
if not (1500 <= position <= 2500):
return False
if avg_l > 800 and avg_r > 800:
return False
if avg_r > 800 and avg_l < 500 and delta > 400:
print("→ 90° right-curve detected")
robot.stop()
time.sleep(0.1)
robot.right()
time.sleep(0.3)
robot.forward()
time.sleep(0.35)
return True
elif avg_l > 800 and avg_r < 500 and delta < -600 and sensors[2] < LINE_DETECTED_THRESHOLD:
print("← 90° left-curve detected")
robot.stop()
time.sleep(0.1)
robot.left()
time.sleep(0.3)
robot.forward()
time.sleep(0.35)
return True
return False
def line_follow_loop(tr, robot, white_line):
robot.forward()
while True:
position, sensors = tr.readLine(white_line=white_line)
print(f"Sensors: {sensors}, pos={position:.1f}")
if perform_turn_if_needed(sensors, robot, position):
robot.forward()
time.sleep(0.35)
robot.stop()
break
if all(s > LINE_DETECTED_THRESHOLD for s in sensors):
time.sleep(0.05)
robot.backward()
time.sleep(0.35)
robot.stop()
continue
correction = compute_correction(sensors)
left_speed = min(MAX_SPEED, max(0, MAX_SPEED + correction))
right_speed = min(MAX_SPEED, max(0, MAX_SPEED - correction))
robot.setPWMA(int(left_speed))
robot.setPWMB(int(right_speed))
time.sleep(0.02)
def line_follow(tr=None, robot=None):
if tr is None or robot is None:
setup_gpio()
robot = AlphaBot2()
tr = TRSensor()
robot.stop()
calibrate_sensors(tr, robot)
white_line = detect_line_type(tr)
print("Starting line follow...")
try:
line_follow_loop(tr, robot, white_line)
except KeyboardInterrupt:
print("Stopping and cleaning up...")
robot.stop()
GPIO.cleanup()
if __name__ == "__main__":
setup_gpio()
robot = AlphaBot2()
tr = TRSensor()
robot.stop()
calibrate_sensors(tr, robot)
input("Press Enter to continue...")
line_follow(tr, robot)
+119
View File
@@ -0,0 +1,119 @@
from enum import Enum
import enum
import time
import RPi.GPIO as GPIO
from AlphaBot2 import AlphaBot2
from TRSensors import TRSensor
import datetime
class LINE_MODE(Enum):
UNSET = -1
WHITE_LINE_MODE = 0
BLACK_LINE_MODE = 1
class STATE(Enum):
INIT_SUCCESS = 0
INIT_FAIL = -1
BTN_PIN = 7
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(BTN_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
MAX_SPEED = 40
OVERDRIVE_SPEED = 50
MAX_REVERSE_SPEED = 20
MAX_CORRECTION_STRENGTH = 5
LineColorDescriptor: LINE_MODE = LINE_MODE.UNSET
CurrentCorrectionStrength: int = 1
logging: bool = True
LineSearchingDirection: int = 0 # -1 -> left, 1 -> right
TR = TRSensor()
Ab = AlphaBot2()
Ab.stop()
def log(msg: str) -> None:
if not logging: return;
print("-> Logger")
print(f"Timestamp: {datetime.datetime.now()}")
print(f"Message: {msg}")
print("<- Logger")
def LineModeBoolConverter(mode: LINE_MODE) -> bool:
return True if mode == LINE_MODE.WHITE_LINE_MODE else False
def setup() -> int:
log("Initializing TR!")
for i in range(1, 4):
TR.calibratedMin[i] = 1023
TR.calibratedMax[i] = 0
log("Calibration sensors!")
for _ in range(100):
values = TR.AnalogRead()
for i in range(1, 5): # Index 1 bis 3 → Sensor 24
TR.calibratedMin[i] = min(TR.calibratedMin[i], values[i])
TR.calibratedMax[i] = max(TR.calibratedMax[i], values[i])
Ab.stop()
time.sleep(0.5)
# Determine line color
sensor_values = TR.readCalibrated()
center_avg = sum(sensor_values[1:4]) / 3
outer_avg = (sensor_values[0] + sensor_values[4]) / 2
LineColorDescriptor = LINE_MODE.WHITE_LINE_MODE if outer_avg < center_avg else LINE_MODE.BLACK_LINE_MODE # If outer sensors are dark → white line on dark
log("Detected line type: " + "WHITE on BLACK" if LineColorDescriptor == LINE_MODE.WHITE_LINE_MODE else "BLACK on WHITE")
# Wait for button press to start
print("Press the button to start line following")
while GPIO.input(BTN_PIN):
time.sleep(0.1)
LineSearchingDirection = 0
log("Setup done!")
return 0;
def loop() -> None:
Ab.forward()
while True:
position, sensors = TR.readLine(white_line=LineModeBoolConverter(LineColorDescriptor))
background_level = (sensors[0] + sensors[1]) / 2
correction_strength = (background_level / 1023.0) * 2 * MAX_CORRECTION_STRENGTH - MAX_CORRECTION_STRENGTH
if sensors[2] < background_level:
Ab.backward()
Ab.setPWMA(MAX_REVERSE_SPEED)
Ab.setPWMB(MAX_REVERSE_SPEED)
while True:
position, sensors = TR.readLine(white_line=LineModeBoolConverter(LineColorDescriptor))
if sensors[2] >= background_level:
break
Ab.forward()
continue
if sensors[1] < background_level:
Ab.setPWMA(OVERDRIVE_SPEED)
Ab.setPWMB(MAX_SPEED)
elif sensors[3] < background_level:
Ab.setPWMB(OVERDRIVE_SPEED)
Ab.setPWMA(MAX_SPEED)
else:
if correction_strength < 0:
Ab.setPWMB(int(MAX_SPEED + correction_strength)) # decrease power from right motor if sensor indicates missing line left
elif correction_strength > 0:
Ab.setPWMA(int(MAX_SPEED - correction_strength))
# time.sleep(0.01); # we drive fast so handle steering as fast as possible
if __name__ == '__main__':
if setup():
loop()
else:
log("Setup failed!")
+179
View File
@@ -0,0 +1,179 @@
#!/usr/bin/env python3
"""
Linienfolgeroboter für Raspberry Pi 4 mit AlphaBot2 und TRSensor
Verwendet einen PID-Regler und eine Suchfunktion bei Linienverlust.
(Version 3: Korrektur für Stopp nach Liniensuche)
"""
import time
import RPi.GPIO as GPIO
from AlphaBot2 import AlphaBot2
from TRSensors import TRSensor
# Geschwindigkeits- und PID-Einstellungen
MAX_SPEED = 40 # 50
BASE_SPEED = 30 # 40
SEARCH_SPEED = 30 # Geschwindigkeit während der Liniensuche
# PID-Konstanten - DIESE MÜSSEN EVENTUELL ANGEPASST WERDEN!
Kp = 0.045 # 0.025
Ki = 0.0001 # 0.0005
Kd = 0.3
# Zielwert der Sensoren (Mitte der Linie)
TARGET_POSITION = 2000
# Schwelle, ab der ein Sensor die Linie als "verloren" betrachtet
LINE_LOST_THRESHOLD = 950
# Zeitlimit für die Suche in Sekunden
SEARCH_TIMEOUT = 3.0
IR = 17
def setup_gpio():
"""Initialisiert die GPIO-Pins."""
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(IR, GPIO.IN)
def calibrate_sensors(tr, robot):
"""Führt eine automatische Kalibrierung der Sensoren durch."""
print("Starte Sensor-Kalibrierung...")
robot.setPWMA(20)
robot.setPWMB(20)
# Drehung zur Kalibrierung auf weißem und schwarzem Untergrund
for _ in range(2):
robot.right()
for _ in range(50):
tr.calibrate()
time.sleep(0.02)
robot.left()
for _ in range(100):
tr.calibrate()
time.sleep(0.02)
robot.right()
for _ in range(50):
tr.calibrate()
time.sleep(0.02)
robot.stop()
print("Kalibrierung abgeschlossen.")
print(" Min:", tr.calibratedMin)
print(" Max:", tr.calibratedMax)
def search_for_line(robot, tr, last_error):
"""
Sucht die Linie, indem der Roboter sich in die Richtung dreht,
in der die Linie zuletzt war.
"""
print("Linie verloren! Suche...")
robot.stop()
time.sleep(0.1)
# Setze zuerst die Geschwindigkeit für beide Motoren
robot.setPWMA(SEARCH_SPEED)
robot.setPWMB(SEARCH_SPEED)
# Bestimme dann die Suchrichtung
if last_error > 0:
print("Suche nach links...")
robot.left()
else:
print("Suche nach rechts...")
robot.right()
start_time = time.time()
while time.time() - start_time < SEARCH_TIMEOUT:
# Lies kontinuierlich die Sensoren, während du dich drehst
_, sensors = tr.readLine(white_line=False)
# Prüfe, ob einer der Sensoren die Linie wieder sieht
if any(s < LINE_LOST_THRESHOLD for s in sensors):
print("Linie wiedergefunden!")
robot.stop()
time.sleep(0.1)
return True # Suche war erfolgreich
time.sleep(0.01)
# Schleife beendet, ohne die Linie zu finden (Timeout)
print(f"Suche nach {SEARCH_TIMEOUT}s erfolglos. Roboter stoppt.")
robot.stop()
return False # Suche war erfolglos
def line_follow_loop(tr, robot):
"""Hauptschleife für die Linienverfolgung mittels PID-Regelung."""
# Setzt die anfängliche Richtung auf vorwärts
robot.forward()
last_error = 0
integral = 0
while True:
# Lese die Position der Linie. False für schwarze Linie auf weißem Grund.
position, sensors = tr.readLine(white_line=False)
# Wenn alle Sensoren weiß sehen, starte die Liniensuche
if all(s > LINE_LOST_THRESHOLD for s in sensors):
search_successful = search_for_line(robot, tr, last_error)
if search_successful:
# Setze PID-Werte zurück, um einen Sprung zu vermeiden
last_error = 0
integral = 0
# WICHTIG: Setze die Richtung wieder auf vorwärts, bevor die Schleife weitergeht
robot.forward()
continue
else:
# Beende die Hauptschleife, wenn die Suche fehlschlägt
break
# --- PID-Berechnung ---
error = position - TARGET_POSITION
integral += error
derivative = error - last_error
last_error = error
correction = (Kp * error) + (Ki * integral) + (Kd * derivative)
# Anpassung der Motor-Geschwindigkeiten
left_speed = BASE_SPEED + correction
right_speed = BASE_SPEED - correction
# Geschwindigkeiten auf den gültigen Bereich begrenzen
left_speed = max(0, min(MAX_SPEED, left_speed))
right_speed = max(0, min(MAX_SPEED, right_speed))
# print(f"Pos: {position:.0f} | Err: {error:.0f} | Corr: {correction:.2f} | L: {left_speed:.0f} | R: {right_speed:.0f}")
robot.setPWMA(int(left_speed))
robot.setPWMB(int(right_speed))
time.sleep(0.01)
def follow(tr , robot):
"""Hauptfunktion des Programms."""
#robot = AlphaBot2()
#tr = TRSensor()
#robot.stop()
try:
calibrate_sensors(tr, robot)
print("Kalibrierung abgeschlossen")
time.sleep(5.0)
#input("Kalibrierung abgeschlossen. Drücke Enter zum Starten...")
line_follow_loop(tr, robot)
except KeyboardInterrupt:
print("\nProgramm gestoppt.")
finally:
robot.stop()
if __name__ == "__main__":
setup_gpio()
robot = AlphaBot2()
tr = TRSensor()
robot.stop()
follow(tr, robot)
print("Räume GPIO auf.")
GPIO.cleanup()