Funktionsaufruf innerhalb einer Klasse unklar

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
tdan
User
Beiträge: 4
Registriert: Samstag 30. Dezember 2023, 15:38

Hallo zusammen,

arbeite gerade an einem Programm, dass auf einem ESP32 zwei Servos steuert. Dieses läuft richtig. Habe es dann (um es wie eine Bibliothek importieren zu können), als Klasse definiert, damit ich in einem anderen Programm ein Objekt davon erzeugen kann. Jetzt gibt es aber eine Fehlermeldung, die den (vorher laufenden) Funktionsaufruf innerhalb dieser neuen Klasse betrifft.

Code: Alles auswählen

from machine import Pin,PWM
import time

class Rotor:

    # Konstruktor, default azPin = 33, elPin = 32
    def __init__(self, azPin, elPin):
        self.elevation = PWM(Pin(elPin, mode=Pin.OUT))
        self.elevation.freq(50)
        self.azimut = PWM(Pin(azPin, mode=Pin.OUT))
        self.azimut.freq(50)

    # Nullstellung
    def nullstellung(self):
        self.elevation.duty(22)  # 0° Elevation
        time.sleep(2)
        self.azimut.duty(70)     # Sueden = 90°-Stellung in Motorkoordinaten
        print("Nullstellung: 0° Elevation, Azimuth = 180° Süden")
        time.sleep(2)

    # Umrechnung Winkel auf duty-Wert für MGG996R als Elevationsmotor
    def elwinkel(self, x):
        d = int(22+x/180*96) 
        #print("Elevation: "+ str(x)+"° / "+str(d))
        return d
        
    # Umrechnung Winkel auf duty-Wert für MGG996R als Azimutmotor
    # mit Berücksichtigung der umgekehrten Drehrichtung
    def azwinkel(self, x):
        d = int(118-x/180*96)
        #d = int(22+x/180*96) 
        #print("Azimut: "+str(x)+"° / "+str(d))
        return d

    # setze auf Position ohne Berücksichtigung von El<0 oder
    # Azimutumrechnung, wird nur von pointTo(..) aufgerufen
    def setTo(self, a,e):
        self.azimut.duty(azwinkel(a))
        time.sleep(1)
        self.elevation.duty(elwinkel(e))
        time.sleep(1)


    # Ausrichtung auf Az, El für SatPosition
    # mit "flipover": wenn az<90 oder az>270, dann Elevationüber 90°
    # beim az immer 90° subtrahieren, da Motor im Osten 0°-Stellung hat
    # wenn El < 0, dann zeigen auf (az,0)
    def pointTo(self, az, el):
        if el<0:
            el = 0
        if az<90:
            a = az + 180-90
            e = 180 - el
        elif az > 270:
            a = az - 180-90
            e = 180 - el
        else:
            a = az-90
            e = el
        
        print("Az: "+str(a)+"° / "+str(azwinkel(a))+" // El: "+str(e)+"° / "+str(elwinkel(e)))
        
        self.setTo(a,e)
        
    # Testprogramm zum Test der o. a. Funktionen
    def test(self):
        self.nullstellung()
        self.pointTo(35,-10)
        self.pointTo(45,10)
        self.pointTo(65,15)
        self.pointTo(90,20)
        self.pointTo(140,30)
        self.pointTo(180,45)
        self.pointTo(240,30)
        self.pointTo(270,20)
        self.pointTo(290,10)
        self.pointTo(320,0)
        self.pointTo(350,-20)
        self.nullstellung(self)

Zweites Programm:

Code: Alles auswählen

from rotorlib import *



mein_rotor = Rotor(33, 32)
mein_rotor.test()

Hier die Fehlermeldung:

Code: Alles auswählen

>>> %Run -c $EDITOR_CONTENT
Nullstellung: 0° Elevation, Azimuth = 180° Süden
Traceback (most recent call last):
  File "<stdin>", line 6, in <module>
  File "rotorlib.py", line 68, in test
NameError: name 'pointTo' isn't defined

Man beachte, dass die erste Zeile der Funktion test (also der Aufruf der Funktion nullstellung) ausgeführt wird.

Das OOP-Konzept ist mir aus Java bekannt. Ich finde keinen Ansatzpunkt, wie ich an den Fehler herangehen soll.
Benutzeravatar
Dennis89
User
Beiträge: 1157
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

