GPS und GSM mit MicroPython auf dem ESP32 - Teil 3 - AZ-Delivery

Diesen Beitrag gibt es auch als:

PDF in deutsch

This episode is also available as:

PDF in English

 

Heute geht es um das Senden von Textnachrichten über das Mobilfunknetz via SMS. ESP32 und SIM808 bilden eine Art Server oder Relaisstation, an den zur Steuerung Textnachrichten gesendet werden können, der aber auch selbst in der Lage ist, Nachrichten zeit- oder eventgesteuert abzusetzen. Als Schalt- und Empfangszentrale könnt ihr dann das Handy, den PC oder einen Mikrocontroller verwenden. Und weil die Strecke zwischen SIM808 und Handy keine Rolle spielt, gibt es auch kein Entfernungslimit, ausreichende Netzabdeckung vorausgesetzt. Das LCD-Keypad wird eigentlich überflüssig, weil eine direkte Steuerung der entfernten ESP32-Einheit sowieso nicht in Frage kommt. Dennoch leistet ein Display gute Dienste beim Start der Relaiseinheit und für weitere Statusmeldungen beim Debuggen. Interessanter als ein LCD ist da vielleicht ein kleines OLED-Display, rein zu Wartungszwecken, aber auch das kann über SMS laufen. Damit herzlich willkommen zum dritten Teil von

Hardwarezuwachs – die SIM-Karte

An Hardware kommt im Vergleich zu Tei2 wenig dazu. Wie? Sie haben die Teile 1 und 2 nicht gelesen und sind neu hier? Na gut, überredet, in der folgenden Liste finden Sie alle Teile für das aktuelle Projekt. Fast alles davon wurde bereits in Teil 1 und Teil2 eingesetzt und dort natürlich auch ausführlich beschrieben. Diese Bauteile verwenden wir selbstverständlich wieder. Neu hinzugekommen ist eine SIM-Karte, denn ohne diese werden Sie keine SMS-Nachricht versenden oder empfangen können. Natürlich kann es sein, dass nicht alle Sensoren und/oder Aktoren wegen der Kabellänge direkt an der Relaisstation angeschlossen werden können. Dann wäre es doch nicht schlecht, wenn es dafür eine Funkstrecke gäbe. Deshalb zeige ich Ihnen in der nächsten Folge, wie man so etwas ganz unkompliziert mit dem UDP-Protokoll über WLAN-Verbindungen umsetzen kann. Für diesen Zweck werde ich als Beispiel einen ESP8266 mit einem LDR-Widerstand als Funksensor verwenden. Andere Sensoren wie DS18B20 (Temperatur), DHT22 AM2302(Feuchte und Temperatur), GY-302 BH1750 (Licht Sensor), etc. können dank der diversen MicroPython-Module, die es dafür gibt, ebenso gut hergenommen werden. Doch jetzt erst einmal zur GSM-Verbindung, wir wollen mit unserem ESP32 ein bisschen simsen.

ESP32 Dev Kit C V4 unverlötet oder ähnlich

LCD1602 Display Keypad Shield HD44780 1602 Modul mit 2x16 Zeichen

SIM 808 GPRS/GSM Shield mit GPS Antenne für Arduino 

1

Battery Expansion Shield 18650 V3 inkl. USB Kabel

1

Li-Akku Typ 18650

1

I2C IIC Adapter serielle Schnittstelle für LCD Display 1602 und 2004

4

Widerstand 10kΩ

1

GY-BMP280 Barometrischer Sensor für Luftdruckmessung 

1

SIM-Karte (beliebiger Anbieter)


Die Schaltung für das Projekt wird erst einmal 1:1 von Teil 2 übernommen. Später entscheiden Sie selbst, welche Teile Sie weglassen, ersetzen oder neu hinzunehmen. Die programmtechnischen Möglichkeiten für die Umsetzung finden Sie in diesem Beitrag.

GPS Schematic

Abbildung 2: gps-teil2

Ein besser lesbares Exemplar der Darstellung in DIN A4 bekommen Sie mit dem Download der PDF-Datei .

Die Software

Verwendete Software:

Fürs Flashen und die Programmierung des ESP:

Thonny oder

µPyCraft

 

Verwendete Firmware:

MicropythonFirmware

 

Download MicroPython-Module und Programme

GPS-Modul für SIM808 und GPS6MV2(U-Blocks)

LCD-Standard-Modul

HD44780U-I2C-Erweiterung zum LCD-Modul

Keypad-Modul

Button Modul

BMP208-Modul

i2cbus-Modul für standardisierten Zugriff auf den Bus

Das Hauptprogramm relais.py

testkeypad.py zum Testen der Tastendecodierung des LCD-Keypads

Tricks und Infos zu MicroPython

In diesem Projekt wird die Interpretersprache MicroPython benutzt. Der Hauptunterschied zur Arduino-IDE ist, dass Sie die MicroPython-Firmware auf den ESP32 flashen müssen, bevor der Controller MicroPython-Anweisungen versteht. Sie können dazu Thonny, µPyCraft oder esptool.py benutzen. Für Thonny habe ich den Vorgang im ersten Teil des Blogs zu diesem Thema beschrieben.

Nachdem die Firmware geflasht ist, können Sie sich zwanglos mit Ihrem Controller im Zwiegespräch unterhalten, einzelne Befehle testen und sofort die Antwort sehen, ohne vorher ein ganzes Programm compilieren und übertragen zu müssen. Bei der Entwicklung der Software für diesen Blog habe ich davon wieder reichlich Gebrauch gemacht. Das Spektrum reicht von einfachen Tests der Syntax bis zum Ausprobieren und Verfeinern von Funktionen und ganzen Programmteilen. Zu diesem Zweck werde ich, wie schon bei den vorangegangenen Folgen, kleine Testprogramme erstellen. Sie bilden eine Art Macro, weil sie wiederkehrende Befehle zusammenfassen.

Gestartet werden solche Programme aus dem aktuellen Editorfenster in der Thonny-IDE über die Taste F5, das geht schneller als der Mausklick auf den Startbutton oder über das Menü Run. Die Installation von Thonny habe ich auch im ersten Teil genau beschrieben.

Die Klassen GPS, SIM808 und GSM

Gut, das Wichtigste für eine GPS-Anwendung ist: Wie spreche ich die GPS-Dienste des SIM808 an? Ach ja, richtig – es soll in diesem Beitrag ja nicht (allein) um GPS sondern vorrangig um GSM gehen. Die Frage muss also wohl anders lauten. Vielleicht: Wie kann man mit dem SIM808 und dem ESP32 simsen? Genau das wollen wir uns jetzt anschauen. Die AT-Befehle erlauben uns eine einfache Handhabung der vielfältigen Eigenschaften des SIM808-Moduls.Einen sehr kleinen Teil habe ich benutzt, um daraus die GSM-Klasse für mein Projekt zu basteln. Zusammen mit der Klasse GPS für die Ortung und der Klasse SIM808 für die Hardwareansteuerung finden Sie GSM im Modul gps.py friedlich vereint.

