
In diesem Teil werden wir über die Softwarekomponente sprechen, wie die Maschine zum Leben erweckt wurde. Welches Betriebssystem wurde verwendet, welche Sprache wurde gewählt, mit welchen Problemen war es konfrontiert?
1. Wie es in 2 Worten funktioniert
Das System besteht aus einem Server, der auf einer Schreibmaschine installiert ist, und einem Client, der auf der Konsole installiert ist. Der Server erhöht den WLAN- Zugangspunkt und wartet, bis der Client eine Verbindung herstellt. Der Server führt Clientbefehle aus und überträgt auch Videos von der Kamera an die Kamera.
2. Betriebssystem
Lassen Sie uns nun über die verwendeten Betriebssysteme sprechen.
Da das gesamte System auf Raspberry pi 3 basiert, wurde das offizielle Betriebssystem dafür verwendet. Zum Zeitpunkt der Erstellung war die neueste Version Stretch . Sie wurde und wurde für die Verwendung auf einer Schreibmaschine und einem Bedienfeld ausgewählt. Es stellte sich jedoch heraus, dass es einen Fehler gibt (der eine Woche lang gequält wurde), aufgrund dessen es unmöglich ist, den WLAN-Zugangspunkt zu erhöhen. Um den Zugangspunkt zu erhöhen, wurde daher eine frühere Version von Jessie verwendet , die keine derartigen Probleme aufwies.
Artikel, wie man einen Zugangspunkt erhöht. Sehr detailliert, habe alles drauf gemacht.
Die Fernbedienung stellt automatisch eine Verbindung zum Gerät her, wenn der Zugangspunkt angehoben wird.
Automatische Verbindung zu unserem Punkt, in der Datei / etc / network / interfaces hinzufügen:
auto wlan0 iface wlan0 inet dhcp wpa-ssid {ssid} wpa-psk {password}
2. Sprache
Ich habe Python gewählt, weil es einfach und unkompliziert ist.
3. Server
Mit Server in diesem Abschnitt meine ich Software, die von mir geschrieben wurde, um die Maschine zu steuern und mit Video zu arbeiten.
Der Server besteht aus 2 Teilen. Videoserver und Verwaltungsserver.
3.1 Videoserver
Es gab zwei Möglichkeiten, mit einer Videokamera zu arbeiten. 1. Verwenden Sie das Picamera- Modul und 2. Verwenden Sie die mjpg-Streamer-Software . Ohne nachzudenken, entschied ich mich, beide zu verwenden und welche in den Konfigurationseinstellungen zu verwenden.
if conf.conf.VideoServerType == 'm' : cmd = "cd /home/pi/projects/mjpg-streamer-experimental && " cmd += './mjpg_streamer -o "./output_http.so -p {0} -w ./www" -i "./input_raspicam.so -x {1} -y {2} -fps 25 -ex auto -awb auto -vs -ISO 10"'.format(conf.conf.videoServerPort, conf.conf.VideoWidth, conf.conf.VideoHeight) print(cmd) os.system(cmd) else : with picamera.PiCamera(resolution = str(conf.conf.VideoWidth) + 'x' + str(conf.conf.VideoHeight) , framerate = conf.conf.VideoRate) as Camera: output = camera.StreamingOutput() camera.output = output Camera.start_recording(output, format = 'mjpeg') try: address = (conf.conf.ServerIP, conf.conf.videoServerPort) server = camera.StreamingServer(address, camera.StreamingHandler) server.serve_forever() finally: Camera.stop_recording()
Da sie dieselben Einstellungen vornehmen, arbeiten sie an derselben Adresse. Es gibt keine Probleme bei der Kommunikation mit der Fernbedienung, wenn Sie von einer zur anderen wechseln. Das einzige, was ich denke, mjpg-Streamer funktioniert schneller.
3.2 Management Server
3.2.1 Interaktion zwischen Client und Server
Die Server- und Client-Austauschbefehle in Form von JSON-Zeichenfolgen:
{'type': 'remote', 'cmd': 'Start', 'status': True, 'val': 0.0} {'type': 'remote', 'cmd': 'Y', 'status': True, 'val': 0.5} {'type': 'remote', 'cmd': 'turn', 'x': 55, 'y': 32}
- Typ - 'Remote' oder 'Auto', je nachdem, wer den Befehl sendet (Client oder Server)
- cmd - eine Zeichenfolge mit dem Namen der Schaltfläche, der dem Namen der Schaltfläche auf dem Game HAT entspricht , zum Beispiel:
- Start - Schaltfläche Start
- Auswählen - Schaltfläche Auswählen
- Y - Y Taste
- usw.
- drehen - Der Befehl zum Ändern des Status des Joysticks ist für das Drehen der Räder verantwortlich
- status - Richtig oder falsch, je nachdem, ob die Taste gedrückt wird oder nicht. Ein Schaltflächenstatusereignis wird jedes Mal ausgelöst, wenn sich sein Status ändert.
- Wert - Drehzahl und Bewegungsrichtung des Motors von -1 ... 1, Wert vom Typ Schwimmer . Wichtiger Parameter nur für Bewegungstasten.
- x - Abweichung des Joysticks entlang der x-Achse von -100 ... 100, Wert vom Typ int
- y - Abweichung des Joysticks entlang der y-Achse von -100 ... 100, Wert vom Typ int
Als nächstes kommt meine Schande, zu wiederholen, welche Hände nicht erreichen. Der Computer löst den Server-Socket aus und wartet, bis der Client eine Verbindung hergestellt hat. Darüber hinaus wird für jede neue Verbindung ein separater Stream erstellt, und jeder neue Client, der eine Verbindung zum Computer herstellt, kann ihn steuern. Dies kann nicht so weit sein, weil niemand sonst eine solche Fernbedienung hat und ich mein geschlossenes WLAN-Netzwerk erhöhe.
def run(self): TCP_IP = conf.conf.ServerIP TCP_PORT = conf.conf.controlServerPort BUFFER_SIZE = conf.conf.ServerBufferSize self.tcpServer = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.tcpServer.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.tcpServer.bind((TCP_IP, TCP_PORT)) threads = []
3.2.2 Eisenmanagement
Bei der Arbeit mit Raspberry wurde das Pin-Nummerierungssystem GPIO.BCM verwendet.
Das Licht wird über GPIO 17 gesteuert und an den 2. Pin von L293 angeschlossen . Als nächstes enthält der Befehl jedes Mal Folgendes:
GPIO.output(self.gpioLight, GPIO.HIGH)
GPIO.output(self.gpioLight, GPIO.LOW)
entsprechende Befehle werden aufgerufen.
Der Servoantrieb wird über die PCA9685-Karte über den I2C-Bus gesteuert , daher benötigen wir die entsprechende Bibliothek Adafruit_PCA9685 . PCA9685 ist über 7-polig mit dem Servo verbunden. Die erforderliche PWM-Frequenz für die Arbeit mit Servo beträgt 50 Hz oder eine Periode von 20 ms.
Das Funktionsprinzip des Servos:

