GAZ-66 mainan di panel kontrol. Bagian 2

gambar


Pada bagian ini kita akan berbicara tentang komponen perangkat lunak, bagaimana mesin itu hidup. OS apa yang digunakan, bahasa apa yang dipilih, masalah apa yang dihadapi.


1. Cara kerjanya dalam 2 kata


Sistem terdiri dari server yang diinstal pada mesin tik, dan klien yang diinstal pada konsol. Server menaikkan titik akses wifi dan menunggu hingga klien terhubung. Server mengeksekusi perintah klien, dan juga mentransmisikan video dari kamera ke sana.


2. OS


Sekarang mari kita bicara tentang sistem operasi yang digunakan.


Karena keseluruhan sistem didasarkan pada Raspberry pi 3 , OS resmi untuk itu digunakan. Pada saat pembuatan, versi terbaru adalah Peregangan , itu dan dipilih untuk digunakan pada mesin tik dan remote control. Tetapi ternyata ia memiliki bug (tersiksa selama seminggu) karena itu tidak mungkin untuk meningkatkan titik akses wifi. Karena itu, untuk meningkatkan jalur akses, versi Jessie sebelumnya diambil yang tidak memiliki masalah seperti itu.


Artikel cara meningkatkan titik akses. Sangat detail, melakukan semua yang ada di sana.


Remote control secara otomatis terhubung ke mesin ketika menaikkan titik akses.
Koneksi otomatis ke titik kami, di file / etc / network / interfaces tambahkan:


auto wlan0 iface wlan0 inet dhcp wpa-ssid {ssid} wpa-psk {password} 

2. Bahasa


Saya memilih python karena mudah dan sederhana.


3. Server


Dengan server di bagian ini saya maksudkan perangkat lunak yang ditulis oleh saya untuk mengendalikan mesin dan bekerja dengan video.


Server terdiri dari 2 bagian. Server video dan server manajemen.


3.1 Server video


Ada 2 opsi cara bekerja dengan kamera video. Modul penggunaan pertama kamera dan perangkat lunak penggunaan mjpg-streamer ke-2 . Tanpa berpikir dua kali, saya memutuskan untuk menggunakan keduanya, dan yang mana untuk digunakan dalam pengaturan konfigurasi.


 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() 

Karena mereka mengambil pengaturan yang sama, mereka bekerja di alamat yang sama. Tidak ada masalah berkomunikasi dengan remote control ketika berpindah dari satu ke yang lain. Satu-satunya hal yang saya pikir mjpg-streamer bekerja lebih cepat.


3.2 Server Manajemen


3.2.1 Interaksi antara klien dan server


Perintah pertukaran server dan klien dalam bentuk string json:


 {'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} 

  • ketik - 'jarak jauh' atau 'mobil' tergantung pada siapa yang mengirim perintah (klien atau server)
  • cmd - string dengan nama tombol yang sesuai dengan nama tombol pada Game HAT , misalnya:
    • Mulai - tombol Mulai
    • Pilih - Pilih tombol
    • Tombol Y - Y
    • dll.
    • turn - perintah untuk mengubah status joystick, bertanggung jawab untuk memutar roda
  • status - Benar atau Salah, tergantung pada apakah tombol ditekan atau tidak. Acara status tombol dikirim setiap kali kondisinya berubah.
  • val - kecepatan dan arah gerakan motor dari -1 ... 1, nilai tipe float . Parameter signifikan hanya untuk tombol gerak.
  • x - deviasi joystick sepanjang sumbu x dari -100 ... 100, nilai tipe int
  • y - deviasi joystick di sepanjang sumbu y dari -100 ... 100, nilai tipe int

Berikutnya adalah rasa malu saya, untuk mengulangi tangan yang tidak bisa menjangkau. Mesin menaikkan soket server dan menunggu sampai klien terhubung. Selain itu, untuk setiap koneksi baru, itu menciptakan aliran terpisah, dan setiap klien baru yang akan terhubung ke mesin akan dapat mengendalikannya)). Ini tidak bisa sejauh ini karena tidak ada orang lain yang memiliki kendali jarak jauh, dan saya meningkatkan jaringan wifi tertutup saya.


 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 = [] #     . self.tcpServer.listen(1) while True: print("Car server up : Waiting for connections from TCP clients...") (conn, (ip, port)) = self.tcpServer.accept() newthread = ClientThread(conn, ip, port) newthread.start() self.threads.append(newthread) 

