اكتب برنامجًا لمنفذ com. إعادة توجيه البيانات من منفذ COM إلى الويب

يحب المطورون المنافذ التسلسلية لسهولة صيانتها واستخدامها.

وبالطبع ، الكتابة إلى وحدة التحكم الخاصة بالبرنامج الطرفي أمر جيد ، لكنني أريد تطبيقي الخاص ، والذي يؤدي ، بالضغط على مفتاح على الشاشة ، إلى تنفيذ الإجراءات التي تحتاجها ؛)

في هذه المقالة سوف أصف كيفية العمل مع منفذ com بلغة C ++.

الحل بسيط ، ولكن لسبب ما لم يتم العثور على مثال عملي على الفور. بالنسبة إلى sim ، أحفظه هنا.

بالطبع ، يمكنك استخدام حلول عبر الأنظمة الأساسية مثل QSerial - مكتبة في Qt ، ربما سأفعل ، لكن في المستقبل. الآن نحن نتحدث عن Windows "خالص" C ++. سنكتب إلى استوديو مرئي. لدي عام 2010 ، رغم أن هذا لا يلعب أي دور ...

إنشاء مشروع Win32 وحدة تحكم جديد.

تضمين ملفات الرأس:

#يشمل #يشمل استخدام اسم للمحطة؛

نعلن عن معالج منفذ com:

مقبض hSerial ؛

أفعل ذلك عالميًا ، لذا لا داعي للقلق بشأن المؤشرات عند تمريرها إلى الوظائف.

int _tmain (int argc، _TCHAR * argv) (

لا أستطيع تحمل أسلوب البرمجة في Windows. دعوا كل شيء على طريقتهم وجلسوا مبتهجين ...

الآن سحر إعلان سلسلة باسم المنفذ. الأمر هو أنه غير قادر على تحويل شار نفسه.

LPCTSTR sPortName = L "COM1" ؛

يعمل العمل مع المنافذ التسلسلية في Windows مثل العمل مع ملف. الافتتاح الأول منفذ com للكتابة / القراءة:

HSerial = :: CreateFile (sPortName، GENERIC_READ | GENERIC_WRITE، 0،0، OPEN_EXISTING، FILE_ATTRIBUTE_NORMAL، 0) ؛

التحقق من الوظيفة:

إذا (hSerial == INVALID_HANDLE_VALUE) (إذا (GetLastError () == ERROR_FILE_NOT_FOUND) (cout<< "serial port does not exist.\n"; } cout << "some other error occurred.\n"; }

أنت الآن بحاجة إلى تكوين معلمات الاتصال:

DCB dcbSerialParams = (0) ؛ dcbSerialParams.DCBlength = sizeof (dcbSerialParams) ؛ إذا (! GetCommState (hSerial، & dcbSerialParams)) (cout<< "getting state error\n"; } dcbSerialParams.BaudRate=CBR_9600; dcbSerialParams.ByteSize=8; dcbSerialParams.StopBits=ONESTOPBIT; dcbSerialParams.Parity=NOPARITY; if(!SetCommState(hSerial, &dcbSerialParams)) { cout << "error setting serial port state\n"; }

في msdn ، يُنصح بالحصول على المعلمات أولاً ، ثم تغييرها. ما زلنا نتعلم ، لذلك نقوم بذلك على النحو المطلوب.

الآن دعنا نعلن عن السلسلة التي سنمررها والمتغيرات اللازمة لذلك:

بيانات Char = "Hello from C ++" ؛ // سلسلة لتمرير DWORD dwSize = sizeof (بيانات) ؛ // حجم هذه السلسلة DWORD dwBytesWritten ؛ // هنا سيكون عدد البايتات التي تم نقلها بالفعل

إرسال سلسلة. دعني أذكرك أن المثال هو الأبسط ، لذلك لا أقوم بأي عمليات تحقق خاصة:

BOOL iRet = WriteFile (hSerial، data، dwSize، & dwBytesWritten، NULL) ؛

قررت أيضًا عرض حجم السلسلة وعدد البايتات المرسلة للتحكم:

كوت<< dwSize << " Bytes in string. " << dwBytesWritten << " Bytes sended. " << endl;

في نهاية البرنامج ، نقوم بعمل حلقة لا نهائية لقراءة البيانات:

بينما (1) (ReadCOM () ؛) تُرجع 0 ؛ )