Wenn ein 1,5-ms-Signal angelegt wird, werden die Räder zentriert. Bei 1 ms. Das Servo dreht sich 2 ms lang bis nach rechts. so weit wie möglich nach links. Die Achsschenkel in Brücken für solche Kurven sind nicht ausgelegt, daher musste der Drehwinkel experimentell ausgewählt werden.
Werte, die an die Adafruit_PCA9685-API übergeben werden können, reichen von 0..4095, 0 kein Signal, 4095 voll. Dementsprechend mussten aus diesem Bereich die für meine Räder geeigneten Werte ausgewählt werden. Der einfachste Weg, die Werte für genau eingestellte Räder zu bestimmen, besteht darin, 1,5 ms auf einen Wert aus dem Bereich von ~ 307 zu übertragen.
Der Maximalwert für rechts beträgt 245, für links 369.
Die vom Joystick kommenden Werte haben Werte von -100 ... 100, daher mussten sie im Bereich von 245 bis 369 übersetzt werden. Auch hier ist die Mitte am einfachsten, wenn 0 307 ist. Links und rechts gemäß der Formel:
val = int(HardwareSetting._turnCenter + (-1 * turn * HardwareSetting._turnDelta / HardwareSetting.yZero))
- HardwareSetting._turnCenter - 307
- Turn - Wert vom Joystick von -100 ... 100
- HardwareSetting._turnDelta - 62, die Differenz zwischen der Mitte und der maximalen Abweichung zur Seite (307 - 245 = 62)
- HardwareSetting.yZero - 100, der vom Joystick empfangene Maximalwert
Räder gerade:
def turnCenter(self): val = int(HardwareSetting._turnCenter) self.pwm_servo.set(val) CarStatus.statusCar['car']['turn'] = val
Biegen Sie links ab:
def turnLeft(self, turn): val = int(HardwareSetting._turnCenter + (-1 * turn * HardwareSetting._turnDelta / HardwareSetting.yZero)) self.pwm_servo.set(val) CarStatus.statusCar['car']['turn'] = val
Biegen Sie rechts ab:
def turnRight(self, turn): val = int(HardwareSetting._turnCenter + (-1 * turn * HardwareSetting._turnDelta / HardwareSetting.yZero)) self.pwm_servo.set(val) CarStatus.statusCar['car']['turn'] = val
Die Motorsteuerung erfolgt auch über die PCA9685-Karte über den I2C-Bus, daher verwenden wir Adafruit_PCA9685 . Die Pins 10 bis 15 des PCA9685 sind mit dem L298N verbunden (ich verwende 2 Kanäle, um Strom zu absorbieren). 10 und 11 an ENA und ENB (ich fülle sie mit PWM, um die Geschwindigkeit zu steuern). 12, 13, 14, 15 bis IN1, IN2, IN3, IN4 - sind für die Drehrichtung des Motors verantwortlich. Die PWM-Frequenz ist hier nicht sehr wichtig, aber ich verwende auch 50 Hertz (mein Standardwert).
Die Maschine steht still:
def stop(self): """ . """ self.pwm.set_pwm(self.ena, 0, self.LOW) self.pwm.set_pwm(self.enb, 0, self.LOW) self.pwm.set_pwm(self.in1, 0, self.LOW) self.pwm.set_pwm(self.in4, 0, self.LOW) self.pwm.set_pwm(self.in2, 0, self.LOW) self.pwm.set_pwm(self.in3, 0, self.LOW)
Weiter geht's:
def back(self, speed): """ . Args: speed: 0 1. """ self.pwm.set_pwm(self.ena, 0, int(speed * self.HIGH)) self.pwm.set_pwm(self.enb, 0, int(speed * self.HIGH)) self.pwm.set_pwm(self.in1, 0, self.LOW) self.pwm.set_pwm(self.in4, 0, self.LOW) self.pwm.set_pwm(self.in2, 0, self.HIGH) self.pwm.set_pwm(self.in3, 0, self.HIGH)
Rückwärtsbewegung:
def forward(self, speed): """ . Args: speed: 0 1. """ self.pwm.set_pwm(self.ena, 0, int(speed * self.HIGH)) self.pwm.set_pwm(self.enb, 0, int(speed * self.HIGH)) self.pwm.set_pwm(self.in1, 0, self.HIGH) self.pwm.set_pwm(self.in4, 0, self.HIGH) self.pwm.set_pwm(self.in2, 0, self.LOW) self.pwm.set_pwm(self.in3, 0, self.LOW)
4. Kunde
4.1 Tastatur
Es gab bestimmte Probleme mit ihr, zuerst wollte ich sie ereignisreich machen (es dauerte ~ 2 Wochen der Qual). Aber die mechanischen Tasten trugen dazu bei, das Klappern der Kontakte führte zu ständigen und unvorhersehbaren Fehlern (die von mir erfundenen Steueralgorithmen funktionierten einwandfrei). Dann erzählte mir mein Kollege, wie die Tastaturen hergestellt werden. Und ich habe beschlossen, dasselbe zu tun. Jetzt frage ich alle 0,005 Sekunden den Status ab (warum und wer weiß). Und wenn es sich geändert hat, senden Sie den Wert an den Server.
def run(self): try: while True: time.sleep(0.005) for pin in self.pins : p = self.pins[pin] status = p['status'] if GPIO.input(pin) == GPIO.HIGH : p['status'] = False else : p['status'] = True if p['status'] != status : p['callback'](pin) except KeyboardInterrupt: GPIO.cleanup()
4.2 Joystick
Das Lesen der Messwerte erfolgt über die ADS1115-Karte über den I2C-Bus. Daher ist die entsprechende Bibliothek Adafruit_PCA9685 . Der Joystick neigt auch dazu, Kontakte zu klappern, daher nehme ich ihn analog zur Tastatur ab.
def run(self): while True: X = self.adc.read_adc(0, gain=self.GAIN) / HardwareSetting.valueStep Y = self.adc.read_adc(1, gain=self.GAIN) / HardwareSetting.valueStep if X > HardwareSetting.xZero : X = X - HardwareSetting.xZero else : X = -1 * (HardwareSetting.xZero - X) if Y > HardwareSetting.yZero : Y = Y - HardwareSetting.yZero else : Y = -1 * (HardwareSetting.yZero - Y) if (abs(X) < 5) : X = 0 if (abs(Y) < 5) : Y = 0 if (abs(self.x - X) >= 1.0 or abs(self.y - Y) >= 1.0) : self.sendCmd(round(X), round(Y)) self.x = X self.y = Y time.sleep(0.005)
Bei einer Stromversorgung von 3,3 Volt reicht der Wertebereich, den der ADS1115 mit einem Joystick ausgibt, von 0 bis 26500 aus. Ich bringe dies in den Bereich von -100 ... 100. In meinem Bereich um 0 schwankt es immer. Wenn die Werte also 5 nicht überschreiten, denke ich, dass es 0 ist (sonst wird es überfluten). Sobald sich die Werte ändern, senden Sie sie an die Schreibmaschine.
4.3 Verbindung zum Verwaltungsserver herstellen
Das Herstellen einer Verbindung zum Server ist eine einfache Sache:
try : tcpClient = socket.socket(socket.AF_INET, socket.SOCK_STREAM) tcpClient.settimeout(2.0) tcpClient.connect((conf.conf.ServerIP, conf.conf.controlServerPort)) self.signalDisplayPrint.emit("+") carStatus.statusRemote['network']['control'] = True self.tcpClient = tcpClient except socket.error as e: self.signalDisplayPrint.emit("-") carStatus.statusRemote['network']['control'] = False time.sleep(conf.conf.timeRecconect) self.tcpClient = None continue if self.tcpClient : self.tcpClient.settimeout(None)
Aber ich möchte auf eine Sache achten. Wenn Sie in der Verbindung kein Timeout verwenden, kann es einfrieren und Sie müssen einige Minuten warten (dies geschieht, wenn der Client vor dem Server gestartet wurde). Ich habe dies folgendermaßen gelöst und das Zeitlimit für die Verbindung festgelegt. Sobald die Verbindung hergestellt ist, entferne ich das Timeout.
Ich speichere auch den Status der Verbindung, damit ich weiß, ob die Kontrolle verloren geht, und zeige sie auf dem Bildschirm an.
4.4 WLAN-Verbindung überprüfen
Ich überprüfe den Status von WLAN für die Verbindung zum Server. Und wenn, dann melde ich mich auch über Probleme.
def run(self): while True: time.sleep(1.0) self.ps = subprocess.Popen(['iwgetid'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) try: output = subprocess.check_output(('grep', 'ESSID'), stdin=self.ps.stdout) if re.search(r'djvu-car-pi3', str(output)) : self.sendStatus('wifi+') continue except subprocess.CalledProcessError: pass self.sendStatus('wifi-') self.ps.kill()
4.5 Verbindung zu einem Videoserver herstellen
Dafür wurde die ganze Leistung von Qt5 benötigt , übrigens auf der Stretch- Distribution ist es neuer und zeigt meiner Meinung nach besser. auf jessie habe ich es auch versucht.
Für die Anzeige habe ich verwendet:
self.videoWidget = QVideoWidget()
Und er folgerte:
self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.LowLatency) self.mediaPlayer.setVideoOutput(self.videoWidget)
Verbindung zum Streaming von Videos:
self.mediaPlayer.setMedia(QMediaContent(QUrl("http://{}:{}/?action=stream".format(conf.conf.ServerIP, conf.conf.videoServerPort)))) self.mediaPlayer.play()
Ich entschuldige mich noch einmal für die Tautologie. Ich überwache den Status der Videoverbindung auf Verbindung zum Videoserver. Und wenn, dann melde ich mich auch über Probleme.
So sieht es aus, wenn nicht alles funktioniert:

- W - bedeutet, dass keine Verbindung mit WLAN besteht
- B - bedeutet kein Video
- Y - bedeutet, dass es keine Kontrolle gibt
Ansonsten gibt es keine roten Buchstaben, es gibt ein Video von der Kamera. Ich werde in Zukunft ein Foto und ein Video mit der Arbeit veröffentlichen. Ich hoffe, dass die Halterung für die Kamera in naher Zukunft kommt und ich sie endlich normal anbringen werde.
5 Konfigurieren des Raspberry-Betriebssystems
Übrigens muss die Arbeit mit der Kamera und anderen notwendigen Dingen eingeschaltet sein (sowohl auf dem Client als auch auf dem Server). Nach dem Laden des Betriebssystems:

Und schalten Sie fast alles ein: Kamera, SSH, i2c, GPIO

Demonstration
Es gibt nur einen Videokanal (die Kamera bleibt in Betrieb). Ich entschuldige mich für seine Abwesenheit, ich werde es am Montag anhängen.

Videoarbeit:
Quellcode
Server- und Client-Quellcode
Startpaket für den Daemon-Server
Referenzen
Teil 1
Teil 3