Für neu dazugestoßene Leser, so nehmen Sie das SIM808-Modul in Betrieb. Sie müssen den kleinen Schiebeschalter gleich neben der Rohrbuchse für die Spannungsversorgung, 5V bis 12V, in Richtung SIM808-Chip schieben. Eine rote LED leuchtet neben der GSM-Antennenbuchse auf. Das finden Sie sicher ganz leicht, denn alle Lötpunkte und Schnittstellen sind auf dem Board gut dokumentiert.

Ein Stück weiter links von der GSM-Antennenbuchse befindet sich die Starttaste. Sie können zur Orientierung auch die obige Schaltskizze zu Hilfe nehmen. Drücken Sie die Starttaste ca. 1 Sekunde lang, dann leuchten zwischen den anderen beiden Antennenbuchsen zwei weitere LEDs auf, die rechte davon blinkt im Sekundenrhythmus. An die linke Schraubbuchse sollte bereits die aktive GPS-Antenne angeschlossen sein. Diese legen Sie am besten in die Nähe eines Fensters.

Damit Sie jetzt nicht jedes Mal das Gehäuse ihres GPS-Empfängers öffnen müssen, um das SIM808 zu starten, empfehle ich Ihnen, es mir gleich zu tun und ein Kabel an den heißen Anschluss des Starttasters zu löten. Von oben betrachtet ist es der rechte, wenn die Rohrbuchse ebenfalls nach rechts zeigt. Sie können nun das SIM808 starten, indem Sie einen GPIO-Pin des ESP32 als Ausgang definieren und für eine Sekunde von High nach Low und zurück auf High schalten. Ich habe dafür den Pin 4 vorgesehen.

SIM808 unten

Abbildung 3: SIM808unten

 

Beim Aufruf des Konstruktors für das GPS-Objekt wird die Nummer des Pins zusammen mit dem Displayobjekt als Parameter übergeben. Bevor Sie die nachfolgenden Kommandos an den ESP32 schicken, laden Sie bitte die eingangs verlinkten Module in den Flash-Speicher des Controllers hoch. Die Befehle werden über die Kommandozeile im Terminalbereich eingegeben.

 >>> from gps import GPS,SIM808,GSM
 >>> from lcd import LCD
 >>> from machine import ADC, Pin, I2C
 >>> from keypad import KEYPAD
 >>> i2c=I2C(-1,Pin(21),Pin(22))
 >>> d=LCD(i2c,0x27,cols=16,lines=2)
 >>> g=GSM(4,d)
 >>> k=KEYPAD(35)

Wird kein Displayobjekt (d) übergeben, gibt es natürlich auch keine Ausgabe auf LCD oder OLED. Es erfolgt aber keine Fehlermeldung, die Tastensteuerung arbeitet normal. Bei fast allen wichtigen Ergebnissen erfolgt eine Ausgabe im Terminalfenster.

Die Klasse GPS erledigt die Hauptarbeit. Der Konstruktor erwartet, wie erwähnt, ein Displayobjekt, das im aufrufenden Programm definiert oder bereits bekannt sein muss. Es wird ein serieller Kanal zum SIM808 auf 9600 Baud, 8,0,1 geöffnet, dann werden die Instanzvariablen für die Aufnahme der GPS-Daten eingerichtet.

Im Überblick hier die wichtigsten Methoden der GPS-Klasse

Die Methode waitForLine() tut, was ihr Name sagt, sie wartet auf einen NMEA-Satz vom SIM808. Als Parameter wird der Typ des NMEA-Satzes angegeben, der erwartet wird. Ist der Satz vollständig und fehlerfrei, wird er an das aufrufende Programm zurückgegeben. Es können in der gegenwärtigen Ausbaustufe des Programms $GPRMC- und $GPGGA-Sätze empfangen werden. Sie enthalten alle relevanten Daten wie Gültigkeit, Datum, Zeit, geographische Breite (Latitude, vom Äquator aus bis zu den Polen in Grad) und Länge (Longitude vom Null-Meridian aus +/- 180°) sowie Höhe über NN in Metern. Analog zu dem bestehenden Code können leicht weitere Datensätze vom SIM808 aufgenommen und decodiert werden.

Die Methode decodeLine() nimmt den empfangenen Satz und versucht ihn zu parsen. Diese Methode enthält eine lokale Funktion, die nach Vorgabe des Attributs Mode die Winkelangaben in die Formate Grad Minuten Sekunden und Bruchteile, Grad und Bruchteile oder Grad Minuten und Bruchteile umwandelt.

Die Methode printData() gibt einen Datensatz im Terminalfenster aus. showData() liefert das Ergebnis an das Display. Weil mit dem LCD-Keypad nur ein zweizeiliges Display verwendet wird, muss die Anzeige in mehrere Abschnitte aufgeteilt werden. Die Tasten des Keypads übernehmen die Steuerung.

Weil die UART0-Schnittstelle für REPL reserviert ist, muss eine zweite Schnittstelle für die Kommunikation mit dem SIM808 vorhanden sein. Der ESP32 stellt eine solche als UART2 bereit. Die Anschlüsse für RXD (Empfang) und TXD (Sendung) können sogar frei gewählt werden. Für einen Vollduplexbetrieb (senden und empfangen gleichzeitig) müssen die Anschlüsse RXD und TXD vom ESP32 zum SIM808 gekreuzt werden. Sie können das am Schaltplan nachvollziehen. Die Defaultwerte am ESP32 sind RXD=16 und TXD=17. Die Organisation des Anschlusses übernimmt die Klasse gps.GPS.

Das beginnt mit dem Einschalten des SIM808. Wenn Sie meiner Empfehlung gefolgt sind und ein Kabel an den Einschalttaster gelötet haben, können Sie das SIM808 jetzt mit folgendem Befehl einschalten, vorausgesetzt, dass dieses Kabel am Pin 4 des ESP32 liegt.

 >>> g.SIMOn()

Befehle an das SIM808 werden im AT-Format übermittelt. Es gibt eine riesige Auswahl von Befehlen, die in einer PDF-Datei nachgelesen werden können. Aber keine Sorge, für unser Projekt reichen wenige Befehle. Zwei davon sind in den Methoden init808() und deinit808() zusammengefasst, ein paar weitere werden im GSM-Kapitel vorgestellt.

     def init808(self):
         self.u.write("AT+CGNSPWR=1\r\n")
         self.u.write("AT+CGNSTST=1\r\n")
 
     def deinit808(self):
         self.u.write("AT+CGNSPWR=0\r\n")
         self.u.write("AT+CGNSTST=0\r\n")

AT+CGNSPWR=1 aktiviert das GPS-Modul und AT+CGNSTST=1 aktiviert die Übertragung der NMEA-Sätze zum ESP32 über die serielle Schnittstelle UART2. Der Controller empfängt die Informationen des SIM808 und stellt sie in der oben beschriebenen Weise via Terminal und LCD bereit.

