لعبة GAZ-66 على لوحة التحكم. الجزء 2

صورة


في هذا الجزء سوف نتحدث عن مكون البرنامج ، كيف ظهر الجهاز. ما نظام التشغيل المستخدم ، اللغة التي تم اختيارها ، ما المشكلات التي واجهتها.


1. كيف يعمل في 2 الكلمات


يتكون النظام من خادم مثبت على آلة كاتبة ، وعميل مثبت على وحدة التحكم. يثير الخادم نقطة وصول wifi وينتظر حتى يتصل العميل. ينفذ الخادم أوامر العميل وينقل أيضًا الفيديو من الكاميرا إليه.


2. نظام التشغيل


الآن دعنا نتحدث عن أنظمة التشغيل المستخدمة.


نظرًا لأن النظام بأكمله يعتمد على Raspberry pi 3 ، فقد تم استخدام نظام التشغيل الرسمي له. في وقت الإنشاء ، كان الإصدار الأخير هو Stretch ، وتم اختياره للاستخدام على آلة كاتبة ولوحة تحكم. لكن اتضح أنه يحتوي على خطأ (معذّب لمدة أسبوع) بسبب أنه من المستحيل رفع نقطة وصول wifi. لذلك ، لرفع نقطة الوصول ، تم التقاط إصدار سابق من Jessie لم يكن لديه مثل هذه المشاكل.


المادة كيفية رفع نقطة وصول. مفصلة جدا ، فعلت كل شيء على ذلك.


يتصل جهاز التحكم عن بُعد تلقائيًا بالجهاز عندما يرفع نقطة الوصول.
يضيف الاتصال التلقائي إلى وجهة نظرنا ، في الملف / etc / network / واجهات :


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

2. اللغة


اخترت الثعبان لأنه سهل وبسيط.


3. الخادم


عن طريق الخادم في هذا القسم ، أعني البرنامج الذي كتبه لي للتحكم في الجهاز والتعامل مع الفيديو.


الخادم يتكون من 2 أجزاء. خادم الفيديو وخادم الإدارة.


3.1 خادم الفيديو


كان هناك 2 خيارات كيفية العمل مع كاميرا الفيديو. أول استخدام picamera module و 2nd استخدام mjpg- streamer البرمجيات . دون التفكير مرتين ، قررت استخدام كلاهما ، وأي منهما لاستخدامه في إعدادات التكوين.


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

نظرًا لأنهم يأخذون نفس الإعدادات ، فإنهم يعملون في نفس العنوان. لا توجد مشاكل في الاتصال بجهاز التحكم عن بعد عند التبديل من واحد إلى آخر. الشيء الوحيد الذي أعتقد أن mjpg-streamer يعمل بشكل أسرع.


3.2 خادم الإدارة


3.2.1 التفاعل بين العميل والخادم


أوامر تبادل الخادم والعميل في شكل سلاسل 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} 

  • النوع - "بعيد" أو "سيارة" حسب من يرسل الأمر (العميل أو الخادم)
  • cmd - سلسلة ذات اسم الزر المطابق لاسم الزر الموجود في Game HAT ، على سبيل المثال:
    • ابدأ - زر البدء
    • اختر - اختر زر
    • زر Y - Y
    • إلخ
    • بدوره - الأمر لتغيير حالة عصا التحكم ، هو المسؤول عن قلب العجلات
  • الحالة - صواب أو خطأ ، اعتمادًا على ما إذا كان الزر مضغوطًا أم لا. يتم إرسال حدث حالة زر في كل مرة تتغير حالتها.
  • val - سرعة واتجاه حركة المحرك من -1 ... 1 ، قيمة تعويم النوع. معلمة كبيرة لأزرار الحركة فقط.
  • x - انحراف ذراع التحكم على طول المحور x من -100 ... 100 ، قيمة type int
  • y - انحراف ذراع التحكم على طول المحور y من -100 ... 100 ، قيمة type int