الآن وظيفة القراءة:

Void ReadCOM () (DWORD iSize؛ char sReceivedChar؛ while (true) (ReadFile (hSerial، & sReceivedChar، 1، & iSize، 0)؛ // احصل على 1 بايت إذا (iSize> 0) // print cout إذا تم استلام شيء ما<< sReceivedChar; } }

هذا في الواقع هو المثال الكامل.

  • تحليل نشاط المنفذ التسلسلي

    يمكن لـ Serial Port Monitor الاتصال بمنفذ COM ، حتى إذا كان مفتوحًا بالفعل بواسطة بعض التطبيقات ، والبدء فورًا في مراقبته. سيتم عرض جميع البيانات التي تمر عبر منفذ COM المراقب في برنامج المراقبة الخاص بنا. نظرًا لأنه يتم تسجيل كل شيء في الوقت الفعلي ، ستتمكن من اكتشاف المشكلات على الفور. لمقارنة البيانات ، توجد وظيفة الاختيار المتزامن لنفس عمليات البحث المستقلة في طرق عرض مختلفة.

    بالإضافة إلى ذلك ، يمكنك إعادة توجيه جميع بيانات المراقبة إلى ملف محدد أو نسخ جميع البيانات المسجلة إلى الحافظة. يمنحك Serial Port Monitor القدرة على اعتراض وتسجيل جميع رموز التحكم في إدخال / إخراج المنفذ التسلسلي (IOCTL) ، ومراقبة جميع بياناتها ومعلماتها. يمكنك حفظ أي جلسة مراقبة وتحميلها في المرة القادمة إذا لزم الأمر.

  • مراقبة منافذ متعددة في نفس الجلسة

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

  • خيارات مختلفة لعرض البيانات المستلمة

    يمكنك عرض بيانات المراقبة في 4 أوضاع في وقت واحد: الجدول أو الصف أو التفريغ أو المحطة الطرفية ، كل منها يوفر طريقة مختلفة لعرض البيانات التسلسلية المسجلة. يسمح لك Serial Port Monitor بتحديد مرشحات المراقبة ، مما يوفر لك الوقت ويسمح لك بمراقبة الأحداث التي تهمك فقط. في الإعدادات ، يمكنك تحديد البيانات المراد عرضها: ثنائي ، ASCII ، تكوين المنفذ. يمكن تطبيق أي إعدادات عرض مباشرة في عملية المراقبة الحالية.

  • محاكاة نقل البيانات إلى جهاز تسلسلي

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

  • دعم كامل لبروتوكول بيانات Modbus (RTU و ASCII)

    بمساعدة مرشحات Serial Port Monitor الجديدة ، ستتمكن من فك تشفير بيانات Modbus وتحليلها. سيساعد البرنامج ليس فقط في إنشاء اتصال بين أجهزة RS485 / 422/232 ، ولكن أيضًا إجراء تحليل فعال لتمرير البيانات.

  • إعادة ومقارنة جلسات المراقبة

    يوفر Serial Port Monitor فرصة فريدة لإعادة تشغيل الجلسة من التطبيق إلى المنفذ للحصول على أفضل تحليل للعمليات الجارية. ستتمكن من مراقبة رد فعل المنفذ التسلسلي على مرور نفس البيانات ، وبالتالي زيادة كفاءة المراقبة. لديك أيضًا خيار مقارنة جلسات المراقبة المتعددة وتتبع الفرق بينها تلقائيًا.

حول كيفية تمثيل البيانات المرسلة من Arduin إلى Serial بشكل جميل. في رأيي ، قدم الرجال حلاً جميلًا للغاية ، والذي يبدو من ناحية بسيطًا جدًا ، ومن ناحية أخرى ، يسمح لك بالحصول على نتيجة ممتازة بأقل جهد.

في التعليقات على المقالة ، أُعرب عن الأسف لأن مثل هذا الحل لن ينجح في ظل فايرفوكس ، وتم التعبير عن فكرة أنه "لا يزال بإمكانك كتابة خادم ويب بسيط بإخراج html بناءً على هذا الشيء". هذه الفكرة "شدني" ، بحث سريع على جوجل لم يعطِ حلاً جاهزًا ، وقررت أن أنفذ الفكرة بنفسي. وها هو ما خرج منها.

تحذير! لا ينبغي بأي حال من الأحوال اعتبار الحل المقترح كاملاً. على عكس جهاز العرض التسلسلي من Amperka ، يعد هذا مفهومًا وإثباتًا لنهج محتمل ونموذج أولي عملي ولا شيء أكثر من ذلك.

منذ بعض الوقت ، قمت بمشروع استخدمت فيه مقاييس التسارع المدمجة في هاتف ذكي يعمل بنظام Android للتحكم في الماكينات المتصلة بـ Arduino. ثم لهذه الأغراض ، استخدمت مشروع Scripting Layer لنظام Android (SL4A) و RemoteSensors. اتضح أن مكتبة Python القياسية تتضمن حزمة BaseHTTPServer ، والتي يمكن من خلالها رفع خدمة ويب في Python مهمة في سطرين من التعليمات البرمجية.

لم تكن هناك أجهزة استشعار لـ Arduino في متناول اليد ، لذلك استخدمت مقياس الحرارة الداخلي المدمج في Arduino Uno كمصدر للمعلومات المعروضة. بقدر ما أفهم ، فهو ليس دقيقًا جدًا ولا يُقصد به على الإطلاق قياس درجة الحرارة المحيطة ، ولكنه سيفي بالغرض للنماذج الأولية.

بعد بحث قصير على googling ، إليك رسم تخطيطي لاردوينو:

// المصدر: https://code.google.com/p/tinkerit/wiki/SecretThermometer long readTemp () (نتيجة طويلة ؛ // قراءة مستشعر درجة الحرارة مقابل مرجع 1.1V ADMUX = _BV (REFS1) | _BV (REFS0) | _BV (MUX3)؛<<8; result = (result - 125) * 1075; return result; } void setup() { Serial.begin(115200); } int count = 0; void loop() { String s = String(count++, DEC) + ": " + String(readTemp(), DEC); Serial.println(s) delay(1000); }
يفتح هذا المخطط منفذ COM ، ويضبطه على 115200 باود ، ثم يكتب القيمة الحالية لميزان الحرارة المدمج إليه كل ثانية. (لا تسألني عن الوحدات المعطاة لدرجة الحرارة - بالنسبة للمهمة الموصوفة ، فهي ليست مهمة). نظرًا لأن القيمة لا تتغير بنشاط كبير ، من أجل رؤية أفضل لتغييرات البيانات ، يتم عرض رقم السطر قبل درجة الحرارة.

للتحقق من أن خادم الويب سيعطي سطورًا كاملة فقط ، وليس أجزاء منها ، كما يقرأ من منفذ COM ، الخط
المسلسل. println (ق)
تم استبداله بـ
لـ (int i = 0 ؛ i< s.length(); i++){ Serial.print(s.charAt(i)); delay(200); } Serial.println("");
أولئك. لا يتم إخراج السلسلة التي تم تكوينها إلى المنفذ التسلسلي بالكامل ، ولكن يتم إخراجها حرفًا بحرف ، مع توقف مؤقت قدره 200 مللي ثانية.

بادئ ذي بدء ، تمت كتابة نموذج أولي بسيط جدًا لخادم الويب (أدناه مفكك في أجزاء):
# - * - الترميز: utf-8 - * - # - بناءً على: https://raw.githubusercontent.com/Jonty/RemoteSensors/master/remoteSensors.py SERIAL_PORT_NAME = "COM6" SERIAL_PORT_SPEED = 115200 WEB_SERVER_PORT = 8000 وقت استيراد ، BaseHTTPServer، urlparse import serial ser = None def main (): global ser httpd = BaseHTTPServer.HTTPServer ((""، WEB_SERVER_PORT)، Handler) # - الحل البديل للحصول على عنوان IP الذي يخدم فيه مقبس الاستيراد = socket.socket ( socket.AF_INET، socket.SOCK_DGRAM) s.connect (("google.co.uk"، 80)) sData = s.getsockname () طباعة "العرض في"٪ s:٪ s ""٪ (sData، WEB_SERVER_PORT) ser = serial.Serial (SERIAL_PORT_NAME، SERIAL_PORT_SPEED، المهلة = 0) معالج فئة httpd.serve_forever () (BaseHTTPServer.BaseHTTPRequestHandler): # تعطيل عمليات بحث DNS للتسجيل def address_string (self): return str (self.client_address) def (def_GET) self.send_response (200) self.send_header ("نوع المحتوى"، "application / x-javascript؛ charset = utf-8") self.end_headers () جرب: while True: new_serial_line = get_full_line_from_serial () إذا لم يكن new_serial_line بلا: self.wfile.write (new_serial_line) self.wfile.write ("\ n") self.wfile.flush () باستثناء خطأ socket. error ، e: print "Client disconnected. \ n" أسر = "" def get_full_line_from_serial (): "" "إرجاع السطر الكامل من المسلسل أو لا شيء يستخدم المتغيرات العالمية" ser "و" التقاطها "" "global capture part = ser.readline () if part: capture + = part parts = capture.split ("\ n"، 1)؛ إذا كان len (الأجزاء) == 2: تم التقاطها = إرجاع الأجزاء إرجاع لا شيء إذا __name__ == "__main__": main ()
دعونا نقسم النص قطعة قطعة.

نظرًا لأن هذا نموذج أولي ، فإن جميع معلمات التشغيل الرئيسية (اسم منفذ COM وسرعته بالإضافة إلى رقم منفذ TCP الذي سيعمل عليه خادم الويب) يشار إليها مباشرة في النص المصدر:
SERIAL_PORT_NAME = "COM6" SERIAL_PORT_SPEED = 115200 WEB_SERVER_PORT = 8000
بالطبع ، يمكنك الترتيب لقراءة هذه المعلمات من سطر الأوامر. على سبيل المثال ، بمساعدة وحدة argparse ، يتم ذلك بسرعة كبيرة وبساطة ومرونة.

في هذه الحالة ، يحتاج مستخدمو Windows إلى معرفة اسم منفذ COM الذي يتصل به Arduin في مدير الجهاز. بالنسبة لي كان "COM6". يحتاج مستخدمو أنظمة التشغيل الأخرى إلى استخدام أدوات نظام التشغيل الخاص بهم. ليس لدي أي خبرة مع MacOS على الإطلاق ولم أعمل مع منافذ COM على Linux أيضًا ، ولكن هناك ، على الأرجح ، سيكون شيئًا مثل "/ dev / ttySn".

يأتي بعد ذلك تعريف المتغير العام الذي سيتم ربط مثيل من الفئة التسلسلية ، والذي يكون في بيثون مسؤولاً عن العمل مع منفذ COM:
ser = لا شيء
في النسق
httpd = BaseHTTPServer.HTTPServer (("" ، WEB_SERVER_PORT) ، معالج)
يتم إنشاء خادم ويب يستمع للطلبات على المنفذ المحدد WEB_SERVER_PORT. وسيتم التعامل مع هذه الطلبات من خلال مثيل لفئة Handler ، الموضحة أدناه.

الأسطر التالية عبارة عن "اختراق" بسيط لعرض عنوان IP الذي يعمل عليه خادم الويب قيد التشغيل بالفعل:
# - الحل البديل للحصول على عنوان IP حيث يتم تقديم مقبس الاستيراد = socket.socket (socket.AF_INET، socket.SOCK_DGRAM) s.connect (("google.co.uk"، 80)) sData = s.getsockname () طباعة "العرض في"٪ s:٪ s ""٪ (sData ، WEB_SERVER_PORT)
بقدر ما أفهم ، لا توجد طريقة أخرى لمعرفة عنوان IP هذا. وبدون هذه المعرفة ، كيف سنصل إلى خادمنا من المتصفح؟

لذلك ، يجب عليك فتح مقبس والاتصال بموقع Google لاستخراج معلومات حول عنوان IP الخاص بك من سمات هذا المقبس.

أقل قليلاً ، يتم فتح منفذ COM ويتم تشغيل خادم الويب بالفعل:
ser = serial.Serial (SERIAL_PORT_NAME ، SERIAL_PORT_SPEED ، المهلة = 0) httpd.serve_forever ()
يتبع ذلك وصف للفئة المسؤولة عن معالجة الطلبات التي يتلقاها خادم الويب قيد التشغيل:
معالج الفئة (BaseHTTPServer.BaseHTTPRequestHandler):
هذا وريث للفئة المضمنة في الوحدة النمطية BaseHTTPServer ، حيث يكفي تجاوز طريقة do_GET فقط

نظرًا لأن هذا لا يزال نموذجًا أوليًا ، فسيكون الخادم "سعيدًا" بأي طلب - بغض النظر عن عنوان URL المطلوب منه ، فإنه سيمنح العميل جميع البيانات التي تمت قراءتها من منفذ COM. لذلك ، في Handler.do_GET ، يستجيب على الفور برمز نجاح والرؤوس الضرورية:
self.send_response (200) self.send_header ("نوع المحتوى"، "application / x-javascript؛ charset = utf-8") self.end_headers ()
بعد ذلك يتم تشغيل حلقة لا نهائية ، حيث يتم إجراء محاولة لقراءة سطر كامل من منفذ COM ، وإذا نجحت هذه المحاولة ، فقم بنقله إلى عميل الويب:
بينما صحيح: new_serial_line = get_full_line_from_serial () إذا لم يكن new_serial_line بلا: self.wfile.write (new_serial_line) self.wfile.write ("\ n") self.wfile.flush ()
في المشروع الذي تم أخذه كأساس ، تم "تغليف" هذه الحلقة اللانهائية في محاولة ... باستثناء الكتلة ، والتي كان من المفترض أن تتعامل معها بعناية مع انقطاع الاتصال. ربما في Android (تم تطوير المشروع الأساسي له) ، يعمل هذا بشكل جيد ، لكنه لم ينجح بالنسبة لي في Windows XP - عندما تم قطع الاتصال ، حدث استثناء آخر ، ولم أتعلم أبدًا كيفية اعتراضه. لكن لحسن الحظ ، لم يمنع هذا خادم الويب من العمل بشكل طبيعي وقبول الطلبات التالية.

تعمل وظيفة الحصول على سلسلة كاملة من منفذ COM على نفس مبدأ منشئي Serial Projector:

  • هناك بعض المخزن المؤقت العالمي الذي يخزن كل ما يتم قراءته من منفذ COM
  • في كل مرة يتم استدعاء الوظيفة ، تحاول قراءة شيء ما من منفذ COM
  • إذا نجحت ، إذن
    • يضيف ما تمت قراءته للتو إلى المخزن المؤقت العالمي المحدد
    • يحاول تقسيم المخزن المؤقت العام إلى جزأين على الأكثر بحرف نهاية السطر
    • إذا نجح ، فإنه يعيد الجزء الأول إلى إجراء الاستدعاء ، ويستخدم الجزء الثاني كقيمة جديدة للمخزن المؤقت العالمي
  • إذا لم تكن هناك بيانات جديدة في منفذ COM أو لم يتم العثور على حرف نهاية السطر ، فإن الوظيفة ترجع بلا:
أسر = "" def get_full_line_from_serial (): "" "إرجاع السطر الكامل من المسلسل أو لا شيء يستخدم المتغيرات العالمية" ser "و" التقاطها "" "global capture part = ser.readline () if part: capture + = part parts = capture.split ("\ n"، 1)؛ إذا كان len (الأجزاء) == 2: تم التقاطها = إرجاع الأجزاء إرجاع الأجزاء بلا
نتيجة لذلك ، اتضح مثل هذا:

يمكن ملاحظة ظهور خطوط في المتصفح تتم قراءتها من منفذ COM. لا أفهم أي شيء عن واجهة الويب: JavaScript و Ajax و CSS و DOM هي غابة مظلمة بالنسبة لي. ولكن يبدو لي أنه بالنسبة للمبرمجين الذين يقومون بإنشاء واجهات ويب ، يجب أن يكون هذا كافيًا لتحويل هذا الإخراج إلى نفس الصورة الجميلة التي ينتجها جهاز العرض التسلسلي من Amperka. في رأيي ، تتمثل المهمة في إنشاء برنامج نصي جافا سكريبت يصل إلى خادم الويب ، ويقرأ تدفقًا منه ، ويخرج آخر سطر تمت قراءته إلى المكان الصحيح على صفحة الويب.

فقط في حالة ، قررت تشغيلها بأمان وحاولت إجراء التقدير التقريبي الأول بمفردي. اقترح بحث غير عميق في Google أنه ، في الواقع ، لمثل هذه الأغراض ، على الأقل ، تم استخدام WebSockets أو الأحداث المرسلة من الخادم. لقد وجدت ما بدا لي استخدامًا جيدًا لـ Server-Sent Events وقررت استخدام هذه التقنية.

ملحوظة! لا يبدو أن هذا هو الحل الأفضل ، لأن هذه التقنية لم تعمل في Internet Explorer 8 ، ولا في المتصفح المدمج في Android 2.3.5. لكنها نجحت على الأقل في Firefox 39.0 ، لذلك لم "أحفر" أكثر.

من وجهة نظر نص Python النصي ، فإن التغييرات ضمن الأحداث المرسلة من الخادم طفيفة تمامًا:

  • من الضروري استبدال نوع البيانات المعطاة للعميل:
    خط
    self.send_header ("نوع المحتوى" ، "application / x-javascript ؛ charset = utf-8")
    وحل محله
    self.send_header ("نوع المحتوى" ، "نص / بث الحدث")
  • وأيضًا قبل قراءة السطر من منفذ COM ، أدخل البادئة "data:" وأضف حرف سطر جديد آخر:
    خطوط
    self.wfile.write (new_serial_line) self.wfile.write ("\ n")
    وحل محله
    self.wfile.write ("data:" + new_serial_line) self.wfile.write ("\ n \ n")

من المحتمل أن يظل كل شيء آخر دون تغيير ، لكن ...

أولاً ، قمت بإنشاء ملف index.html بالمحتوى التالي:

رأس




الأكثر إثارة للاهتمام في ذلك هو الخط
الذي يشكل مكانًا لإخراج السطر التالي من منفذ COM ، ونص جافا سكريبت

الذي يقرأ في الواقع الدفق من خادم الويب ويخرج معلومات القراءة إلى الموقع المحدد.

كنت أنوي فتح هذا الملف في متصفح ، على سبيل المثال ، من قرص أو من خادم ويب آخر ، لكن هذا لم ينجح: عند فتح صفحة من القرص ، وصل نص جافا سكريبت مرة واحدة إلى خادم الويب Python قيد التشغيل وفصل الاتصال على الفور. لم أفهم سبب حدوث ذلك ، واقترحت أن هذا قد يكون نوعًا من حماية المتصفح ضد الهجمات المختلفة. ربما لا يحب أن يتم فتح الصفحة نفسها من مصدر واحد ، وأن البرنامج النصي يقرأ البيانات من مصدر آخر.

لذلك ، تقرر تغيير خادم الويب Python بحيث يخدم أيضًا صفحة html هذه. ثم يتضح أن كلاً من الصفحة والدفق تتم قراءتهما من نفس المصدر. لا أعرف ما إذا كان افتراضي بشأن الأمان صحيحًا أم شيء آخر ، ولكن مع هذا التطبيق ، كل شيء سار كما ينبغي.

بالطبع ، ما عليك سوى تغيير فئة معالج طلب المعالج:
معالج الفئة (BaseHTTPServer.BaseHTTPRequestHandler): # تعطيل تسجيل عمليات بحث DNS def address_string (self): إرجاع str (self.client_address) def do_GET (self): if self.path == "/" or self.path == "/ index .html ": self.process_index () elif self.path ==" / get_serial ": self.process_get_serial () else: self.process_unknown () def process_index (self): self.send_response (200) self.send_header (" المحتوى -type "،" text / html؛ charset = utf-8 ") self.end_headers () self.wfile.write (open (" index.html "). read ()) self.wfile.write (" \ n \ n ") self.wfile.flush () def process_get_serial (self): self.send_response (200) self.send_header (" Content-type "،" text / event-stream ") self.end_headers () حاول: while True: new_serial_line = get_full_line_from_serial () إذا لم يكن new_serial_line بلا: self.wfile.write ("data:" + new_serial_line) self.wfile.write ("\ n \ n") self.wfile.flush () باستثناء خطأ socket.e : طباعة "Client disconnected. \ n" def process_unknown (self): self.send_response (404)
يفترض هذا الخيار أن خادم الويب سيستجيب فقط لطلبيْن: "/index.html" (مع إعطاء كود html للصفحة) و "get_serial" (إعطاء دفق لا نهائي من السلاسل المقروءة من منفذ COM). سيتم الرد على جميع الطلبات الأخرى برمز 404.

نظرًا لأن index.html يتم تقديمه بواسطة خادم الويب Python ، فيمكن تعديله قليلاً عن طريق تحديد نسبي بدلاً من العنوان المطلق لدفق السلسلة من منفذ COM:
خيط
var source = new EventSource ("http://192.168.1.207:8000/")
وحل محله
var source = new EventSource ("/ get_serial")
نتيجة لذلك ، اتضح مثل هذا:

هذا هو المكان الذي قررت فيه التوقف. يبدو لي أن تصميم الصفحة بشكل جميل يجب أن يكون بالفعل بسيطًا للغاية. لكنني لست بارعًا في HTML أو CSS ، لذا دع شخصًا آخر يفعل ذلك. لقد رأيت مهمتي في إظهار أن إنشاء خدمة ويب ترسل بيانات من منفذ COM ، على ما يبدو ، ليس بالأمر الصعب على الإطلاق.

أكرر مرة أخرى: الكود المقدم ليس حلاً كاملاً يمكن "وضعه قيد الإنتاج". هذا ليس سوى نموذج أولي يظهر نهجًا أساسيًا لحل المشكلة.

ما الأشياء الأخرى التي يمكن العمل عليها:

  • أولاً ، قراءة البيانات من منفذ COM في نص Python تتم بشكل "أخرق" - في الواقع ، هناك استقصاء مستمر "هل هناك شيء جديد؟". هذا النهج ، بالطبع ، يقوم بتحميل المعالج ويتم احتلال نواة واحدة على جهاز الكمبيوتر بنسبة 100 ٪.
    يمكن استخدام قراءة الحظر مع انتهاء المهلة كحل. للقيام بذلك ، يكفي تحديد قيمة غير صفرية (بالثواني) كمهلة عند فتح منفذ COM ، على سبيل المثال:
    ser = serial.Serial (SERIAL_PORT_NAME ، SERIAL_PORT_SPEED ، المهلة = 0.03)
    بالإضافة إلى ذلك ، في وصف وحدة pySerial ، هناك ثلاثة أمثلة لإنشاء جسر: "TCP / IP - جسر تسلسلي" و "منفذ واحد TCP / IP - جسر تسلسلي (RFC 2217)" و "منفذ TCP متعدد المنافذ / IP - الجسر التسلسلي (RFC 2217) "- يمكنك أن ترى كيف يحل المحترفون مثل هذه المشاكل.
  • ثانيًا ، يمكن لعميل واحد فقط تلقي البيانات. حتى يتم إغلاق الصفحة على العميل الأول ، لا يمكنك الاتصال بهذا الخادم والحصول على القيم على الكمبيوتر الثاني. من ناحية أخرى ، ربما يكون هذا صحيحًا: لا يوجد سوى منفذ COM واحد ، وهناك العديد من المستهلكين - أي منهم يجب أن يعطي سطر القراءة؟ إذا كنت تعتقد أن الإجابة على هذا السؤال يجب أن تكون "الجميع" ، فإليك أفكاري في هذا الشأن. يبدو لي أنه لا يمكن حل المشكلة إلا باستخدام خادم ويب متعدد الخيوط "صادق" (على سبيل المثال ، بعض Tornado أو Flask) ، والذي يمكنه تقديم الطلبات من العديد من عملاء الويب في نفس الوقت. نظرًا لأنه لا يمكنك فتح منفذ COM من كل مؤشر ترابط والقراءة منه - في هذه الحالة ، ستنتقل البيانات من منفذ COM إلى مؤشر ترابط / عملية واحدة فقط. لذلك ، في رأيي ، من الضروري تقسيم جزء الخادم إلى قسمين:
    • خادم zmq الذي يعمل مع منفذ COM ، يقرأ الأسطر منه ويرسلها عبر مقبس PUB إلى جميع المستهلكين المهتمين
    • خادم الويب python بدلاً من الاتصال بمنفذ COM يتصل بخادم zmq ويستقبل البيانات منه
    إذا لم تكن معتادًا على مكتبة ZMQ (ZeroMQ) ، فيمكنك استخدام مآخذ TCP / IP أو UDP عادية بدلاً من ذلك ، لكنني أوصي بشدة بالتعرف على ZMQ ، لأن هذه المكتبة تجعل من السهل جدًا حل مثل هذه المشكلات. يبدو لي أنه بمساعدة ZMQ ، سيتناسب الحل في 20 سطرًا كحد أقصى. (لا يمكنني مقاومة الكتابة: حتى إذا كنت لا تخطط لحل المهمة الموصوفة ، لكن عملك مرتبط ببرمجة متعددة الخيوط / عمليات متعددة مع تبادل البيانات بين الخيوط / العمليات ، ألق نظرة فاحصة على هذه المكتبة - ربما هذا ما كنت تتحدث عنه منذ فترة طويلة تحلم)
  • لا يزال تدفق البيانات أحادي الاتجاه - من منفذ COM إلى متصفح الويب. لا يمكنك بعد إرسال البيانات من المتصفح إلى Arduino. يبدو لي أن هذه المهمة ليست صعبة للغاية أيضًا ، وعلى عكس المهمة السابقة ، يمكن حلها فقط
    • باستخدام خادم متعدد الخيوط
    • تحسين أسلوب Handler.do_GET بحيث يقبل طلبات GET مع المعلمات ويرسل قيم معينة منها إلى منفذ COM
    في رأيي ، إذا كنت تريد كتابة تناظرية كاملة لشاشة المنفذ التسلسلي المستندة إلى الويب والمدمجة في Arduino IDE ، فهذا ليس بالأمر الصعب. شخصيًا ، بالنسبة لي ، أرى صعوبة فقط في إنشاء واجهة أمامية عادية.
  • ليس من الممكن بعد تعيين اسم منفذ COM ومعلمات تشغيله من خلال المستعرض. من ناحية ، يبدو هذا منطقيًا: كيف يمكن لمستخدم على الجانب الآخر من كوكبنا معرفة منفذ COM وبأي سرعة يتصل بها اردوينو؟ لكن خادم الويب Python الذي يعمل على نفس الكمبيوتر يعرف بالتأكيد. ولكن إذا كان لا يزال من المرغوب فيه منح المستخدم الفرصة لتغيير اسم منفذ COM أو معلمات تشغيله ، فيمكن حل هذا بسهولة مرة أخرى عن طريق تحسين طريقة Handler.do_GET
  • مطلوب بيثون لتشغيل الخادم. هذا ليس بالأمر الصعب بشكل عام ، ولكن إذا كان لا يمكن القيام بذلك لسبب ما أو إذا كنت لا ترغب في ذلك ، فيمكن لـ pyInstaller أن ينقذ. باستخدامه ، يمكن تجميع نص Python في ملف واحد قابل للتنفيذ (في حالة Windows - في. exe) ، والذي يسهل نسخه إلى الكمبيوتر الذي يتصل به اردوينو.
    ربما يكون أفضل حل هو استخدام لغة Go في هذه الحالة. بقدر ما أعرف ، فإنه يحل مشكلة إنشاء ملف من أجل "التوزيع" بشكل أفضل.
في الختام: قد يطرح السؤال: "أليس من الأسهل حل هذه المشكلة من خلال بعض السحابة الجاهزة؟". لماذا لا تنشر بيانات يمكن قراءتها من منفذ COM في السحابة ، وعلى العملاء الوصول ببساطة إلى الخدمة المقابلة في السحابة؟ من المحتمل أن يكون لهذا الحل أيضًا الحق في الوجود ، ولكن قبل تطبيق هذا الحل ، يجب الإجابة على الأسئلة التالية:
  • هل هناك خدمات ويب جاهزة تسمح لي بنشر البيانات بالسرعة / التردد الذي أحتاجه؟ هل من بينهم مجانيون أم أنك مستعد لدفع المال المقابل؟
  • هل أنت مستعد لحقيقة أنه في حالة سقوط السحابة أو الاتصال بها ، ستترك بدون بيانات
  • ألا يزعجك أنه من أجل نقل البيانات من غرفة إلى أخرى ، سوف يعبرون المحيط أو نصف قارة مرتين؟


تحميل...
قمة