Das Modul gps.py enthält neben der Hardwaresteuerung des SIM808 auch noch die nötigen Befehle für das kleinere GPS-System GPS6MV2 mit dem Chip Neo 6M von UBLOX. Die Steuerung dieses Moduls erfolgt nicht über AT-Befehle, sondern über eine eigene Syntax. Die Klassen SIM808 und GPS6MV2 sind nicht gegen einander austauschbar, weil sie über unterschiedliche APIs verfügen.

Zum genaueren Studium des gps-Moduls folgt nun das Listing. Die drei enthaltenen Klassen GPS, SIM808(GPS) und GSM(SIM808) bauen durch Vererbung einen einheitlichen Namensraum auf. Daher sind alle Methoden in einem Objekt der Klasse GSM verfügbar. Wird GSM nicht gebraucht (kein SMS-Transfer), kann man auch über die Klasse SIM808 einsteigen, wie es im Teil 2 gemacht wurde.

Die Klasse SIM808 kümmert sich um die Hardwaresteuerung und um den Datentransfer zum ESP32. Die Klasse GPS enthält Methoden zur Decodierung der NMEA-Sätze vom SIM808, zu deren Darstellung auf dem Display und zur Kursberechnung. Die GSM-Klasse schließlich, stellt die Methoden für den SMS-Transfer und die Verwaltung von Nachrichten zur Verfügung. Diese Nachrichten müssen sich nicht allein auf GPS-Daten beziehen. Vielmehr habe ich die Klasse gps.GSM neutral gehalten, sodass sie auch in anderen Projekten einsetzbar ist. Auch im Programm relais.py finden Sie nicht nur einen GPS-Ansatz, sondern auch die Umsetzung einer BMP280-Abfrage via GSM als Beispiel für die Einbindung weiterer Sensoren. Die dafür erforderlichen Klassen BMP280 und I2CBus wurden bereits in Teil2 vorgestellt.

 """
 File: gps.py
 Author: J. Grzesina
 Rev. 1.0: AVR-Assembler
 Rev. 2.0: Adaption auf Micropython
 ------------------
 Die enthaltenen Klassen sprechen einen ESP32 als Controller an.
 Dieses Modul beherbergt die Klassen GPS, GPS6MV2 und SIM808
 GPS stellt Methoden zur Decodierung und Verarbeitung der NMEA-Saetze
 $GPGAA und $GPRMC bereit, welche die wesentlichen Infos zur
 Position, Hoehe und Zeit einer Position liefern. Sie werden dann
 angezeigt, wenn die Datensaetze als "gueltig" gemeldet werden.
 Eine Skalierung auf weitere NMEA-Sätze ist jederzeit möglich.
 GPS6MV2 und SIM808 beziehen sich auf die entsprechende Hardware.
 """
 from machine import UART,I2C,Pin
 import sys
 from time import sleep, time, ticks_ms
 from math import *
 
 # *********************** Beginn GSM ****************************PS
 class GPS:
     #
     gDeg=const(0)
     gFdeg=const(1)
     gMin=const(1)
     gFmin=const(2)
     gSec=const(2)
     gFsec=const(3)
     gHemi=const(4)
     
     #DEFAULT_TIMEOUT=500
     #CHAR_TIMEOUT=200
     
     def __init__(self,disp=None,key=None):  # display mit OLED-API
         self.u=UART(2,9600)
         # u=UART(2,9600,tx=19,rx=18) # mit alternativen Pins
         self.display=disp
         self.key=key
         self.timecorr=2
         self.Latitude=""
         self.Longitude=""
         self.Time=""
         self.Date=""
         self.Height=""
         self.Valid=""
         self.Mode="DMF" # default
         self.AngleModes=["DDF","DMS","DMF"]
         self.displayModes=["time","height","pos"]
         self.DMode="pos"
         # DDF = Degrees + DegreeFractions
         # DMS = Degrees + Minutes + Seconds + Fractions
         # DMF = Degrees + Minutes + MinuteFraktions
         self.DDLat=49.28868056  # aktuelle Position
         self.DDLon=11.47506105
         self.DDLatOld=49.3223 # vorige Position
         self.DDLonOld=11.5000
         self.zielPtr=0
         self.course=0
         self.distance=0
         print("GPS initialized, Position:{},{}".format(self.DDLat,self.DDLon))
 
     def decodeLine(self,zeile):
         latitude=["","","","","N"]
         longitude=["","","","","E"]
         angleDecimal=0
         def formatAngle(angle):  # Eingabe ist Deg:Min:Fmin
             nonlocal angleDecimal
             minute=int(angle[1])     # min als int
             minFrac=float("0."+angle[2]) # minfrac als float
             angleDecimal=int(angle[0])+(float(minute)+minFrac)/60
             if self.Mode == "DMS":
                 seconds=minFrac*60
                 secInt=int(seconds)
                 secFrac=str(seconds - secInt)
                 a=str(int(angle[0]))+"*"+angle[1]+"'"+str(secInt)+secFrac[1:6]+'"'+angle[4]
             elif self.Mode == "DDF":
                 minutes=minute+minFrac
                 degFrac=str(minutes/60)
                 a=str(int(angle[0]))+degFrac[1:]+"* "+angle[4]
             else:
                 a=str(int(angle[0]))+"*"+angle[1]+"."+angle[2]+"' "+angle[4]
             return a
 
         # GPGGA-Fields
         nmea=[0]*16
         name=const(0)
         time=const(1)
         lati=const(2)
         hemi=const(3)
         long=const(4)
         part=const(5)
         qual=const(6)
         sats=const(7)
         hdop=const(8)
         alti=const(9)
         auni=const(10)
         geos=const(11)
         geou=const(12)
         aged=const(13)
         trash=const(14)
         nmea=zeile.split(",")
         lineStart=nmea[0]
         if lineStart == "$GPGGA":
             self.Time=str((int(nmea[time][:2])+self.timecorr)%24)+":"+nmea[time][2:4]+":"+nmea[time][4:6]
             latitude[gDeg]=nmea[lati][:2]
             latitude[gMin]=nmea[lati][2:4]
             latitude[gFmin]=nmea[lati][5:]
             latitude[gHemi]=nmea[hemi]
             longitude[gDeg]=nmea[long][:3]
             longitude[gMin]=nmea[long][3:5]
             longitude[gFmin]=nmea[long][6:]
             longitude[gHemi]=nmea[part]
             self.Height,despose=nmea[alti].split(".")
             self.Latitude=formatAngle(latitude)  # mode = Zielmodus Winkelangabe
             self.DDLat=angleDecimal
             self.Longitude=formatAngle(longitude)
             self.DDLon=angleDecimal
         if lineStart == "$GPRMC":
             date=nmea[9]
             self.Date=date[:2]+"."+date[2:4]+"."+date[4:]
             try:
                 self.Valid=nmea[2]
             except:
                 self.Valid="V"
 
     def waitForLine(self,title,delay=2000):
         line=""
         c=""
         d=delay
         if delay < 1000: d=1000
         start = ticks_ms()
         end=start+d
         current=start
         while current <= end:
             #print(end-current)
             if self.u.any():
                 c=self.u.read(1)
                 if ord(c) <=126:
                     c=c.decode()
                     if c == "\n":
                         test=line[0:6]
                         if test==title:
                             #print(line)
                             return line
                         else:
                             line=""
                     else:
                         if c != "\r":
                             line +=c
             current = ticks_ms()
             sleep(0.05)
         return ""    
         
     def showData(self):
         if self.display:
             if self.DMode=="time":
                 self.display.writeAt("Date:{}".format(self.Date),0,0)
                 self.display.writeAt("Time:{}".format(self.Time),0,1)
             if self.DMode=="height":
                 self.display.writeAt("Height: {}m   ".format(self.Height),0,0)
                 self.display.writeAt("Time:{}".format(self.Time),0,1)
             if self.DMode=="pos":
                 self.display.writeAt(self.Latitude+" "*(16-len(self.Latitude)),0,0)
                 self.display.writeAt(self.Longitude+" "*(16-len(self.Longitude)),0,1)
         
     def printData(self):
         print(self.Date,self.Time,sep="_")
         print("LAT",self.Latitude)
         print("LON",self.Longitude)
         print("ALT",self.Height)
         
     def showError(self,msg):
             if self.display:
                 self.display.clearAll()
                 self.display.writeAt(msg,0,0)
             print(msg)
 
     def storePosition(self):  # aktuelle Position als DD.dddd merken
         lat=str(self.DDLat)+","
         lon=str(self.DDLon)+"\n"
         try:
             D=open("stored.pos","wt")
             D.write(lat)
             D.write(lon)
             D.close()
             if self.display:
                 self.display.clearAll()
                 self.display.writeAt("Pos. stored",0,0)
                 sleep(3)
                 self.display.clearAll()
         except (OSError) as e:
             enumber=e.args[0]
             if enumber==2:
                 print("Not stored")
                 if self.display:
                     self.display.clearAll()
                     self.display.writeAt("act. Position",0,0)
                     self.display.writeAt("not stored",0,0)
                     sleep(3)
                     self.display.clearAll()
       
     def chooseDestination(self, wait=3):
         if not self.display: return None
         self.display.clearAll()
         self.display.writeAt("ENTER=RST-Button",0,0)
         n="positions.pos"
         try:
             D=open(n,"rt")
             ziel=D.readlines()
             D.close()
             i = 0
             while 1:
                 lat,lon=(ziel[i].rstrip("\r\n")).split(",")
                 self.display.clearAll()
                 self.display.writeAt("{}. {}".format(i,lat),0,0)
                 self.display.writeAt("   {}".format(lon),0,1)
                 sleep(wait)
                 if self.key.value()==0: break
                 i+=1
                 if i>=len(ziel): i=0
             self.zielPtr=i
             self.display.clearAll()
             self.display.writeAt("picked: {}".format(i),0,0)
             sleep(wait)
             self.display.clearAll()
             lat,lon=ziel[i].split(",")
             lon=lon.strip("\r\n")
             print("{}. Lat,Lon: {}, {}".format(i,lat,lon))
             lat=float(lat)
             lon=float(lon)
             return (lat,lon)
         except (OSError) as e:
             enumber=e.args[0]
             if enumber==2: print("File not found")
             self.display.clearAll()
             self.display.writeAt("There is no",0,0)
             self.display.writeAt("Positionfile",0,1)
             sleep(3)
             self.display.clearAll()
             return (0,0)
         
     def calcNewCourse(self,delay=3): # von letzter Position bis hier
         lat,lon=self.chooseDestination(delay)
         if lat==0 and lon==0: return
         dy=(lat-self.DDLat)*60*1852
         dx=(lon-self.DDLon)*60*1852*cos(radians(self.DDLatOld))
         print("Start {},{}".format(self.DDLat,self.DDLon)," Ziel {},{}".format(lat,lon))
         #print("Ziel-Start",self.DDLon-self.DDLonOld, self.DDLat-self.DDLatOld)
         return self.calcCourse(dx,dy)
         
     def calcLastCourse(self): # von letzter Position bis hier
         try:
             D=open("stored.pos","rt")
             lat,lon=(D.readline()).split(",")
             D.close()
             self.DDLatOld=float(lat)
             self.DDLonOld=float(lon)
         except:
             self.DDLatOld=49.3223
             self.DDLonOld=11.50
         dy=(self.DDLat-self.DDLatOld)*60*1852
         dx=(self.DDLon-self.DDLonOld)*60*1852*cos(radians(self.DDLatOld))
         print("Start {},{}".format(self.DDLonOld,self.DDLatOld)," Ziel {},{}".format(self.DDLon,self.DDLat))
         #print("Ziel-Start",self.DDLon-self.DDLonOld, self.DDLat-self.DDLatOld)
         return self.calcCourse(dx,dy)
 
     def calcCourse(self,dx,dy): # von letzter Position bis hier
         course=0
         distance=0
         #print(dx,dy,degrees(atan2(dy,dx)))
         if abs(dx) < 0.0002:
             if dy > 0:
                 course=0
                 #print("Trace: 1")
             if dy < 0:
                 course=180
                 #print("Trace: 2")
             if abs(dy) < 0.0002:
                 course=None
                 #print("Trace: 3")
         else:  # dx >= 0.0002
             if abs(dy) < 0.0002:
                 if dx > 0:
                     course=90
                     #print("Trace: 4")
                 if dx < 0:
                     course=270
                     #print("Trace: 5")
             else:  ## dy > 0.0002
                 course=90-degrees(atan2(dy,dx))
                 #print("Trace: 6")
                 if course > 360:
                     course -= 360
                     #print("Trace: 7")
                 if course < 0:
                     course += 360
                     print("Trace: 8")
         self.course=int(course)
         self.distance=int(sqrt(dx*dx+dy*dy))
         print("Distance: {}, Course: {}".format(self.distance,self.course))
         return (self.distance,self.course)
 
 # *********************** Ende GPS ******************************
 
 # ********************* Beginn SIM808 ***************************
 class SIM808(GPS):
     DEFAULT_TIMEOUT=const(500)  
     CHAR_TIMEOUT=const(100)
     CMD=const(1)
     DATA=const(0)
     
     def __init__(self,switch=4,disp=None,key=None):
         self.switch=Pin(switch,Pin.OUT)
         self.switch.on()
         super().__init__(disp,key)
         self.display=disp
         self.key=key
         print("SIM808 initialized")
     
     def simOn(self):
         self.switch.off()
         sleep(1)
         self.switch.on()
         sleep(3)
 
     def simOff(self):
         self.switch.off()
         sleep(3)
         self.switch.on()
         sleep(3)
 
     def simStartPhone():
         pass
 
     def simGPSInit(self):
         self.u.write("AT+CGNSPWR=1\r\n")
         self.u.write("AT+CGNSTST=1\r\n")
 
     def simGPSDeinit(self):
         self.u.write("AT+CGNSPWR=0\r\n")
         self.u.write("AT+CGNSTST=0\r\n")
         
     def simStopGPSTransmitting(self):
         self.u.write("AT+CGNSTST=0\r\n")
 
     def simStartGPSTransmitting(self):
         self.u.write("AT+CGNSTST=1\r\n")
 
     def simFlushUART(self):
         while self.u.any():
             self.u.read()
 
     # Wartet auf Zeichen an UART -> 0: keine Zeichen bis Timeout
     def simWaitForData(self,delay=CHAR_TIMEOUT):
         noOfBytes=0
         start=ticks_ms()
         end=start+delay
         current=start
         while current <= end:
             sleep(0.1)
             noOfBytes=self.u.any()
             if noOfBytes>0:
                 break
         return noOfBytes
 
     def simReadBuffer(self,cnt,tout=DEFAULT_TIMEOUT,ctout=CHAR_TIMEOUT):
         i=0
         strbuffer=""
         start=ticks_ms()
         prevchar=0
         while 1:
             while self.u.any():
                 c=self.u.read(1)
                 c=chr(ord(c))
                 prevchar=ticks_ms()
                 strbuffer+=c
                 i+=1
                 if i>=cnt: break
             if i>= cnt:break
             if ticks_ms()-start > tout: break
             if ticks_ms()-prevchar > ctout: break
         return (i,strbuffer) # gelesene Zeichen
 
     def simSendByte(self,data):
         return self.u.write(data.to_bytes(1,"little"))
         
     def simSendChar(self,data):
         return self.u.write(data)
             
     def simSendCommand(self,cmd):
         self.u.write(cmd)
 
     def simSendCommandCRLF(self,cmd):
         self.u.write(cmd+"\r\n")
 
     def simSendAT(self):
         return self.simSendCmdChecked("AT","OK",CMD)
 
     def simSendEndMark(self):
         self.simSendChar(chr(26))
 
     def simWaitForResponse(self,resp,typ=DATA,tout=DEFAULT_TIMEOUT,ctout=CHAR_TIMEOUT):
         l=len(resp)
         s=0
         self.simWaitForData(300)
         start=ticks_ms()
         prevchar=0
         while 1:
             if self.u.any():
                 c=self.u.read(1)
                 if ord(c) < 126:
                     c=c.decode()
                     prevchar=ticks_ms()
                     s=(s+1 if c==resp[s] else 0)
                 if s == l: break
             if ticks_ms()-start > tout: return False
             if ticks_ms()-prevchar > ctout: return False
         if type==CMD:
             self.simFlushUART()
         return True
                     
     def simSendCmdChecked(self,cmd,response,typ,tout=DEFAULT_TIMEOUT,ctout=CHAR_TIMEOUT):
         self.simSendCommand(cmd)
         return self.simWaitForResponse(response,typ,tout,ctout)
 
 # ********************** Ende SIM808 ***************************
 
 # *********************** Beginn GSM ****************************
 
 class GSM(SIM808):
     
     def __init__(self, switch=4, disp=None, key=None):
         super().__init__(switch,disp,key)
         self.gsmInit()
         print("GSM module initialized")
         
     def gsmInit(self):
         if not self.simSendCmdChecked("AT","OK\r\n",CMD):
             return False
         if not self.simSendCmdChecked("AT+CFUN=1","OK\r\n",CMD):
             return False
         if not self.gsmCheckSimStatus():
             return False
         return True
     
     def gsmIsPowerUp(self):
         return self.simSendAT()
 
     def gsmPowerOn(self):
         self.switch.off()
         sleep(3)
         self.switch.on()
         sleep(3)
 
     def gsmPowerReset(self):
         self.switch.off()
         sleep(3)
         self.switch.on()
         sleep(3)
         self.switch.off()
         sleep(1)
         self.switch.on()
         sleep(3)
 
     def gsmCheckSimStatus(self):
         n=0
         a=""
         self.simFlushUART()
         while n < 3:
             self.simSendCommand("AT+CPIN?\r\n")
             a=self.simReadBuffer(32)
             if "+CPIN: READY" in a[1]:
                 break
             n+=1
             sleep(0.3)
         if n == 3:
             return False
         return True
 
       
     def gsmSendSMS(self,phoneNbr,mesg):
         if not self.simSendCmdChecked("AT+CMGF=1\r\n","OK\r\n",CMD):
             print("SMS-Mode not selcted")
             return False
         sleep(0.5)
         self.simFlushUART()
         if not self.simSendCmdChecked('AT+CMGS="'+phoneNbr+'"\r\n',">",CMD):
             print("Phonenumber Problem")
             return False
         sleep(1)
         self.simSendCommand(mesg)
         sleep(0.5)
         self.simSendEndMark()
         sleep(1)
         return self.simWaitForResponse(mesg,CMD)
         #return self.simReadBuffer(50)
 
     def gsmAreThereSMS(self,stat):
         buf=""
         # SMS-Modus aktivieren
         self.simSendCmdChecked("AT+CMGF=1\r\n","OK\r\n",CMD)
         sleep(1)
         self.simFlushUART()
         # ungelesene SMS listen ohne Statusänderung ",1"
         #print("SMS-Status",stat)
         self.simSendCommand('AT+CMGL="{}",1\r\n'.format(stat))
         sleep(2)
         # OK findet sich in den ersten 30 Zeichen nur, wenn
         # keine ungelesene SMS vorliegt
         a=self.simReadBuffer(30)[1]
         #print(30,a)
         if "OK" in a:
             sleep(0.1)
             return 0
         else:
             # restliche Zeichen im UART-Buffer entsorgen
             self.simFlushUART()
             # erneuter Aufruf zum Einlesen
             self.simSendCommand('AT+CMGL="{}",1\r\n'.format(stat))
             sleep(2)
             a=self.simReadBuffer(48)[1]
             #print(48,a)
             # suche nach der Position von "+CMGL:"
             p=a.find("+CMGL:")
             if p != -1:
                 pkomma=a.find(",",p)
                 #print("gefunden",a[p+6:pkomma])
                 return int(a[p+6:pkomma])
             else:
                 #print("CMGL not found", a)
                 return None
         #print("Kein 'OK'")    
         return None
 
     def gsmReadAll(self,stat,cnt=500):
         # SMS-Modus aktivieren
         self.simSendCmdChecked("AT+CMGF=1\r\n","OK\r\n",CMD)
         sleep(1)
         self.simFlushUART()
         # SMS listen ohne Statusänderung ",1"
         self.simSendCommand('AT+CMGL="{}",1\r\n'.format(stat))
         sleep(2)
         a=self.simReadBuffer(cnt)
         #print("BUFFER:\n",a[1],"\n")
         return a
 
     def gsmFindSMS(self,stat="ALL",cnt=150):
         Liste=[]
         i=0
         p0=0
         again=-1
         self.simFlushUART()
         m,a=self.gsmReadAll(stat,cnt)
         while again == -1:
             again=a.find("OK")  # wenn OK gefunden, letzter Durchgang
             #print("again",again) # only for debugging
             #print("@@@@",a,"@@@@") # only for debugging
             while 1:
                 merker=p0
                 #print("merker: ",merker) # only for debugging
                 p0=a.find("+CMGL:",p0)
                 #print("While1_p0",p0)
                 if p0 == -1:
                     p0=merker
                     #print("@p0_break",p0) # only for debugging
                     break
                 p1=a.find(",",p0)
                 #print("p0 und p1: ",p0,p1) # only for debugging
                 if p1 == -1:
                     p0=merker
                     #print("@p1_break",p0) # only for debugging
                     break
                 n=int(a[p0+6:p1])
                 #print("Liste: ",Liste,"neu: ",n) # only for debugging
                 Liste.append(n)
                 p0=p1
             m,x=self.simReadBuffer(cnt)
             #print(p0,": Buffer: \n",a[p0:],"*****",x,"<<<<<") # only for debugging
             a=a[p0:]+x
             p0=0
             if m == 0:
                 break
         return Liste
 
     def gsmReadSMS(self,index,mode=0): # mode=1: Status nicht veraendern
         # SMS-Modus einschalten
         self.simSendCmdChecked("AT+CMGF=1\r\n","OK\r\n",CMD)
         sleep(1)
         self.simFlushUART()
         try:
             if mode==1:
                 self.simSendCommand("AT+CMGR={},1\r\n".format(index))
             elif mode==0:
                 self.simSendCommand("AT+CMGR={}\r\n".format(index))
             sleep(1)
             a=self.simReadBuffer(250) # Antwort=(Anzahl, Zeichen)
             #print(a[1]) # only for debugging
             if a[0] != 0:
                 a=a[1].split(',',4+mode)
                 #print(a) # only for debugging
                 p0=a[0+mode].find('"')
                 p1=a[0+mode].find('"',p0+1)
                 Status=a[0+mode][p0+1:p1]
                 Phone=a[1+mode].strip('"')
                 Date=a[3+mode].lstrip('"')
                 Time=a[4+mode].split('"')[0]
                 Message=a[4+mode].split('"')[1].strip("\r\n").rstrip("\r\nOK")
                 return (Status,Phone,Date,Time,Message)
             else:
                 return None
         except (IndexError,TypeError) as e:
             print("Index out if range",e.args[0])
             return None
             
     def gsmShowAndDelete(self,stat,disp=None,delete=None):
         SMSlist=self.gsmFindSMS(stat, cnt=100)
         while 1:
             if len(SMSlist)==0:
                 print("nothing to do")
                 break
             for i in SMSlist:
                 response=self.gsmReadSMS(i,mode=1)
                 print(i,response[0],"\n",response[4])
                 if disp:
                     disp.clearAll()
                     disp.writeAt("{}. {}".format(i,response[0]),0,0)
                     disp.writeAt(response[4],0,1)
                     sleep(2)
                 if delete or not(response[1]=='+49xxxxxxxxxxx'): #evtl. weitere PhoneNbr + stati
                     if self.gsmDeleteSMS(i):
                         print("!!! deleted",response[1])
             first=SMSlist[0]
             SMSlist=self.gsmFindSMS(stat,cnt=100)
             if len(SMSlist)==0 or SMSlist[0] == first: break
         if disp: disp.clearAll()
 
     def gsmDeleteSMS(self,index):
         print("SMS[{}] deleted".format(index))
         return self.simSendCmdChecked("AT+CMGD={},0\r\n".format(index),"OK\r\n",CMD)
 
 # *********************** Ende GSM ******************************
 
 # ******************** Beginn GPS6MV2 ***************************
 class GPS6MV2(GPS):
     # Befehlscodes setzen
     GPGLLcmd=const(0x01)
     GPGSVcmd=const(0x03)
     GPGSAcmd=const(0x02)
     GPVTGcmd=const(0x05)
     GPRMCcmd=const(0x04)
 
     def __init__(self,delay=1,disp=None,key=None):
         super().__init__(disp,key)
         self.display=disp
         self.delay=delay  # GPS sendet im delay Sekunden Abstand
         period=delay*1000
         SetPeriod=bytearray([0x06,0x08,0x06,0x00,period&0xFF,(period>>8)&0xFF,0x01,0x00,0x01,0x00])
         self.sendCommand(SetPeriod)
         self.sendScanOff(bytes([GPGLLcmd]))
         self.sendScanOff(bytes([GPGSVcmd]))
         self.sendScanOff(bytes([GPGSAcmd]))
         self.sendScanOff(bytes([GPVTGcmd]))
         self.sendScanOn(bytes([GPRMCcmd]))
         print("GPS6MV2 initialized")
 
     def sendCommand(self,comnd):  # comnd ist ein bytearray
         self.u.write(b'\xB5\x62')
         a=0; b=0
         for i in range(len(comnd)):
             c=comnd[i]
             a+=c  # Fletcher Algorithmus
             b+=a
             self.u.write(bytes([c]))
         self.u.write(bytes([a&0xff]))
         self.u.write(bytes([b&0xff]))
               
     def sendScanOff(self,item):  # item ist ein bytes-objekt
         shutoff=b'\x06\x01\x03\x00\xF0'+item+b'\x00'
         self.sendCommand(shutoff)
 
     def sendScanOn(self,item):
         turnon=b'\x06\x01\x03\x00\xF0'+item+b'\x01'
         self.sendCommand(turnon)
 
 

