في عام 2017 ، بدأت في كتابة مشروع على nodejs - تطبيق بروتوكول Weinzierl ObjectServer للوصول إلى قيم KNX. أثناء عملية الكتابة ، درسنا: العمل مع البروتوكولات الثنائية ، وتقديم البيانات ، والعمل مع مآخذ (مآخذ يونيكس على وجه الخصوص) ، والعمل مع قاعدة بيانات redis وقنوات pub / sub.
لقد وصل المشروع إلى نسخة مستقرة. في هذا الوقت ، اخترت ببطء لغات أخرى ، ولا سيما دارت ورفرفة كتطبيق له. على الرف غبار دون إجراء شراء في وقت دليل الطالب G. شيلدت.
الفكر المستمر لإعادة كتابة المشروع في C استقر في رأسي. أنا أفكر في خيارات الذهاب ، الصدأ ، صد الإنشاءات النحوية الأخرى. لا توجد طريقة للبدء ، تم تأجيل الفكرة لفترة من الوقت.
في أيار (مايو) من هذا العام ، قررت إلقاء نظرة على اللغة D ، لسبب ما مقتنعًا أن الحرف D يعني ديناميكيًا. لقد تساءلت لفترة طويلة عن مكان وسبب هذا الفكر في رأسي ، لذلك لم أجد إجابة. لكن هذا لم يعد مهمًا ، حيث تم ترحيلي من جديد طوال الصيف.
جوهر المشروع
يتم توصيل الوحدات KNX BAOS 830/832/838 عبر UART إلى جهاز كمبيوتر ، يتم لف بروتوكول ObjectServer في FT1.2. ينشئ التطبيق اتصالًا بـ /dev/ttyXXX
، ويعالج البيانات الواردة ، ويرسل البايتات من طلب المستخدم المحول إلى قناة PUB / SUB إلى قائمة الانتظار نفسها من رسائل JSON ، أو إلى قائمة انتظار المهام استنادًا إلى قوائم Redis (بالنسبة للعقدة ، يتم تنفيذ قوائم الانتظار باستخدام حزمة bee-queue ).
queue.on("job", data => {
ديناميكية
JSON في js شيء أصلي ؛ لم يكن لدي أي فكرة عن كيفية حدوث المعالجة باللغات المكتوبة بشكل ثابت. كما اتضح ، فرق بسيط. على سبيل المثال ، get value
طريقة get value
. كحجج ، يتطلب الأمر إما رقمًا - رقم نقطة التاريخ أو مجموعة من الأرقام.
في js ، يتم إجراء الاختبارات:
if (Array.isArray(payload)) {
أساسا نفس الشيء على D:
if (payload.type() == JSONType.integer) {
لسبب ما ، في وقت دراسة Rust ، كان عدم فهمي للعمل مع JSON هو الذي أبطئني. نقطة أخرى تتعلق بالديناميكية: المصفوفات. في js ، تعتاد على حقيقة أنه يكفي استدعاء طريقة push
لإضافة عنصر. في C ، يتم تطبيق الديناميكية من خلال التخصيص اليدوي للذاكرة ، لكنني لا أريد حقًا التسلق إلى هناك. Dlang ، كما اتضح فيما بعد ، يدعم المصفوفات الديناميكية.
ubyte[] res;
تم تحويل بيانات UART الواردة في js إلى Object
. بالنسبة لهذه الأغراض ، تعد الهياكل والتعدادات ذات القيم والوصلات كبيرة في D.
enum OS_Services { unknown, GetServerItemReq = 0x01, GetServerItemRes = 0x81, SetServerItemReq = 0x02, SetServerItemRes = 0x82,
مع رسالة واردة:
ubyte mainService = data.read!ubyte(); ubyte subService = data.read!ubyte(); try { if (mainService == OS_MainService) { switch(subService) { case OS_Services.GetServerItemRes: result.direction = OS_MessageDirection.response; result.service= OS_Services.GetServerItemRes; result.success = true; result.server_items = _processServerItemRes(data); break; case OS_Services.SetServerItemRes: result.direction = OS_MessageDirection.response;
في js ، قمت بتخزين قيم البايت في صفيف ، مع البيانات الواردة ، لقد أجريت عملية بحث وأرجعت سلسلة باسم الخدمة. تبدو الهياكل والتعدادات والجمعيات أكثر صرامة.
العمل مع صفائف بيانات البايت
Node.js أحب التجريد من Buffer
. على سبيل المثال: من المريح إجراء تحويل وحدتي بايت إلى عدد صحيح غير موقّع باستخدام طريقة readUInt16BE(offset)
، للكتابة - writeUInt16BE(value, offset)
، المخازن المؤقتة المستخدمة بنشاط للعمل مع البروتوكول الثنائي. بالنسبة إلى dlang ، بدأت في البداية حزم تخزين صوفي على شيء مشابه. تم العثور على الإجابة في مكتبة std.bitmanip
القياسية. بالنسبة للأعداد الصحيحة غير الموقعة 2 بايت: ushort start = data.read!ushort()
، للكتابة: result.write!ushort(start, 2);
حيث الوسيطة الثانية هي الإزاحة.
هاء ، الوعود ، متزامن / تنتظر.
أسوأ جزء كان البرمجة دون EventEmitter
. في node.js ، يتم تسجيل وظائف المستمع ببساطة ، ويطلق عليها اسم حدث. وبالتالي ، لا يتعين على المرء أن يفكر مليا. تحتوي حزم tinylis و serialport
dlang (تبعيات تطبيقي) على أساليب غير محظورة لمعالجة الرسائل. الحل بسيط: في الوقت الحالي ، من الصحيح تلقي رسائل المنفذ التسلسلي وقناة الحانة / الفرعية بدورها. في حالة طلب مستخدم وارد إلى قناة pub / sub ، ينبغي للبرنامج إرسال رسالة إلى المنفذ التسلسلي والحصول على النتيجة وإرسال المستخدم مرة أخرى إلى pub / sub. تقرر جعل طرق حظر الطلبات التسلسلية.
while(!(_responseReceived || _resetInd || _interrupted)) { try { processIncomingData(); processIncomingInterrupts(); if (_resetInd || _interrupted) { _response.success = false; _response.service = OS_Services.unknown; _response.error = Errors.interrupted; _responseReceived = true; _ackReceived = true; }
في حلقة من الوقت ، يتم استقصاء البيانات عن طريق عملية non-blocking processIncomingData()
. يتم توفير احتمال إعادة تشغيل وحدة KNX (قطع الاتصال وإعادة توصيلها إلى ناقل KNX أو البرنامج). أيضًا ، يتحقق معالج processIncomingInterrupts()
من قناة الخدمة / القناة الفرعية لطلب reset
. لا توجد وعود أو وظائف غير متزامنة ، على عكس تطبيقات js السابقة. كان علي أن أفكر في بنية البرنامج (أي تسلسل استدعاءات الوظائف) ، ولكن بسبب عدم وجود تجريدات غير ضرورية ، أصبح البرنامج أكثر سهولة. في الواقع ، عند await someAsyncMethod
في كود js ، فإن الوظيفة غير المتزامنة تسمى الحجب وتمرير حلقة الحدث. إن إمكانية اللغة جيدة ، لكن يمكنك الاستغناء عنها.
الخلافات
طابور الوظيفة. يستخدم تطبيق node.js حزمة bee-queue
لهذا الغرض. عند التنفيذ على D ، يتم إرسال الطلبات فقط من خلال pub / sub.
خلاف ذلك ، كل شيء مماثل تقريبا.
يستهلك الإصدار المترجم أقل من ذاكرة الوصول العشوائي بمقدار 10 مرات ، مما قد يكون مهمًا لأجهزة الكمبيوتر ذات اللوحة الواحدة.
مجموعة
تم إجراء التجميع باستخدام ldc على النظام الأساسي aarch64.
لتثبيت ldc:
curl -fsS https://dlang.org/install.sh | bash -s ldc
تم تجميع اللوحة الأم ، التي تتألف من ثلاثة مكونات رئيسية: NanoPi Neo Core2 كجهاز كمبيوتر ، وحدة KNX BAOS 830 للتواصل مع ناقل KNX ، و Silvertel Ag9205 for PoE power ، التي تم تنفيذ البرمجة عليها.
استنتاج
لن أحكم على اللغة التي هي أفضل أو أسوأ. لكل منهم: js رائع للتعلم ، ومستوى التجريد (الوعود ، والبواعث) يجعل من السهل والسريع بناء بنية التطبيق. اقتربت من التنفيذ على dlang من خلال خطة واضحة ومحفوظة لمدة عام ونصف ، ما يجب القيام به. عندما تعرف البيانات التي يجب معالجتها وكيف ، فإن الكتابة الثابتة ليست مخيفة. تسمح لك الطرق غير المحظورة بتنظيم دورة عمل. كان هذا أول عمل لي في D ، وهو عمل رائع وغني بالمعلومات.
بالنسبة لترك منطقة الراحة (كما هو موضح في العنوان): في حالتي ، كان الخوف ذو عيون كبيرة ، وهو ما حالني لفترة طويلة منعتني من تجربة شيء آخر غير العقدة.
أكواد المصدر مفتوحة ويمكن الاطلاع عليها على github.com/dobaos/dobaos