[Перевод] Обмен данными между средами программирования на Intel Edison
Нередко мы оказываемся в ситуациях, когда нам для разработки IoT-приложений хотелось бы воспользоваться несколькими программными средами, например:
1. Мы предпочитаем делать обработку изображений на языке программирования C++ с использованием OpenCV, нежели делать это на NodeJS или Arduino. Так происходит в основном потому, что есть много примеров для OpenCV, написанных на C++.
2. Допустим, наше написанное на Arduino приложение должно определить количество лиц на полученном с камеры изображении. Для этого нам нужно использовать OpenCV для обработки изображения, а затем передать Arduino количество выявленных лиц.
3. Мы предпочитаем использовать NodeJS для создания веб-сервера, потому что это относительно легко сделать с помощью NodeJS, нежели других языков программирования.
Существует множество подобных примеров. Тем не менее, во всех этих случаях нам всегда необходим простой механизм обмена данными между различными средами программирования.
Матиас Хан (Matthias Han) написал хорошую статью о совместно используемой Arduino и C ++ памяти. Этот метод применим благодаря тому, что С++ и Arduino работают так же, как и Linux-процесс. Тем не менее, для новичка в C++ этот процесс выглядит немного сложным. Кроме того, если вы хотите одновременно работать еще и с NodeJS, то вам придется написать C++ код и нативные биндинги для доступа к этим переменным в NodeJS.
Другой метод заключается в создании общего файла совместного доступа, который может быть прочитан всеми этими программами. Каждая программа будет запрашивать информацию об изменениях в файле. В данном случае изменения будут отслеживаться в потоке или в цикле. Если замечено какое-либо изменение в процессе чтения, то необходимо прочитать и обработать данные. Эта идея кажется простой, однако использование опроса не нашла должной поддержки в мире ПО.
Сегодня в своем блоге я хочу поговорить о модели «издатель-подписчик» для обмена информацией между различными программами для Linux. Эта модель очень хорошо подходит для разработки программного обеспечения. Тем не менее, во встраиваемом пространстве мы получим эту модель благодаря связке железа и программного обеспечения.
Архитектура
Схема обработки сообщений состоит из двух частей. Одна — это уведомление об изменении, другая — считывание данных. Это довольно устоявшийся старый метод в программном обеспечении. Этот подход был использован в некоторых приложениях хранилищ данных (DW) для объединения данных. В этом хранилище данных изначально маленький файл уведомления бросается в директорию. В этом файле уведомления будет содержаться информация о дате и времени изменения и ссылка на размещение фактического файла с данными, который, как правило, очень большой. Демон прочитает этот файл уведомления и запустит процесс для прочтения этого большого файла. Я пробую использовать тот же концепт. Только в данном случае вместо файла уведомления мы будем запускать прерывание.
Поток уведомлений:
Поток сообщений:
Давайте разберёмся в потоке уведомлений и сообщений.
Каждая среда программирования будет отличаться собственным контейнером выходящих данных и пином для запуска уведомления. Если какие-то данные должны быть высланы, то среда вначале перенесет контент в контейнер. Затем пину, отвечающему за уведомления, будет отослан сигнал HIGH. Давайте посмотрим на что-то вроде схемы цепи, расположенной ниже. Как ни странно, но эти пины замкнуты. Давайте я объясню детали на примере.
Схема
Давайте посмотрим, как программа Arduino может общаться с NodeJS и наоборот.
Поток данных и уведомлений от Arduino в NodeJS:
Поток данных и уведомлений от NodeJS в Arduino:
Давайте предположим, что у вас есть программа Arduino, которая считывает данные с датчика расстояния. Эти данные должны быть отправлены NodeJS для дальнейшей обработки.
В этом случае Arduino необходим пин уведомления, который является ничем иным, как GPIO-пином. Допустим, этот пин Arduino расположен на нашей схеме под №3. И если есть какая-то новая информация, то Arduino запишет эти данные в свой контейнер уведомлений и корневой каталог (смотрите диаграмму потока сообщений), в этом случае: /arduino_notification_out.txt.
После успешной записи данных Arduino отправит сигнал HIGH пину №3. Теперь посмотрите на диаграмму выше. Сейчас №3 замкнут с №1. Это значит, что каждый раз при получении сигнала HIGH пином №3 он отправляет этот сигнал пину №1.
Код Arduino
int notifier_pin = 3;
int js_subscriber_pin = 6;
FILE *fromarduino, *toarduino;
int i = 0;
int c;
// the setup routine runs once when you press reset:
void setup() {
pinMode(notifier_pin, OUTPUT); //Notification pin
pinMode(js_subscriber_pin, INPUT_PULLUP); //interrupt pin for reading message from JS
attachInterrupt(js_subscriber_pin, subscriberEvent, RISING); //Subscribe to interrupt notifications from JavaScript
Serial.begin(9600);
}
//Read message from js notification file
void subscriberEvent() {
toarduino = fopen("/js_notification_out.txt","r"); //Opening message from JS
if (toarduino)
{
while ((c = getc(toarduino)) != EOF)
{
if(c != 10)//new line
{
Serial.print((char)c);
}
}
Serial.println("");
Serial.println("----------------");
fclose(toarduino);
}
}
// the loop routine runs over and over again forever:
void loop() {
if(i < 50)
{
i = i + 1;
}
else
{
i = 0;
}
publishData();
notifyWorld();
delay(1000); // wait for a second
}
void publishData()
{
fromarduino = fopen ("/arduino_notification_out.txt", "w+");
fprintf(fromarduino, "[%d]", i);
fclose(fromarduino);
}
//Nofity any body connected to this interrupt (C++ program and NodeJS) program
void notifyWorld()
{
digitalWrite(notifier_pin, HIGH);
delay(200);
digitalWrite(notifier_pin, LOW);
}
В программе NodeJS мы добавим прерывание к пину №1. Всякий раз, когда он получает сигнал HIGH, это обозначает появление новых данных в контейнере уведомлений Arduino. NodeJS прочтет этот файл-контейнер и обработает данные. Для большей ясности смотрите схему «Поток данных и событий из Arduino в NodeJS».
Код NodeJS
var mraa = require("mraa");
var fs = require('fs');
/**********Read notification from arduino*************/
var subscriber_pin = new mraa.Gpio(1);
subscriber_pin.dir(mraa.DIR_IN);
subscriber_pin.isr(mraa.EDGE_RISING, subscriberEvent); //Subscribe to interrupt notifications from Arduino
function subscriberEvent() {
var contents = fs.readFileSync('/arduino_notification_out.txt').toString();
console.log("Message from Arduino:" + contents);
}
/********** Trigger message sending interrupt every 20 seconds *************/
var counter = 0;
var notifier_pin = new mraa.Gpio(5);
notifier_pin.dir(mraa.DIR_OUT);
setInterval(function(){
counter++;
fs.writeFileSync("/js_notification_out.txt", "NodeJS: [" + counter + "]\n");
notifyWorld();
counter = 0;
},20000);
function notifyWorld()
{
notifier_pin.write(1);
setTimeout(function(){
notifier_pin.write(0);
},200);
}
Вы можете похожим способом отправлять данные и с NodeJS в Arduino. Для большей ясности смотрите схему «Поток данных и событий из NodeJS в Arduino».
Данный подход возможно применить в любых средах программирования, которые поддерживают прерывания.
Преимущества этого подхода
При таком подходе вы избегаете ненужных опросов об изменении файла, а также вам не нужно использовать в C++ биндинг для обмена данными с NodeJS. Программирование здесь также довольно простое.
Недостатки этого метода
Тем не менее, у этого метода есть один недостаток. Каждому направлению потока данных нужна пара GPIO-пинов. Если вам нужно осуществить обмен данными во множестве сред программирования, то вам может просто не хватить GPIO-пинов. Тем не менее, мы можем достичь того же результата с похожей архитектурой, использовав всего одну пару GPIO. Но тогда вам нужно будет управлять вашей логикой, используя единственный JSON-файл с подходящими свойствами, такими как eventsource, event data и т.д. Кроме того, вам придется разобраться с ситуациями блокировки файлов. Однако это выполнимо.