GSM – oder Simsen für den ESP32

Zu den AT-Befehlen für das Einschalten der GPS-Einheit und das Öffnen der UART-Verbindung auf dem SIM808 gesellen sich für den SMS-Betrieb einige weitere, auf die ich kurz eingehen werde. Grundsätzlich sind AT-Befehle mit einem \r\n (Wagenrücklauf und Zeilenvorschub) abzuschließen, damit sie vom SIM808 als solche erkannt werden. Das SIM808 sendet Antworten oder Ergebnisse zurück. Außer bei Nutzdaten, wie Nachrichtentexten, ist es hier nur wichtig, ob die Antwort der erwarteten entspricht. Es gibt daher eine gsm-Methode, die genau diese Überprüfung durchführt. Das Klassenattribut CMD (Wert = 1) kennzeichnet den AT-Befehl als Kommando. Für Kommandos gilt, dass nach dem Einlesen der Antwort vom SIM808 der Rest des UART-Buffers automatisch geleert wird.

simSendCmdChecked("AT+CMGF=1\r\n","OK\r\n",CMD)

AT+CMGF=1: schaltet auf Textbetrieb um, und ermöglich so den SMS-Betrieb

Der Status von SMS-Nachrichten erlaubt deren Auswahl zum Beispiel beim Listbefehl. stat enthält demnach einen der folgenden Strings: "ALL", "REC UNREAD" oder "REC_READ". Dieser Wert wird durch die Formatanweisung in den Befehl eingebaut.