3.2.2 Manajemen Besi


Saat bekerja dengan Raspberry, sistem penomoran pin GPIO.BCM digunakan.


Lampu dikontrol melalui gpio 17, terhubung ke pin 2 pada L293 . Selanjutnya, setiap kali perintah datang untuk menyertakan:


 GPIO.output(self.gpioLight, GPIO.HIGH) 

 GPIO.output(self.gpioLight, GPIO.LOW) 

perintah yang sesuai disebut.


Drive servo dikendalikan melalui papan PCA9685 melalui bus I2C, jadi kami membutuhkan pustaka yang sesuai untuk itu Adafruit_PCA9685 . PCA9685 terhubung ke servo melalui 7 pin. Frekuensi PWM yang diperlukan untuk bekerja dengan servo adalah 50 Hz atau periode 20 ms.


Prinsip pengoperasian servo:


gambar


Ketika sinyal 1,5 ms diterapkan, roda akan berada di tengah. Pada 1 ms. servo akan berubah sejauh mungkin ke kanan, 2 ms. ke kiri sebanyak mungkin. Buku-buku jari kemudi di jembatan untuk belokan semacam itu tidak dirancang, sehingga sudut rotasi harus dipilih secara eksperimental.


Nilai yang dapat diteruskan ke rentang API Adafruit_PCA9685 mulai dari 0.,4095, 0 tanpa sinyal, 4095 penuh. Oleh karena itu, dari kisaran ini perlu untuk memilih nilai yang cocok untuk roda saya. Cara termudah untuk menentukan nilai untuk set roda yang tepat adalah dengan mentransfer 1,5 ms ke nilai dari kisaran ~ 307.


Nilai maksimum untuk kanan adalah 245, untuk kiri 369.


Nilai-nilai yang berasal dari joystick mengambil nilai dari -100 ... 100, sehingga harus diterjemahkan dalam kisaran 245 hingga 369. Sekali lagi, pusatnya adalah yang termudah, jika 0 adalah 307. Kiri dan kanan sesuai dengan rumus:


 val = int(HardwareSetting._turnCenter + (-1 * turn * HardwareSetting._turnDelta / HardwareSetting.yZero)) 

  • HardwareSetting._turnCenter - 307
  • turn - nilai dari joystick dari -100 ... 100
  • HardwareSetting._turnDelta - 62, perbedaan antara pusat dan deviasi maksimum ke samping (307 - 245 = 62)
  • HardwareSetting.yZero - 100, nilai maksimum yang diterima dari joystick

Roda lurus:


 def turnCenter(self): val = int(HardwareSetting._turnCenter) self.pwm_servo.set(val) CarStatus.statusCar['car']['turn'] = val 

Belok kiri:


 def turnLeft(self, turn): val = int(HardwareSetting._turnCenter + (-1 * turn * HardwareSetting._turnDelta / HardwareSetting.yZero)) self.pwm_servo.set(val) CarStatus.statusCar['car']['turn'] = val 

Belok kanan:


 def turnRight(self, turn): val = int(HardwareSetting._turnCenter + (-1 * turn * HardwareSetting._turnDelta / HardwareSetting.yZero)) self.pwm_servo.set(val) CarStatus.statusCar['car']['turn'] = val 

Kontrol engine juga dilakukan melalui papan PCA9685 melalui bus I2C, jadi kami menggunakan Adafruit_PCA9685 . Pin 10 hingga 15 pada PCA9685 terhubung ke L298N (saya menggunakan 2 saluran untuk menyerap daya). 10 dan 11 untuk ENA dan ENB (saya mengisinya dengan PWM untuk mengontrol kecepatan). 12, 13, 14, 15 hingga IN1, IN2, IN3, IN4 - bertanggung jawab untuk arah putaran motor. Frekuensi PWM tidak terlalu penting di sini, tetapi saya juga menggunakan 50 Hertz (nilai default saya).


Mesin diam:


 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) 

Bergerak maju:


 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) 

Gerakan mundur:


 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. Pelanggan


4.1 Keyboard