يأتي بعد ذلك عاري ، لإعادة أيدي لا تصل. يقوم الجهاز برفع مقبس الخادم وينتظر حتى يتصل العميل به. علاوة على ذلك ، لكل اتصال جديد ، فإنه ينشئ دفقًا منفصلًا ، وسيتمكن كل عميل جديد سيتصل بالجهاز من التحكم فيه)). لا يمكن أن يكون هذا بعيدًا لأنه لا يوجد أحد لديه جهاز تحكم عن بعد ، وأرفع شبكة WiFi مغلقة.


 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 إدارة الحديد


عند العمل مع Raspberry ، تم استخدام نظام الترقيم GPIO.BCM .


يتم التحكم في الضوء عبر gpio 17 ، وهو متصل بالدبوس الثاني في L293 . بعد ذلك ، في كل مرة يأتي فيها الأمر لتشمل:


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

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

تسمى الأوامر المقابلة.


يتم التحكم في محرك سيرفو عبر لوحة PCA9685 عبر ناقل I2C ، لذلك نحن بحاجة إلى المكتبة المناسبة له Adafruit_PCA9685 . يتم توصيل PCA9685 إلى المؤازرة عبر 7 دبوس. تردد PWM المطلوب للعمل مع المؤازرة هو 50 هرتز أو فترة 20 مللي ثانية.


مبدأ تشغيل المؤازرة:


صورة


عند تطبيق إشارة 1.5 مللي ثانية ، سيتم توسيط العجلات. في 1 مللي ثانية. سيتحول المؤازر إلى أقصى حد ممكن إلى اليمين ، 2 مللي ثانية. إلى اليسار قدر الإمكان. لم يتم تصميم مفاصل التوجيه في الجسور لمثل هذه المنعطفات ، لذا يجب تحديد زاوية الدوران بشكل تجريبي.


القيم التي يمكن تمريرها إلى مجموعة Adafruit_PCA9685 API تتراوح من 0..4095 ، 0 بدون إشارة ، 4095 ممتلئة. وفقًا لذلك ، من هذا النطاق ، كان من الضروري اختيار القيم المناسبة لعجلاتي. أسهل طريقة لتحديد قيم العجلات المحددة بالضبط هي نقل 1.5 مللي ثانية إلى قيمة من مدى ~ 307.


القيمة القصوى لليمين هي 245 ، لليسار 369.


تأخذ القيم الواردة من عصا التحكم القيم من -100 ... 100 ، لذلك كان يجب ترجمتها في النطاق من 245 إلى 369. مرة أخرى ، يكون المركز هو الأسهل ، إذا كان 0 هو 307. اليسار واليمين وفقًا للصيغة:


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

  • HardwareSetting._turnCenter - 307
  • بدوره - قيمة من عصا التحكم من -100 ... 100
  • HardwareSetting._turnDelta - 62 ، والفرق بين المركز والحد الأقصى للانحراف إلى الجانب (307 - 245 = 62)
  • HardwareSetting.yZero - 100 ، القيمة القصوى المستلمة من عصا التحكم

عجلات مستقيمة:


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

الاتجاه يسارًا:


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

الاتجاه يمينًا:


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

يتم التحكم في المحرك أيضًا عبر لوحة PCA9685 عبر ناقل I2C ، لذلك نستخدم Adafruit_PCA9685 . يتم توصيل الدبابيس 10 إلى 15 على PCA9685 إلى L298N (يمكنني استخدام 2 قنوات عليه لامتصاص الطاقة). 10 و 11 إلى ENA و ENB (أملأهما بـ PWM للتحكم في السرعة). 12 و 13 و 14 و 15 إلى IN1 و IN2 و IN3 و IN4 - هي المسؤولة عن اتجاه دوران المحرك. تردد PWM ليس مهمًا للغاية هنا ، لكنني أستخدم أيضًا 50 هرتز (القيمة الافتراضية).


الجهاز لا يزال قائما:


 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) 

المضي قدما:


 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) 

الحركة الخلفية:


 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. العملاء


4.1 لوحة المفاتيح