simSendCommand('AT+CMGL="{}",1\r\n'.format(stat))

Die '1' sorgt dafür, dass sich beim Auflisten der Status nicht ändert.

Beim Versenden von SMS-Nachrichten muss dem Befehl als erstes die Nummer des Anschlusses mitgeteilt werden. Das SIM808 antwortet darauf mit einem ">".

simSendCmdChecked('AT+CMGS="'+phoneNbr+'"\r\n',">",CMD)

Wurde ">" erkannt, kann die Nachricht gesendet werden.

simSendCommand(mesg)

Das Ende der Nachricht wird der Gegenstation durch Senden eines Textendezeichens (chr(26)) bekannt gegeben.

Um eine SMS-Nachricht aus dem Speicher des SIM808 zu lesen, muss deren Index bekannt sein. Nach Absetzen des Lesebefehls kann der UART-Puffer des SIM808 ausgelesen werden. Er enthält den Status, Datum, Uhrzeit und den Text der Nachricht.

"AT+CMGR={}\r\n".format(index))

Nachrichten können durch Angabe des Index auch gelöscht werden.

simSendCmdChecked("AT+CMGD={},0\r\n".format(index),"OK\r\n",CMD)

Erst nachdem eine SIM-Karte erkannt wurde, ist der SMS-Betrieb möglich. Der Befehl