kann dass sein dass da irgendwas beim kopieren schief ging?
Bei mir läuft deine `test` - Funktion soweit, bis die nicht definierten Namen in `pointTo` Alarm schlagen und danach merkt Python das nur `self` ein Argument zu wenig für `pointTo` ist.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
tdan
User
Beiträge: 4
Registriert: Samstag 30. Dezember 2023, 15:38

Hallo Dennis,

vielen Dank zuerst für Deine Mühe!

Welche Namen in pointTo sind denn nicht definiert? Muss ich den Parametern az und el einen Datentyp zuweisen? In Java ginge das ja gar nicht anders.
Ich rufe ja pointTo mit zwei Zahlenwerten auf: self.pointTo(35,-10) zum Beispiel. Muss da dieses self mit rein?

VG
Thomas.
Benutzeravatar
Dennis89
User
Beiträge: 1157
Registriert: Freitag 11. Dezember 2020, 15:13

Folgende Zeile ist aus `pointTo`

Code: Alles auswählen

 print("Az: "+str(a)+"° / "+str(azwinkel(a))+" // El: "+str(e)+"° / "+str(elwinkel(e)))
in dieser Funktion gibt es kein `azwinkel` und kein `elwinkel`. Das sollten bestimmt `az` und `el` sein.


Ah sorry, da bin ich im Namen verkommen. Ich meinte das du ein Problem mit `nullstellung(self)` bekommen wirst, weil `nullstellung` so definiert ist, dass es kein Argument beim Funktionsaufruf benötigt, das müsste dann wie zu Beginn `self.nullstellung()` heißen.


Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
tdan
User
Beiträge: 4
Registriert: Samstag 30. Dezember 2023, 15:38

oha, da habe ich was auszuprobieren, tue ich aber erst morgen

Vielen Dank!
Benutzeravatar
__blackjack__
User
Beiträge: 13122
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@tdan: OO-Prinzip aus Java ist ein bisschen problematisch. Also ein ”Programm“ in eine ”Klasse” stecken klingt zumindest schon mal Borderline falsch, und die Begründung das man das machen muss um es aus einem anderen Modul heraus zu benutzen ist auch nicht stichhaltig. OO-Prinzip ist es einen Zustand und Operationen auf diesem Zustand zu einem Objekt zusammen zu fassen. Nicht zwanghaft jede Funktion in eine Klasse zu stecken. Wenn eine ”Methode” das Objekt gar nicht benötigt, auf der sie aufgerufen wird, dann hat die in der Regel nichts in der Klasse zu suchen. Das betrifft beispielsweise `azwinkel()` und `elwinkel()`

Kommentare über Funktionen/Methoden die sagen was die jeweilige Funktion oder Methode macht, sind eher Docstrings denn Kommentare.

Von Defaultwerten zu schreiben, die aber gar nicht dort zu setzen wo sie beschrieben stehen, ist verwirrend.

Kommentare sollen dem Leser einen Mehrwert über den Code geben. Faustregel: Kommentare beschreiben nicht *was* der Code macht, denn das steht da bereits als Code, sondern warum er das macht. Sofern das nicht offensichtlich ist. Offensichtlich ist in aller Regel auch was in der Dokumentation von Python und den verwendeten Bibliotheken steht. ``# Nullstellung`` vor eine ``def nullstellung(…)`` zu schreiben, bringt niemandem etwas.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase).

Namen sollten keine kryptischen Abkürzungen enthalten oder gar nur daraus bestehen. Der Name soll dem Leser vermitteln was der Wert dahinter im Programm bedeutet, nicht zum rätseln zwingen.

Funktionen und Methoden werden üblicherweise nach Tätigkeiten benannt, damit der Leser weiss was sie tun und damit man sie leichter von eher passiven Werten unterscheiden kann. Wenn man sie schon nach einem ”Ding” benennt, ist es verwirrend wenn das nicht das Ergebnis beschreibt. Wenn man eine Funktion sieht, die `foo_winkel()` heisst, dann würde man erwarten, dass die den *Winkel* berechnet, nicht das die einen Winkel als Argument erwartet und den Duty-Wert als Ergebnis hat.

Man sollte bei einer Sprache bleiben, nicht `nullstellung()`, `point_to()`, `elwinkel()`. Da Programmiersprache und alle anderen Bibliotheken in Englischer Sprache gehalten sind, bietet sich das auch für den eigenen Code an.

`__init__()` ist kein Konstruktor, das ist `__new__()`. Braucht man aber sehr selten wirklich selber zu schreiben, der Normalfall ist das Initialisieren eines Objektes.

