"""Das Modul regelt die serielle Kommunikation mit dem CNC-Controller.
"""
import hilfsfunktionen
import serial
import time
import threading
[Doku]class Controller:
[Doku] def __init__(self, grundeinstellungen):
"""Der Konstruktor wird aufgerufen, wenn ein Controller-Objekt erzeugt
wird und speichert unter Anderem Grundeinstellungen wie
Verbindungsparameter und Korrekturfaktoren in lokale Variablen.
In ihm wird auserdem die Methode *verbindung_initialisieren()*
aufgerufen, welche den Handshake mit dem Controller durchführt.
Args:
grundeinstellungen (dict): Aus dem Config-File eingelesene
Grundeinstellungen des Roboters.
"""
# Threading Lock Objekt erzeugen
self.codelock = threading.Lock()
# Grundeinstellungen einlesen
self.basiseinstellungen = grundeinstellungen
# Verbindungseinstellungen
self.schnittstelle = grundeinstellungen['usb_interface']
self.baudrate = int(grundeinstellungen['usb_baudrate'])
self.timeout = int(grundeinstellungen['usb_timeout'])
# Drehzahlkorrektur
self.steigung = grundeinstellungen['spindeldrehzahl_steigung']
self.offset = grundeinstellungen['spindeldrehzahl_offset']
# Serielle Verbindung instanziieren
self.serielle_verbindung = serial.Serial(
self.schnittstelle, self.baudrate, timeout=self.timeout)
# Verbindung zum Controller checken
self.verbindung_initialisieren()
# Variableninitialisierung
self.x_position = 0
self.y_position = 0
self.z_position = 0
self.cmd = ""
self.controller_status = ""
self.antwort = ""
# Einen Befehl an den CNC-Controller senden
[Doku] def gcode_senden(self, cmd):
"""Übernimmt einen G-CODE und sendet diesen über eine serielle
Verbindung (USB) an den CNC-Controller.
Examples:
Beispiel für einen G-CODE: ``G01 X120.5 Y20 Z-3.25 F950``
Note:
Um einen Mehrfachzugriff (durch Threads) auf die Serielle-
Schnittstelle zu vermeiden, wird der entsprechende Codeteil mit
``lock.aquire()`` gesperrt und mit ``release()`` wieder freigegeben.
Args:
cmd (str): Enthält den zu sendenden G-CODE.
Returns:
antwort (str): Gibt die Antwort des Controllers zurück.
"""
try:
# Programmabschnitt für andere Threads sperren um Mehrfachzugriff
# auf die serielle Schnittstelle zu verhindern
self.codelock.acquire()
# Empfangsbuffer leeren
self.serielle_verbindung.flushInput()
# Carriage Return und Linefeed anhängen, damit der Controller das
# Ende des Befehls erkennt
cmd += '\n\r'
# Befehlsstring binär codieren
cmd = cmd.encode()
# Daten senden
self.serielle_verbindung.write(cmd)
# Kurz warten bevor die Antwort gelesen wird
time.sleep(0.01)
# Abfragen wieviel Bytes zu erwarten sind
bytesleft = self.serielle_verbindung.inWaiting()
antwort = ""
if self.serielle_verbindung.inWaiting() > 0:
antwort = self.serielle_verbindung.read(bytesleft)
# print(antwort)
# Programmabschnitt für andere Threads wieder freigeben
self.codelock.release()
# Antwort des Controllers zurückgeben
return antwort
except:
print("Fehler beim Senden/Empfangen über die serielle Verbindung!")
antwort = "Error"
return antwort
[Doku] def verbindung_initialisieren(self):
"""Führt den Handshake mit dem CNC-Controller durch.
Note:
Ein erfolgreicher Aufbau wir vom Controller mit der Zeichenkette
``Grbl 1.1f [$ for help]`` beantwortet.
"""
print("Initialisiere Verbindung zum Controller ...")
# Auf Anfrage des Controllers warten
init_string = self.serielle_verbindung.readline()
if b'\r\n' in init_string:
init_string = self.serielle_verbindung.readline()
# Nach spezieller Init-Zeichenkette suchen
if b'Grbl 1.1f [\'$\' for help]\r\n' in init_string:
print("Verbindung erfolgreich aufgebaut.")
else:
print("Verbindung zu Controller NICHT erfolgreich.")
else:
print("Verbindung zu Controller NICHT erfolgreich.#2")
[Doku] def geraete_status(self):
"""Fragt den aktuellen Maschinenstatus ab und speichert die Ergenisse
in lokale Variablen.
Der Gerätestatus kann ``Idle``, ``Run``, ``Alarm`` usw. annehmen.
Die aktuelle Position wird als X-, Y-, und Z-Koordinate gespeichert.
"""
# Statusabfrage starten
status_string = self.gcode_senden("?")
if len(status_string) >= 39:
# Gerätestatus aus Statusstring extrahieren
self.controller_status = hilfsfunktionen.status_extrahieren(
status_string)
# Koordinaten aus Statusstring extrahieren
koordinaten = hilfsfunktionen.koordinaten_extrahieren(status_string)
# Aktuelle Koordinaten des Gerätes speichern
self.x_position = koordinaten[0]
self.y_position = koordinaten[1]
self.z_position = koordinaten[2]
[Doku] def controller_initialisieren(self):
"""Führt eine Refernzfahrt durch und legt den Nullpunkt des
Koordinatensystems fest.
Note:
Der Befehl ``$H`` fährt die Schlitten zum Endtaster der jeweiligen
Achse.
"""
self.gcode_senden("$H")
self.gcode_senden("G92 X0 Y0 Z0")
# while not self.controller_status == "Idle":
# self.geraete_status(lock)
# print (self.controller_status)
# time.sleep(0.1)
# pass
[Doku] def geraet_halten(self):
"""Pausiert die aktuelle Fahrt, indem alle Schrittmotoren gestoppt
werden.
Beim Pausieren gehen keine Schritte verloren.
Note:
Drehteller dreht sich auch beim Pausieren weiter.
"""
self.gcode_senden("!")
[Doku] def geraet_fortsetzen(self):
"""Setzt die Fahrt fort, indem alle Schrittmotoren wieder aktiviert
werden.
Note:
Es wird das Zeichen ``~`` an den Controller gesendet.
"""
self.gcode_senden("~")
[Doku] def geraet_entsperren(self):
"""Hebt den Alarmstatus auf.
Note:
Es wird das Zeichenkette ``$X`` an den Controller gesendet.
"""
self.gcode_senden("$X")
[Doku] def geraet_softreset(self):
"""Führt einen Software-Reset des CNC-Controllers durch.
Note:
Es wird ``CTRL+X`` an den Controller gesendet, welches im
Oktalformat ``\030`` entspricht.
"""
self.gcode_senden("\030")
[Doku] def drehteller_einschalten(self, drehzahl):
"""Schaltet den Drehteller durch Aktivierung des DC-Getriebemoters ein.
Note:
Vor dem Senden an den CNC-Controller wird die Drehzahl korrigiert,
damit eingestellte und tatsächliche Drehzahl übereinstimmen. Das ist
Aufgrund der Untersetzungen und dem nicht-sauberen PWM-Signal des
CNC-Controllers notwendig.
Args:
drehzahl (int): Gewünschte Drehzahl in 1/min.
"""
# Drehzahl korrigieren
korrigierte_drehzahl = hilfsfunktionen.drehzahlkorrektur(
drehzahl, self.steigung, self.offset)
print("Korrigierte Drehzahl: ", korrigierte_drehzahl, " 1/min")
# An den Controller senden
cmd = "M03 S" + str(korrigierte_drehzahl)
self.gcode_senden(cmd)
[Doku] def drehteller_ausschalten(self):
"""Stoppt den Drehteller.
"""
self.gcode_senden("M05")
[Doku] def reinigungsposition_anfahren(self):
"""Fährt die Reingung-Startsposition lt. Grundeinstellungen an.
Wird vor der Reinigung angefahren. Das Polyester-Pad befindet sich in
dieser Position zentral über der Optik.
Note:
X-, Y- und Z-Position lassen sich in den Grundeinstellungen unter
den Attributen ``reinigungs_start_position_x`` ,
``reinigungs_start_position_y`` und
``reinigungs_start_position_z`` ändern.
"""
x_pos = float(self.basiseinstellungen['reinigungs_start_position_x'])
y_pos = float(self.basiseinstellungen['reinigungs_start_position_y'])
z_pos = float(self.basiseinstellungen['reinigungs_start_position_z'])
xy_start = "G00 X" + str(x_pos) + " Y" + str(
y_pos) + " Z" + str(z_pos) + " F1500"
self.gcode_senden(xy_start)
# Warten bis Endposition erreich ist
# while not ((self.x_position == x_pos) and (self.y_position == y_pos)
# and (self.z_position == z_pos)):
# pass
[Doku] def beladeposition_anfahren(self):
"""Fährt die Beladeposition lt. Grundeinstellungen an.
Wird vor der Reinigung angefahren, damit die Optik eingelegt werden
kann.
Note:
X-, Y- und Z-Position lassen sich in den Grundeinstellungen unter
den Attributen ``belade_position_x`` , ``belade_position_y`` und
``belade_position_z`` ändern.
"""
x_pos = float(self.basiseinstellungen['belade_position_x'])
y_pos = float(self.basiseinstellungen['belade_position_y'])
z_pos = float(self.basiseinstellungen['belade_position_z'])
xy_belade = "G00 X" + str(x_pos) + " Y" + str(
y_pos) + " Z" + str(z_pos) + " F1500"
self.gcode_senden(xy_belade)
[Doku] def abtastposition_anfahren(self):
"""Fährt die Abtast-Startsposition lt. Grundeinstellungen an.
Wird benötigt, wenn ein Oberflächenprofil abgetatet wird. Die Sonde des
Nivellierungssensors befindet sich in dieser Position zentral über der
Optik.
Note:
X-, Y- und Z-Position lassen sich in den Grundeinstellungen unter
den Attributen ``babtast_start_position_x`` ,
``abtast_start_position_y`` und ``abtast_start_position_z`` ändern.
"""
x_pos = float(self.basiseinstellungen['abtast_start_position_x'])
y_pos = float(self.basiseinstellungen['abtast_start_position_y'])
z_pos = float(self.basiseinstellungen['abtast_start_position_z'])
xyz_profilposition = "G00 X" + str(x_pos) + " Y" + str(
y_pos) + " Z" + str(z_pos) + " F1500"
self.gcode_senden(xyz_profilposition)