simSendCommand("AT+CPIN?\r\n")

überprüft das. Die SIM-Karte darf dabei nicht durch eine PIN gesichert sein.

Ein Teil der GSM-Methoden dient der internen Verarbeitung und Steuerung. Das Hauptprogramm relais.py zeigt die Anwendung der Methoden der Klasse.

Das Anwendungsprogramm relais.py ist im Vergleich zur reinen GPS-Anwendung aus Teil2 ein wenig umfangreicher geworden. Das liegt an den verschiedenen Beispielen für die Abfrage von Sensoren und dem SMS-Nachrichtenverkehr. Das Programm hat schon einiges zu bieten. Im Einzelnen demonstriert es:

  • zeitgesteuerte SMS (alle x Stunden, Minuten…)

  • eventgesteuerte SMS (Temperatur außerhalb eines Bereichs)

  • SMS on demand (Antwort auf eine SMS)

  • GPS-Tracker (Entfernung überschritten oder Wegpunktübermittlung)

  • Messwert übermitteln

 # File: relais.py
 # put contents into boot.py after successful testing
 # Purpose: Booting SMS-Relais Station
 # Author: J. Grzesina
 # Rev.:1.0 - 2021-04-29
 #********************** Beginn Bootsequenz ************************
 # Dieser Teil geht 1:1 an boot.py fuer autonomen Start
 #************************ Importgeschaeft *************************
 # Hier werden grundlegende Importe erledigt
 import os,sys       # System- und Dateianweisungen    
 
 import esp          # nervige Systemmeldungen aus
 esp.osdebug(None)
 
 import gc           # Platz fuer Variablen schaffen
 gc.collect()
 #
 from machine import ADC, Pin, I2C
 from button import BUTTONS, BUTTON32
 from time import sleep,time,sleep_ms, ticks_ms
 from gps import GPS,SIM808, GSM
 from lcd import LCD
 from keypad import KEYPAD
 from bmp280 import BMP280
 
 #***************** Variablen/Objekte deklarieren *****************
 # ----------------------------------------------------------------
 # ***************** create essential objects *********************
 # ----------------------------------------------------------------
 rstNbr=25
 rst=BUTTON32(rstNbr,True,"RST")
 ctrl=Pin(rstNbr,Pin.IN,Pin.PULL_UP)
 t=BUTTONS() # Methoden für Buttons bereitstellen
 
 i2c=I2C(-1,Pin(21),Pin(22))
 
 k=KEYPAD(35)
 
 d=LCD(i2c,0x27,cols=16,lines=2)
 d.printAt("SIM808-GSM",0,0)
 d.printAt("RELAY booting",0,1)
 
 sleep(1)
 
 g=GSM(switch=4,disp=d,key=ctrl)
 print("************************")
 g.simGPSInit()
 g.simOn()
 g.mode="DDF"
 
 b=BMP280(i2c)
 #sleep(10)
 
 timeFlag   =0b00000001 # Intervallsteuerung
 distanceFlag=0b00000010 # Streckenalarm
 tempFlag   =0b00000100 # Tempraturalarm
 orderFlag   =0b00001000 # SMS Anforderung per SMS
 Trigger=orderFlag # Hier die Flags der Dienste eintragen
 
 distLimit=100 # Entfernungsrichtwert in Metern
 tempMax=25 # *C
 tempMin=20 # *C
 
 heartbeat=0 # Actionblinker
 
 timeBase=3600*1 # Zeitintervall ins Sekunden
 distanceBase=3600*1#3600*2 # Sekunden
 tempBase=20#3600*2 # Sekunden
 timeEnde=time()+5
 distanceEnde=time()+5
 tempEnde=time()+5
 
 # Die Dictionarystruktur (dict) erlaubt spaeter die Klartextausgabe
 # des Verbindungsstatus anstelle der Zahlencodes
 #********************Funktionen deklarieren ***********************
 
 def timeJob():
     t=str(b.calcTemperature())+"*C"
     p=str(b.calcPressureNN())+"hPa"
     print("sende Wetterdaten")
     g.simFlushUART()
     g.gsmSendSMS("+49xxxxxxxxxxx","Wetterwerte:\n{},\n{}".format(t,p))
     return  True
 
 def tempJob(temp):
     t=str(temp)+"*C"
     return g.gsmSendSMS("+49xxxxxxxxxxx","WARNUNG!!:\n{},\nTemperatur nicht im Limit!!".format(t))
 
 def positionJob(dist):
     g.gsmSendSMS("+49xxxxxxxxxxx","Entfernung: {},\n Positionswerte\n{},\n{}".format(dist,g.Latitude,g.Longitude))
     print("Position gemeldet")
     
 def orderJob(Nachrichten):
     # decode message
     if "wetter" in Nachrichten.lower():
         print("Wetterdaten angefordert")
         return timeJob()
     # do jobs
     # send results
     # return True # if no errors occured
 
 def getDistance():
     rmc=g.waitForLine("$GPRMC",delay=1000)
     if rmc:
         try:
             g.decodeLine(rmc)
             if g.Valid == "A":
                 try:
                     gga=g.waitForLine("$GPGGA",delay=2)
                     g.decodeLine(gga)
                     g.printData()
                     if d: g.showData()
                     entfernung=g.calcLastCourse()[0]
                     print("Distanz: {}".format(entfernung))
                     return entfernung
                 except:
                     g.showError("Invalid GGA-set!")
         except:
             g.showError("Invalid RMC-set!")          
 # ********************   Funktionen Ende   ***********************
 
 print("Check SMS")
 g.gsmShowAndDelete("REC_READ",d,delete=True)
 g.gsmShowAndDelete("ALL",d,delete=False)
 
 # ***********************   Main Loop   **************************
 while 1:
     getDistance()
     if d:
         heartbeat+=1
         if heartbeat % 2:
             d.writeAt("X ",14,0)
         else:
             d.writeAt(" "+g.Valid,14,0)
 
     if (Trigger & timeFlag) == timeFlag and time() >= timeEnde:
         timeJob()
         timeEnde=time()+timeBase
         
     if (Trigger & distanceFlag) == distanceFlag and time() >= distanceEnde:
         g.calcLastCourse()        
         w=g.distance
         print(w)
         if w > distLimit:
             positionJob(w)
             ## naechsten Befehl aus kommentieren für Kreisfläche
             g.storePosition()
         distanceEnde = time()+ distanceBase
         
     if (Trigger & orderFlag) == orderFlag:
         L=g.gsmFindSMS("REC UNREAD")
         if L:
             Nachricht=g.gsmReadSMS(L[0],mode=1)[4]
             print("neue SMS gefunden", L)
             orderJob(Nachricht)
             print("Job erledigt, mache jetzt Kaffepause")
             sleep(5)
             g.gsmDeleteSMS(L[0])
             sleep(2)
             
     if (Trigger & tempFlag)==tempFlag and time() >= tempEnde:
         t=b.calcTemperature()
         #print("Temperatur: {} ## {}".format(t,tempEnde))
         if t < tempMin or t > tempMax:
             tempJob(t)
         tempEnde=time()+tempBase
         #print(tempEnde)
         
     # Job-Schleifenende
     if ctrl.value() == 0: break
     sleep(0.1)