Ada masalah tertentu dengannya, pada awalnya saya ingin membuatnya penting (butuh ~ 2 minggu siksaan). Tapi tombol mekanis berkontribusi, derak kontak menyebabkan kegagalan yang konstan dan tidak dapat diprediksi (algoritma kontrol yang saya temukan bekerja dengan tidak sempurna). Lalu kolega saya memberi tahu saya cara membuat keyboard. Dan saya memutuskan untuk melakukan hal yang sama, sekarang saya polling negara setiap 0,005 detik (kenapa begitu, dan siapa yang tahu). Dan jika sudah berubah, kirim nilainya ke 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


Pembacaan bacaan dilakukan melalui papan ADS1115 melalui bus I2C, oleh karena itu, perpustakaan yang sesuai untuk itu adalah Adafruit_PCA9685 . Joystick juga rentan terhadap obrolan kontak, jadi saya mengambil bacaan dengan analogi dengan keyboard.


 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) 

Saat diberdayakan dari 3,3 volt, rentang nilai yang diberikan ADS1115 dengan joystick dari 0 ... 26500. Saya membawa ini ke kisaran -100 ... 100. Dalam kisaran saya sekitar 0 selalu berfluktuasi, jadi jika nilainya tidak melebihi 5, maka saya menganggap bahwa itu adalah 0 (jika tidak maka akan banjir). Segera setelah nilai berubah, kirim ke mesin tik.


4.3 Menghubungkan ke server manajemen


Menghubungkan ke server adalah hal sederhana:


 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) 

Tapi saya ingin memperhatikan satu hal. Jika Anda tidak menggunakan batas waktu dalam koneksi, maka itu dapat membeku dan Anda harus menunggu sekitar beberapa menit (ini terjadi ketika klien mulai sebelum server). Saya memecahkan ini dengan cara berikut, saya mengatur batas waktu untuk koneksi. Segera setelah koneksi terjadi, saya menghapus batas waktu.


Saya juga menyimpan status koneksi, sehingga saya akan tahu jika kontrol hilang dan menampilkannya di layar.


4.4 Memeriksa koneksi WiFi


Saya memeriksa status wifi untuk koneksi ke server. Dan jika, saya juga memberi tahu diri saya tentang masalah.


 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 Menghubungkan ke server video


Untuk ini, semua kekuatan Qt5 diperlukan , dengan cara distribusi Peregangan lebih baru dan menurut saya menunjukkan lebih baik. pada jessie saya mencoba juga.


Untuk tampilan saya menggunakan:


 self.videoWidget = QVideoWidget() 

Dan dia menyimpulkan:


 self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.LowLatency) self.mediaPlayer.setVideoOutput(self.videoWidget) 

Koneksi ke streaming video:


 self.mediaPlayer.setMedia(QMediaContent(QUrl("http://{}:{}/?action=stream".format(conf.conf.ServerIP, conf.conf.videoServerPort)))) self.mediaPlayer.play() 

Sekali lagi, saya minta maaf untuk tautologi). Saya memantau status koneksi video untuk koneksi ke server video. Dan jika, saya juga memberi tahu diri saya tentang masalah.


Ini adalah tampilannya ketika semuanya tidak berfungsi:


gambar


  • W - berarti tidak ada koneksi dengan wifi
  • B - berarti tidak ada video
  • Y - artinya tidak ada kontrol

Kalau tidak ada huruf merah, ada video dari kamera. Saya akan memposting foto dan video dengan pekerjaan di masa depan) Saya berharap bahwa mount untuk kamera akan datang dalam waktu dekat dan akhirnya saya akan memasangnya secara normal.


5 Mengkonfigurasi Raspberry OS


Ngomong-ngomong, bekerja dengan kamera dan hal-hal lain yang diperlukan harus dihidupkan (baik pada klien maupun pada server). Setelah memuat OS:


gambar


Dan nyalakan hampir semuanya: kamera, ssh, i2c, gpio


gambar


Demonstrasi


Hanya ada saluran video (kamera tetap bekerja). Saya minta maaf atas ketidakhadirannya, saya akan melampirkannya pada hari Senin.


Topi permainan

Pekerjaan video:



Kode sumber


Server dan kode sumber klien
Paket startup server daemon


Referensi


Bagian 1
Bagian 3

Source: https://habr.com/ru/post/id462331/


All Articles