عرض NodeMCU لنموذج برنامج التشغيل البسيط (SDM): واجهة مستخدم ديناميكية

صورة


NodeMCU عبارة عن برنامج ثابت تفاعلي ، والذي يسمح بتشغيل مترجم Lua على متحكم ESP8266 (دعم ESP32 قيد التطوير). جنبا إلى جنب مع جميع واجهات الأجهزة العادية ، فقد وحدة واي فاي ونظام الملفات SPIFFS .


توضح هذه المقالة الوحدة النمطية الجديدة لـ NodeMCU - sdm. SDM يرمز إلى طراز برنامج التشغيل البسيط ويوفر تجريد طراز برنامج تشغيل الجهاز للنظام. في الجزء الأول من هذه المقالة سنناقش النموذج نفسه ، وفي الجزء الثاني سيكون عرضًا لواجهة مستخدم الويب التي تم إنشاؤها ديناميكيًا باستخدام sdm مع بعض التعليقات.


أساسيات نموذج السائق


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


DEVICES + DRIVERS | +-----+ | +-----+ |1WIRE<----------------------+1WIRE| ++-+-++ | +-----+ | | | | +---------+ | +--------+ | +------+ | | | +------+DS1820| +---v----+ +---v----+ +---v----+ | | +------+ |DS1820|0| |DS1820|1| |DS1822|0| | | +---^----+ +---^----+ +---^----+ | | +------+ | | +--------------+DS1822| | | | | +------+ +-----------+------------------+ + 

برنامج تشغيل الجهاز هو جزء من المنطق المرتبط بجهاز معين. تسمى الوظائف التي يوفرها برنامج التشغيل الأساليب ، وتسمى حاويات البيانات المرتبطة ببرنامج التشغيل سمات . كل من الطرق والسمات تعيش داخل السائق.


للسمات وظيفتان مرتبطتان بها: خطاطيف getter و setter . لذلك ، تُعزِّز وظيفة أسلوب مجموعة العمل الفائقة ، ولكنها تستهلك أيضًا ذاكرة أكبر (ذاكرة جهاز التحكم الصغير شحيحة ، تذكر؟).


 sdm.attr_add(drv, -- device handle "ref", -- attribute name "Reference voltage", -- attribute description 5, function(dev) -- this is a getter function return sdm.attr_data(sdm.attr_handle(dev, "ref")) end, function(dev, value) -- this is a setter function sdm.attr_set(sdm.attr_handle(dev, "ref"), value) end ) 

ربط الجهاز


جزء صعب من طراز برنامج التشغيل هو ربط برنامج تشغيل الجهاز. العملية في حد ذاتها بسيطة: نطابق الجهاز مع كل برنامج تشغيل متاح حتى يناسب. هناك جزءان فقط مفقودين - مطابقة المنطق وبعض البيانات لمطابقتهما.


في SDM مطابقة المنطق يعيش في برامج التشغيل تحت اسم _poll() . إنها طريقة عادية تُدعى بمقبض الجهاز كمعلمة وتُرجع true أو false إذا كان الجهاز يمكن أو لا يمكن توصيله ببرنامج التشغيل على التوالي.


 sdm.method_add(drv, "_poll", nil, function(dev, drv, par) local attr = sdm.attr_data(sdm.local_attr_handle(dev, "id")) -- get device attribute "id" if attr == nil then return false end -- if it does not have one, driver does not match -- parent name must be "ESP8266_1W" and first byte of "id" must be "0x28" return (sdm.device_name(par) == "ESP8266_1W") and (attr:byte(1) == 0x28) end ) 

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


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


 sdm.method_add(drv, "_init", nil, function(dev, drv, par) sdm.device_rename(dev, sdm.request_name("DS18B20")) -- rename device sdm.attr_copy(dev, "temp") -- copy attribute sdm.attr_copy(dev, "precision") -- copy attribute local met = sdm.method_dev_handle(par, "setup") -- get 1Wire bus pin init function .. local func = sdm.method_func(met) -- .. and .. func(par, dev) -- .. call it end ) sdm.method_add(drv, "_free", nil, function(dev, drv, par) local met = sdm.method_dev_handle(par, "free") -- get 1Wire bus pin free function .. local func = sdm.method_func(met) -- .. and .. func(par, dev) -- .. call it end ) 