Hinweis:

Damit Sie auch Nachrichten auf Ihr Handy bekommen, setzen Sie bitte an anstatt der +49xxxxxxxxxxx Ihre eigene Handynummer ein.

Für die einzelnen Jobs gibt es jeweils ein Steuerflag. damit wird die Funktion scharf geschaltet. Alle Funktionen bis auf SMS on demand haben neben dem Steuerflag noch eine zusätzliche Zeitsperre. Diese bewirkt, dass zum Beispiel nach dem Überschreiten der Temperatur nicht pausenlos SMS versandt werden, bis der Wert wieder im Rahmen ist. Die Zeitsperre wird in Sekunden angegeben, kann aber durch entsprechende Faktoren fast beliebig gedehnt werden.

Im Eventmodus wird in festen Zeitabschnitten ein Sensor abgefragt. Nur wenn der Sensorwert nicht den Vorgaben entspricht, wird eine Nachricht versandt.

Eine Art Eventmodus ist auch die SMS-Nachricht über GPS-Positionen. Möglich sind natürlich viel mehr als die gezeigten zwei Optionen. Mit Aktivierung des distanceFlags ist der Wegpunkt-Modus voreingestellt. Wenn Sie das Speichern der letzten Position auskommentieren, reagiert das System auf jede Entfernung über die vorgegebene Strecke hinaus, in festen zeitlichen Abständen, wie oben beschrieben.