كانت هناك مشاكل معينة معها ، في البداية أردت أن أجعلها مليئة بالأحداث (استغرق الأمر حوالي أسبوعين من العذاب). لكن الأزرار الميكانيكية التي ساهمت بها ، أدت قعقعة جهات الاتصال إلى إخفاقات ثابتة وغير متوقعة (خوارزميات التحكم التي اخترعتها عملت بشكل غير كامل). ثم أخبرني زميلي كيف تصنع لوحات المفاتيح. وقررت أن أفعل نفس الشيء ، والآن أقوم باستطلاع الحالة كل 0.005 ثانية (لماذا ، ومن يدري). وإذا كان قد تغير ، أرسل القيمة إلى الخادم.


 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 عصا التحكم


تتم قراءة القراءات عبر لوحة ADS1115 عبر حافلة I2C ، وبالتالي فإن المكتبة المناسبة لها هي Adafruit_PCA9685 . عصا التحكم أيضًا عرضة لثرثرة جهات الاتصال ، لذلك أأخذ قراءات منها من خلال القياس باستخدام لوحة المفاتيح.


 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) 

عند تشغيله من 3.3 فولت ، فإن مجموعة القيم التي تقدمها ADS1115 بها ذراع تحكم من 0 ... 26500. أحمل هذا إلى مجموعة من -100 ... 100. في النطاق الخاص بي حول 0 يتقلب دائمًا ، لذلك إذا كانت القيم لا تتجاوز 5 ، فأنا أعتبر أنها 0 (وإلا فسوف تتدفق). بمجرد أن تتغير القيم ، أرسلها إلى الآلة الكاتبة.


4.3 الاتصال بخادم الإدارة


الاتصال بالخادم شيء بسيط:


 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) 

لكنني أريد الانتباه إلى شيء واحد. إذا لم تستخدم المهلة في الاتصال ، فيمكنها التجميد وسيكون عليك الانتظار بضع دقائق (يحدث هذا عندما يبدأ العميل قبل الخادم). لقد قمت بحل هذا بالطريقة التالية ، قمت بتعيين مهلة للاتصال. بمجرد حدوث الاتصال ، أقوم بإزالة المهلة.


يمكنني أيضًا تخزين حالة الاتصال ، حتى أعرف ما إذا كان التحكم قد فقد وعرضه على الشاشة.


4.4 التحقق من اتصال WiFi


أتحقق من حالة wifi للاتصال بالخادم. وإذا كان الأمر كذلك ، فأنا أخطر نفسي أيضًا بالمشاكل.


 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 الاتصال بخادم الفيديو


لهذا ، كانت كل قوة Qt5 مطلوبة ، بالمناسبة على توزيع الإمتداد ، فهي أحدث وفي رأيي تظهر بشكل أفضل. في جيسي حاولت أيضا.


للعرض اعتدت:


 self.videoWidget = QVideoWidget() 

واستنتج:


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

الاتصال ببث الفيديو:


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

مرة أخرى ، أعتذر عن التحريف). أراقب حالة اتصال الفيديو للاتصال بخادم الفيديو. وإذا كان الأمر كذلك ، فأنا أخطر نفسي أيضًا بالمشاكل.


هكذا يبدو عندما لا يعمل كل شيء:


صورة


  • W - يعني أنه لا يوجد اتصال مع واي فاي
  • ب - يعني عدم وجود فيديو
  • Y - يعني أنه لا يوجد تحكم

خلاف ذلك لا توجد رسائل حمراء ، وهناك شريط فيديو من الكاميرا. سأنشر صورة وفيديو مع العمل في المستقبل) آمل أن يأتي تركيب الكاميرا في المستقبل القريب وأرفقها أخيرًا بشكل طبيعي.


5 تكوين نظام تشغيل التوت


بالمناسبة ، يجب تشغيل العمل مع الكاميرا والأشياء الضرورية الأخرى (سواء على العميل أو الخادم). بعد تحميل نظام التشغيل:


صورة


قم بتشغيل كل شيء تقريبًا: الكاميرا ، ssh ، i2c ، gpio


صورة


مظاهرة


لا يوجد سوى قناة فيديو (تظل الكاميرا في العمل). أعتذر عن غيابه ، سأرفقه يوم الاثنين.


قبعة اللعبة

عمل الفيديو:



شفرة المصدر


الخادم ورمز العميل المصدر
حزمة بدء تشغيل خادم الخفي


مراجع


الجزء 1
الجزء 3

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


All Articles