`str()` und ``+`` um Werte und Zeichenketten zusammenzustückeln ist eher BASIC als Python. In Python gibt es dafür die `format()`-Methode auf Zeichenketten und f-Zeichenkettenliterale.

Bei der `nullstellung()` würde ich die Duty-Werte nicht hart kodieren. Wenn man aus irgendwelchen Gründen die Berechnungsfunktionen mal anpassen muss, dann stehen dort falsche Werte die man manuell nachrechnen und korrigieren muss. Letztlich könnte man von `nullstellung()` eigentlich `set_to()` aufrufen und sich sehr ähnlichen Code sparen.

Das ”Testprogramm” würde ich dann wieder als Funktion umsetzen.

Ungetestet:

Code: Alles auswählen

import time

from machine import PWM, Pin


def calculate_elevation_duty(angle):
    """
    Umrechnung Winkel auf duty-Wert für MGG996R als Elevationsmotor.
    """
    duty = int(22 + angle / 180 * 96)
    # print(f"Elevation: {angle}° / {duty}"
    return duty


def calculate_azimuth_duty(angle):
    """
    Umrechnung Winkel auf duty-Wert für MGG996R als Azimutmotor mit
    Berücksichtigung der umgekehrten Drehrichtung.
    """
    duty = int(118 - angle / 180 * 96)
    # print(f"Azimut: {angle}° / {duty}"
    return duty


class Rotor:
    def __init__(self, azimuth_pin_number, elevation_pin_number):
        self._azimuth = PWM(Pin(azimuth_pin_number, mode=Pin.OUT))
        self._azimuth.freq(50)
        self._elevation = PWM(Pin(elevation_pin_number, mode=Pin.OUT))
        self._elevation.freq(50)

    def set_azimuth(self, angle):
        self._azimuth.duty(calculate_azimuth_duty(angle))

    def set_elevation(self, angle):
        self._elevation.duty(calculate_elevation_duty(angle))

    def reset(self):
        self.set_elevation(0)
        time.sleep(2)
        self.set_azimuth(90)  # Sueden = 90°-Stellung in Motorkoordinaten
        print("Nullstellung: 0° Elevation, Azimuth = 180° Süden")
        time.sleep(2)

    def set_to(self, azimuth_angle, elevation_angle):
        """
        Setze auf Position ohne Berücksichtigung von Elevation<0 oder
        Azimutumrechnung.
        """
        self.set_azimuth(azimuth_angle)
        time.sleep(1)
        self.set_elevation(elevation_angle)
        time.sleep(1)

    def point_to(self, azimuth_angle, elevation_angle):
        """
        Ausrichtung auf Az, El für SatPosition mit "flipover": wenn az<90 oder
        az>270, dann Elevationüber 90° beim az immer 90° subtrahieren, da Motor
        im Osten 0°-Stellung hat wenn El < 0, dann zeigen auf (az,0).
        """
        elevation_angle = max(0, elevation_angle)

        if azimuth_angle < 90:
            azimuth_angle = azimuth_angle + 180 - 90
            elevation_angle = 180 - elevation_angle
        elif azimuth_angle > 270:
            azimuth_angle = azimuth_angle - 180 - 90
            elevation_angle = 180 - elevation_angle
        else:
            azimuth_angle = azimuth_angle - 90

        print(
            f"Az: {azimuth_angle}°"
            f" / {calculate_azimuth_duty(azimuth_angle)}"
            f" // El: {elevation_angle}°"
            f" / {calculate_elevation_duty(elevation_angle)}"
        )
        self.set_to(azimuth_angle, elevation_angle)


def test(rotor):
    rotor.reset()

    for azimuth_angle, elevation_angle in [
        (35, -10),
        (45, 10),
        (65, 15),
        (90, 20),
        (140, 30),
        (180, 45),
        (240, 30),
        (270, 20),
        (290, 10),
        (320, 0),
        (350, -20),
    ]:
        rotor.point_to(azimuth_angle, elevation_angle)

    rotor.reset()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
tdan
User
Beiträge: 4
Registriert: Samstag 30. Dezember 2023, 15:38

Nochmal ich:
Vielen Dank an Dennis89 und blackjack für die schnelle Hilfe, insbesnodere auch für den langen und aufschlussreichen Text dazu!
Komme aus arbeitszeitlichen Gründen nicht dazu, hier schnell weiterzumachen, möchte mich aber doch schnell bedanken und nicht unhöflich erscheinen, wenn hier das Schweigen ausbricht.
Euch ein gutes Neues 2024!
Antworten