Der rein zeitgesteuerte Modus verschickt unabhängig von einem Sensorwert das Ergebnis einer Messung. Hier wird per Voreinstellung auf die Messergebnisse des BMP280 zugegriffen, stellvertretend für beliebige weitere Sensoren.

Bei der SMS on demand können Codeworte an den ESP32 via SMS gesendet werden. Ungelesene SMS-Leichen werden bei jedem Neustart gelöscht. Dann informiert das System über noch im Speicher befindliche andere Nachrichten.

in der Jobschleife wird auf eingetroffene Nachrichten geprüft. Die Jobfunktion muss den Inhalt decodieren und gegebenenfalls entsprechende Aktionen einleiten oder Messungen durchführen. Denkbar sind auch Aktionen, die direkt auf das SIM808 wirken und zum Beispiel alle SMS löschen. In jedem Fall automatisch gelöscht wird auch die Mail, welche die Aktion ausgelöst hat. Diese Betriebsart ist voreingestellt.

Weitere Nachrichten warten im Eingangspuffer des SIM808 und werden der Reihe nach abgearbeitet. Sehr wichtig sind die Wartezeiten (fett). werden die weggelassen oder zu kurz angesetzt, dann gehen SMS im Dauerlauf raus, weil der nächste Schleifendurchlauf die Nachricht erneut auffindet und bearbeitet.

     if (Trigger & orderFlag) == orderFlag:
         L=g.gsmFindSMS("REC UNREAD")
         if L:
             Nachricht=g.gsmReadSMS(L[0],mode=1)[4]
             print("neue SMS gefunden", L)
             orderJob(Nachricht)
             print("Job erledigt, mache jetzt Kaffepause")
             sleep(5)
             g.gsmDeleteSMS(L[0])
             sleep(2)

Sie sehen, die Einsatzmöglichkeiten sind sehr vielfältig. Dazu kommt, dass niemand außer Ihnen diese Steuerung unberechtigt benutzen kann, es sei denn, Sie hängen die Telefonnummer Ihrer SIM-Karte ans Schwarze Brett, zusammen mit all Ihren Steuercodes und dem Handy. Denn selbst Zugriffe von anderen Telefonnummern außer Ihrer eigenen sind im Programm geblockt.

So, und damit das alles autonom mit dem Einschalten des ESP32 startet, müssen Sie das Programm einfach in die Datei boot.py kopieren und diese zum ESP32 hochladen, Neustart, das war's. Sollte nachträglich etwas schief gehen und der ESP32 von Thonny oder einem anderen Terminalprogramm aus nicht mehr ansprechbar sein, dann ziehen Sie einfach die Notbremse, die das Programm abbricht. Es ist die RST-Taste am LCD-Keypad, die Sie so lange drücken, bis im Terminal der REPL-Prompt >>> erscheint. Jetzt können Sie Änderungen durchführen.

Ich wünsche viel Vergnügen beim Simsen mit Ihrem ESP32. In der nächsten Folge erlauben wir unserem Controller, zusammen mit dem SIM808, fremdzugehen. Ein oder auch mehrere Funkmodule, die mit ESP8266 oder ESP32 bestückt sein können, werden den Aktionsradius des Controllers erweitern. Das Übertragungsprotokoll wird UDP sein, das reicht für unsere Zwecke locker aus und ist viel schneller und simpler als TCP. Bleiben Sie dran!

 

Viel Spaß bei der Umsetzung des Projekts!

 

Weitere Downloadlinks:

PDF in deutsch

PDF in english

AmateurfunkDisplaysEsp-32Projekte für anfänger

Deja un comentario

Todos los comentarios son moderados antes de ser publicados