ربما يسأل القارئ اليقظ: ما معنى "نسخة السمة" في المثال أعلاه تعني؟ وسيكون على صواب ، لأن هذا يتعلق بالنوع الثالث من السمات التي لم نناقشها بعد - السمة الخاصة . ليس من المنطقي أن تتم مشاركة جميع بيانات السمات بين جميع مثيلات الجهاز. لهذا الغرض ، توفر sdm آلية نسخ السمة من برنامج التشغيل وربطها بالجهاز. هذا يجعل برنامج التشغيل سمة نموذج أو قالب.


ملخص سريع:


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

ممتلكاتالسمة المحليةالسمة الخاصةسائق (العامة) السمة
تخزينها فيجهازجهازسائق
يمكن الوصول إليها باستخدام مقبض السائق--+
يمكن الوصول إليها باستخدام مقبض الجهاز+++
مشترك بين الأجهزة--+
تستمر عند فصل السائق+-+

تنفيذ واجهة مستخدم الويب


كود الخادم


يوجد مشروع httpserver nodemcu جميل يقوم بتنفيذ كود الخادم الخاص بـ NudeMCU. للأسف يبدو أنه قد مات. تم استخدامه كأساس للخادم. أولاً ، تم نقل وظائف الخادم إلى LFS ومن ثم تعديلها قليلاً لتقديم صفحة ثابتة واحدة لكل مكالمة. Vue.js هو الاختيار الأمثل لصفحات الويب القائمة على القوالب. لذلك تم استخدامه للواجهة الأمامية . تجدر الإشارة إلى أن NodeMCU قد لا تكون متصلاً بالإنترنت. لهذا السبب ، يجب أن تكون مكتبة vue.js موجودة محليًا وتخدمها خادم NodeMCU.


نظرًا لأن جميع الأجهزة منظمة في هيكل شجرة ، يتم الوصول إليها تمامًا مثل الدليل: /ESP8266/ESP8266_1W/DS18S20-0 . هنا /ESP8266 هي صفحة NodeMCU ، و /ESP8266/ESP8266_1W هي صفحة حافلة 1Wire وأخيراً /ESP8266/ESP8266_1W/DS18S20-0 هي مستشعر درجة الحرارة.


كما ذكرنا سابقًا ، يتم إنشاء جميع صفحات الجهاز من صفحة قالب واحدة يتم تقديمها في كل مكالمة. كود JS داخل هذه الصفحة ثم يجعل طلب لنفس عنوان URL ، معبأ مسبقا مع /api . على سبيل المثال أعلاه ، سيكون عنوان URL للاتصال /api/ESP8266/ESP8266_1W/DS18S20-0 . في مثل هذه الطلبات ، يستجيب الخادم للبيانات الخاصة بالجهاز المشفرة JSON ، والتي تملأ الصفحة. بالطبع ، قد يتم تخطي طلب صفحة HTML إذا كانت هناك حاجة إلى البيانات الأولية فقط.


شجرة الجهاز


يتم التكوين الأولي للجهاز باستخدام بنية شجرة الجهاز البسيطة . هو مثل شجرة الجهاز ، ولكن أبسط. يصف تكوين الجهاز بما في ذلك السمات المحلية للجهاز.


 local root={ -- local_attributes={}, children={ { name="ESP8266_1W", -- local_attributes={}, children = { { name="DS18S20-0", -- static declaration alternative to 1Wire poll method local_attributes={ { name="id", desc=nil, -- empty description to save space data=string.char(16) .. string.char(221) .. string.char(109) .. string.char(104) .. string.char(3) .. string.char(8) .. string.char(0) .. string.char(150) -- ugly way to create byte array }, { datapin=2 } } }, } }, { name="ESP8266_SPI", -- local_attributes={}, children = { { name="MCP3208-0" }, } }, } } 

إعداد الأجهزة


هنا يبدأ العرض. لهذا الغرض تم توصيل مجموعة من أجهزة الاستشعار إلى NodeMCU:



ترتبط أجهزة الاستشعار 1Wire إلى نفس دبوس.



صفحات الويب وبرامج التشغيل


جهاز الجذر


الغرض الرئيسي من جهاز الجذر (المعروف أيضًا باسم ESP8266) هو توفير مكان لأطفاله للاتصال به. ومع ذلك ، لا يقتصر وجود أساليب أو سمات مرتبطة به.


