Drehbare Magnetic-Loop Antenne

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
Schmiedezwerg
User
Beiträge: 2
Registriert: Samstag 22. August 2020, 16:39
Wohnort: Wilhelmshaven

Hallo,
ich möchte euch mein erstes Projekt vorstellen, daß über einen 6 Zeiler hinausgeht. Eine Antenne die über einen Servo von einen Python Progamm per TCP/IP gesteuert wird.
Das Programm zeigt eine Weltkarte (bzw. eine lokale Karte) mit einem zweifarbigen Strich, der die Hauptempfangsrichtung darstellt.
Mit Rechtsklick wählt man den Heimatort und mit Linksklick den Zielort. Sofort dreht der Servo die Antenne in die gewünschte Richtung.
Eine Loop-Antenne ist bidirektional, so das die Antenne nur um 180 Grad gedreht werden muss.

Ich habe lange an dem Projekt geabeitet (fast ein dreiviertel Jahr), und habe dabei vieles über Python und Micropython gelernt. Aber längst nicht alles.
Vieles muss noch geändert werden, ich glaube das Projekt wird niemals fertig. Ideen habe ich noch genug.
Auch an gängige Konventionen habe ich mich noch nicht gehalten. Aber habe hier schon viel darüber gelesen. Verzeihung!

Das komplette Programm liegt als "antenne-esp8266.zip" auf filehorst.de. Bitte die LIESMICH.TXT lesen.

Datei von filehorst.de laden

ich freue mich schon auf eure Kommentare.
Bernd

Code: Alles auswählen

# Importieren u. initialisieren der Pygame-Bibliothek
import pygame
import math
import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('192.168.178.100', 50011))

pygame.init()

# Ort, Ziel, und Grat lesen aus Datei
datei = open("ort.txt", "r")
x_ort = datei.readline()
y_ort = datei.readline()
v_ort = datei.readline()
w_ort = datei.readline()
g_rad = datei.readline()
quad = datei.readline()
grad = datei.readline()
xpo = datei.readline()
ypo = datei.readline()
x_heimat = int(x_ort.strip())
y_heimat = int(y_ort.strip())
v_ziel = int(v_ort.strip())
w_ziel = int(w_ort.strip())
winkel = float(g_rad.strip())
quadrant = int(quad.strip())
grad = str(grad.strip())

datei.close()

# Variablen/KONSTANTEN setzen

B, H = 1280, 766
LINKE_MAUSTASTE = 1
RECHTE_MAUSTASTE = 3
MITTLERE_MAUSTASTE = 2
FPS = 30
GRAU = (155, 155, 155)

spielaktiv = True

# Definieren und Öffnen eines neuen Fensters

pfeil = pygame.image.load("bilder/pfeil2.png")
strich1 = pygame.image.load("bilder/strich1.png")
strich2 = pygame.image.load("bilder/strich2.png")
hintergrund = pygame.image.load("bilder/welt_gross.png")
fenster = pygame.display.set_mode((B, H))
clock = pygame.time.Clock()

# Funktion zum Grafiken rotieren und zentrieren
def rotieren_zentrieren(x, y, strich, winkel):
    # rotieren und in einem neuen "surface" speichern
    rotiert = pygame.transform.rotate(strich, winkel * -1)
    # Bestimmen der neuen Abmessungen (nach Rotation ändern sich diese!)
    groesse = rotiert.get_rect()
    # Ausgabe
    fenster.blit(rotiert, (x - groesse.center[0], y - groesse.center[1]))


def speichern():
    # öffnen der Datei "neu/ort.txt"
    # umwandeln der Zahl in ein String, schreiben in die Datei,
    # hinzufügen einer neuen Zeile
    datei = open("ort.txt", "w")
    datei.write(str(x_heimat))
    datei.write("\n")
    datei.write(str(y_heimat))
    datei.write("\n")
    datei.write(str(v_ziel))
    datei.write("\n")
    datei.write(str(w_ziel))
    datei.write("\n")
    datei.write(str(winkel))
    datei.write("\n")
    datei.write(str(quadrant))
    datei.write("\n")
    datei.write(str(grad))
    datei.write("\n")
# speichern und schließen
    datei.close()
    return ()