مقتطف الشفرة هذا من هنا :


 sdm.method_add(drv, "_init", nil, function(dev, drv, par) local attr = sdm.attr_handle(dev, "id") -- get device "id" attribute sdm.attr_set(attr, node.chipid()) -- set "id" value attr = sdm.attr_handle(dev, "float") -- get device "float" attribute sdm.attr_set(attr, 3 / 2 ~= 1) -- set to true if firmware supports floating point instructions end ) sdm.attr_add(drv, "float", "Floating point build", false, function(drv) -- attribute value is set inside "_init" function local attr = sdm.attr_drv_handle(drv, "float") return sdm.attr_data(attr) -- just return stored value end, nil ) 

يضيف هذا الرمز float السمة الذي يستخدم للاحتفاظ بنوع البرامج الثابتة. تتم تهيئة قيمتها في _init() وهي وظيفة خاصة ، يتم تشغيلها مرة واحدة عندما يتصل برنامج التشغيل بالجهاز.


هذه هي الصفحة التي تم إنشاؤها للجهاز الجذر.



هنا يمكننا أن نرى أن الجهاز الجذر يحتوي على heap طريقة واحدة ، وهما سمات سائق float id . أخيرًا ، لديه جهازان متصلان به - حافلات SPI و 1 Wire .


SPI


سائق SPI ليست مثيرة للاهتمام للغاية. انها مجرد خرائط وظائف NodeMCU SPI .



MCP3208


MCP3208 عبارة عن شريحة ADC . فهو يقيس الفولتية من الصفر إلى المرجع ويعود رمز 12 بت. ما هو مثير للاهتمام حول تنفيذ برنامج التشغيل هذا هو أن سمة ref ستكون موجودة فقط إذا كانت البرامج الثابتة تدعم حساب الفاصلة العائمة. إذا لم يكن مدعومًا ، فبدلاً من الجهد المطلق ، يتم إرجاع رمز الجهد بواسطة كل من الطرق single differential .


 sdm.method_add(drv, "single", "Single ended measure 0|1|2|3|4|5|6|7", function(dev, channel) -- ... if ref ~= nil then -- this part is executed only if floating point arithmetic is enabled rv = ref * rv / 4096 end return rv end ) if 3/2~=1 then -- other alternative is to access ESP8266 "float" method sdm.attr_add(drv, "ref", "Reference voltage", 5, function(dev) return sdm.attr_data(sdm.attr_handle(dev, "ref")) end, function(dev, value) sdm.attr_set(sdm.attr_handle(dev, "ref"), value) end ) end 


لاحظ أيضًا أن هذا الجهاز يحتوي على سمة مميزة تحمل علامة خاصة . يتم ضبطه على أساس كل جهاز.


1Wire


1 يقوم برنامج التشغيل بتنفيذ طريقة poll - البحث الديناميكي عن الأجهزة .


مباشرة بعد اكتشاف الجهاز نوعه غير معروف. لذلك يتم استخدام عنوانه الفريد 1Wire كاسم جديد للجهاز (يتم تمثيل البايتات كأرقام مفصولة بحرف _ ).


 sdm.method_add(drv, "poll", "Poll for devices", function(bus, pin) local children = sdm.device_children(bus) or {} -- already attached local ids = {} -- get IDs of attached devices for name, handle in pairs(children) do local dpin = sdm.attr_data(sdm.local_attr_handle(handle, "pin")) if dpin == pin then ids[sdm.attr_data(sdm.local_attr_handle(handle, "id"))] = true end end ow.reset_search(pin) -- reset previous search while true do -- for all found devices local id = ow.search(pin) if id == nil then break end if ids[id] == nil then -- if not already present local name = "" for i=1,#id do name = name .. tostring(id:byte(i)) .. "_" end name = name:sub(1,-2) -- add to system with their ID used as name local device = sdm.device_add(name, bus) -- add "pin" attribute local rv = sdm.local_attr_add(device, "datapin", nil, pin, nil, nil) -- add "id" attribute local rv = sdm.local_attr_add(device, "id", nil, id, nil, nil) -- poll for driver local rv = sdm.device_poll(device) end end end ) 

هذه هي الصفحة الأولية لبرنامج 1Wire .



بعد إصدار استدعاء poll باستخدام الوسيطة 2 وصفحة منعشة ، يظهر قسم الأطفال. لاحظ أن أسماء الأطفال قابلة للقراءة البشرية. هذا لأنه تم استدعاء دالة device_rename() أثناء _init .



DS18S20


عند التهيئة ، يتحقق برنامج تشغيل DS18S20 من أن معرف الجهاز يبدأ بـ 0x10 ، وهو رمز عائلة للجهاز. عند توصيل الجهاز ببرنامج التشغيل ، تتم إعادة تسميته باسم DS18S20-X ، حيث DS18S20 هو اسم أساسي و X هو رقم مثيل.


 sdm.method_add(drv, "_poll", nil, function(dev, drv, par) local attr = sdm.attr_data(sdm.local_attr_handle(dev, "id")) if attr == nil then return false end return (sdm.device_name(par) == "ESP8266_1W") and (attr:byte(1) == 0x10) -- check family ID end ) sdm.method_add(drv, "_init", nil, function(dev, drv, par) sdm.device_rename(dev, sdm.request_name("DS18S20")) -- rename device sdm.attr_copy(dev, "temp") -- copy attribute to device local met = sdm.method_dev_handle(par, "setup") local func = sdm.method_func(met) -- use parent "setup" method on the device func(par, dev) end ) 


لا تحتوي id السمات المحلية و datapin على خطاطيف setter و setter ، لذلك تكون أسماءها مرئية فقط.


DS18B20


برنامج تشغيل DS18B20 هو نفس برنامج تشغيل DS18S20 تقريبًا. الفرق الوحيد هو طريقة precision . يفترض كلا السائقين DS18؟ 20 إنشاء عدد صحيح ولا يستخدم تقسيم الفاصلة العائمة.


 sdm.attr_add(drv, "precision", "Precision (9|10|11|12)", 12, function(dev, precision) local attr = sdm.attr_dev_handle(dev, "precision") return sdm.attr_data(attr) end, function(dev, precision) local par = sdm.device_parent(dev) local attr = sdm.attr_dev_handle(dev, "precision") local ex = sdm.method_func(sdm.method_dev_handle(par, "exchange")) local modes = {[9]=0x1f, [10]=0x3f, [11]=0x5f, [12]=0x7f} if modes[precision] ~= nil then ex(par, dev, {0x4e, 0, 0, modes[precision]}) sdm.attr_set(attr, precision) end end ) 


استخدام الذاكرة


ذاكرة حرة ESP8266 حوالي 40 كيلو . يتم نقل رمز الخادم إلى LFS ، لذلك لا يستغرق أي مساحة RAM في وقت التهيئة (استغرق الرمز الأصلي حوالي 10 كيلو ).


SDM يستغرق حوالي 10k لمدة 5 برامج تشغيل الجهاز و 5 أجهزة. أقل قليلاً لبناء البرامج الثابتة غير العائمة. لذلك من الأفضل تحديد برامج التشغيل المطلوبة فقط للمهمة في متناول اليد في بيان برنامج التشغيل. المهمة الأكثر استهلاكًا للذاكرة هي خدمة مكتبة vue.js



في حالة طلب بيانات JSON غير مشفرة (باستخدام curl ) ، قد ينخفض ​​استهلاك الذروة في الذاكرة بشكل كبير.



بدلا من خاتمة


إحدى الطرق الأولى التي طبقتها باستخدام sdm هي الربط لـ
node.restart() .
تجربة ذلك باستخدام واجهة مستخدم الويب أنتجت نتيجة غريبة. مباشرة بعد إصدار متصفح الويب للطلب ، تمت إعادة تشغيل الشريحة كما هو متوقع. ولكن نظرًا لأن NodeMCU لم يستجب بشكل صحيح لطلب HTTP ، جرب متصفح الويب نفس الطلب مرة أخرى. عند إعادة تشغيل خادم NodeMCU وكان قيد التشغيل مرة أخرى ، متصفح متصل به ، إعادة تعيين المحاولة الداخلية مرة أخرى العداد ودعا الأسلوب node.restart() ، وبالتالي بدء حلقة لا نهائية من إعادة تشغيل NodeMCU.

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


All Articles