# Dreieck berechnen und Quadranten rausfinden
def dreieck(x_heimat, y_heimat, v_ziel, w_ziel):
    global bereich, hoehe_dreieck
    if x_heimat == v_ziel:
        v_ziel = v_ziel + 0.001
    if y_heimat == w_ziel:
        w_ziel = w_ziel + 0.001

    if v_ziel > x_heimat and w_ziel < y_heimat:
        bereich = 1
        strecke_b = v_ziel - x_heimat
        hoehe_dreieck = y_heimat - w_ziel
    elif v_ziel < x_heimat and w_ziel < y_heimat:
        bereich = 2
        strecke_b = x_heimat - v_ziel
        hoehe_dreieck = y_heimat - w_ziel
    elif v_ziel < x_heimat and w_ziel > y_heimat:
        bereich = 3
        strecke_b = x_heimat - v_ziel
        hoehe_dreieck = w_ziel - y_heimat
    elif v_ziel > x_heimat and w_ziel > y_heimat:
        bereich = 4
        strecke_b = v_ziel - x_heimat
        hoehe_dreieck = w_ziel - y_heimat

    a2 = hoehe_dreieck * hoehe_dreieck
    b2 = strecke_b * strecke_b
    c2 = (strecke_b * strecke_b) + (hoehe_dreieck * hoehe_dreieck)
    hypotenuse = math.sqrt(c2)
    a1 = math.asin((b2 + c2 - a2) / (2 * strecke_b * hypotenuse))
    b1 = math.asin((a2 + c2 - b2) / (2 * hoehe_dreieck * hypotenuse))
    alpha = math.degrees(a1) # Bereich 1 und 3
    beta = math.degrees(b1)  # Bereich 2 und 4

    if v_ziel > x_heimat and w_ziel < y_heimat:
        return (alpha, bereich)
    elif v_ziel < x_heimat and w_ziel < y_heimat:
        return (beta + 270, bereich)
    elif v_ziel < x_heimat and w_ziel > y_heimat:
        return (alpha + 180, bereich)
    elif v_ziel > x_heimat and w_ziel > y_heimat:
        return (beta + 90, bereich)

# Schleife Hauptprogramm
while spielaktiv:

    # Überprüfen, auf eine Aktion
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            speichern()
            spielaktiv = False
            
    if event.type == pygame.KEYDOWN:       
        if event.key == pygame.K_F1:
            hintergrund = pygame.image.load("bilder/welt_gross.png")
            
        if event.key == pygame.K_F2:
            hintergrund = pygame.image.load("bilder/welt_mittel.png")
            
        if event.key == pygame.K_F3:        
            hintergrund = pygame.image.load("bilder/welt_klein.png")
             
            
# Mausabfrage
    if event.type == pygame.MOUSEBUTTONDOWN:
        if event.button == LINKE_MAUSTASTE:
                v_ziel, w_ziel = pygame.mouse.get_pos()
                speichern()
                winkel, quadrant = dreieck(x_heimat, y_heimat, v_ziel, w_ziel)
                grad = round(winkel)
                speichern()
                
                servo = grad
                print (servo)
                if servo > 180 :
                    servo = servo -180    
                msg = str(servo)
                msg = msg.encode()
                s.send (msg)
                
                msg = s.recv(1024)
                msg = msg.decode()
                
                print("Serverantwort: " + msg)
                
                
                
                
        if event.button == RECHTE_MAUSTASTE:
                x_heimat, y_heimat = pygame.mouse.get_pos()
                speichern()

# Programmfenster Füllen
    #fenster.fill(GRAU)

    # Bilder zeichnen, Überschrift schreiben
    fenster.blit(hintergrund, (0, 0))
    fenster.blit(pfeil, (v_ziel, w_ziel))
    
    pygame.display.set_caption("Antennen-Richtung: "
    + str(grad) + " Grad    ")
   

    # rotieren(x_heimat, y_heimat, strich, winkel)
    # und den rot-blauen bzw. blau-roten Strich setzen.
    if winkel > 180 and winkel < 360:
        strich = strich2
    elif winkel < 180 and winkel >= 0:
        strich = strich1 
    rotieren_zentrieren(x_heimat, y_heimat, strich , winkel)
    
    # Fenster aktualisieren
    pygame.display.flip()
    clock.tick(FPS)

pygame.quit()
s.close()
Benutzeravatar
__blackjack__
User
Beiträge: 11265
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Hier noch mal den MicroPython-Teil des Programms, damit man sich das nicht vom Filehoster laden muss:

Code: Alles auswählen

import socket
from machine import Pin, PWM
import time 

servo = PWM(Pin(15), freq=250)

def convert(x): 
    return (round ((x * 1.833333333) + 210))
    # return (round ((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min))
    
    
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("192.168.178.100", 50011)) 
s.listen(1)
try: 
    while True: 
        komm, addr = s.accept()
        while True: 
            data = komm.recv(1024)
            
            grad = data.decode()
            grad = int (grad)
            duty_steuerung = convert (grad)
            servo.duty(duty_steuerung)
            
            
            if not data: 
                komm.close()
                break
            print("[{}] {}".format(addr[0], data.decode())) 
            nachricht = ("OK")
            komm.send(nachricht.encode()) 
finally: 
    s.close()
Was auf jeden Fall in beiden Programmen falsch/fehlerhaft ist, ist wie üblich die Verwendung von TCP-Sockets und `send()`/`recv()` als wenn das immer komplette Nachrichten beträfe, also das `send()` wirklich immer alles sendet, und `recv()` mit einem Aufruf immer genau das empfängt was auf der anderen Seite mit *einem* `send()`-Aufruf abgeschickt wurde. Das geht oft so, ist aber überhaupt gar nicht garantiert. Im Gegenteil. TCP ist ein Datenstrom. Mit `send()` wird nicht garantiert das mit dem Aufruf auch alles gesendet wird — das gibt als Ergebnis zurück wie viele Bytes *tatsächlich* gesendet wurden. Das lässt sich mit `sendall()` noch einfach beheben. Aber auf der anderen Seite das `recv()` das nur garantiert das mindestens ein Byte gelesen wird, kann es kein `recvall()` geben, weil gar nicht klar ist was bei einen Bytestrom denn ”all” eigentlich bedeuten soll. Wenn man Nachrichten versenden will, dann muss man sich dafür ein Protokoll überlegen wie der Sender die Daten so senden kann, der der Code auf der Empfängerseite erkennen kann wann eine Nachricht komplett ist. Und sich gegebenenfalls auch Daten die schon zu der oder den folgenden Nachrichten gehören merkt, damit die nicht verloren gehen. Das ist alles nicht so einfach selbst zu programmieren, darum verwendet man in der Regel lieber ein fertiges Protokoll für das es eine Bibliothek gibt.

Auf der PC-Seite ist die Ereignisschleife fehlerhaft, da gehen Ereignisse verloren weil die Mausereignisse *nach* der Schleife über die Ereignisse behandelt werden, als gar nicht *alle* Ereignisse erfassen, sondern immer nur das jeweils letzte Ereignis das in der Schleife vorkam.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst. Es darf keine Variablen auf Modulebene geben — die existieren alle lokal in Funktionen und Methoden. Was bedeutet das eine Funktion oder Methode alles was sie an Werten ausser Konstanten benötigt, als Argument(e) übergeben bekommt.

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.

Man nummeriert keine Namen. Dann will man sich entweder bessere Namen überlegen, oder gar keine Einzelnamen/-werte verwenden, sondern eine Datenstruktur. Oft eine Liste. `strich1` und `strich2` haben beispielsweise einen Kommentar der erklärt wie die aussehen. Wenn das im Namen stünde statt nichtssagender Nummern, wüsste man das auch ohne Kommentar. Noch besser wären aber Namen die dem Leser vermitteln was die beiden Stricharten *bedeuten*.

Namen sollten nicht unnötig weit vom Ort der Verwendung definiert werden. Also beispielsweise nicht `spielaktiv` definieren, dann Tonnen von Code der damit gar nichts zu tun hat, und dann erst die Schleife in der das dann letztlich nur verwendet wird. Wobei der Name etwas unpassend wirkt, weil es sich nicht um ein Spiel handelt. Und eigentlich könnte man sich das auch ganz sparen und eine ``while True:``-Schleife schreiben, die an der entsprechenden Stelle verlassen wird, statt ein Flag zu setzen das die Schleife verlassen werden soll.
“It should be noted that no ethically-trained software engineer would ever consent to write a `DestroyBaghdad` procedure. Basic professional ethics would instead require him to write a `DestroyCity` procedure, to which `Baghdad` could be given as a parameter.” — Nathaniel Borenstein
Antworten