Arduino+Ubuntu server+PHP

antotor
Offline
Зарегистрирован: 10.09.2013

Всем привет. Ну вот наконец, после почти месяца самостоятельных изысканий я созрел для того, чтобы обратиться за помощью к сообществу (:

Итак - прошу помощи знатоков в вопросе передачи и считывания данных с ардуино с помощью php на платформе Ubuntu server. Ниже - описание проблемы и конфигурация среды.

Аппаратная платформа - ПК на основе miniITX материнской платы с процессором Intel Celeron, 4Гб оперативной памяти и винчестером на 500Гб. Управляется ПК операционной системой Windows 2003 Server (так сложилось по историческим причинам, есть ключ). К Пк посредством USB кабеля подключен Arduino UNO последней ревизии. Это видно на следующем изображении: http://clip.corp.mail.ru/clip/m477/1378843561-clip-10kb-I0KkPjIaujKb.png

При помощи программы VirtualBox создана виртуальная машина с характеристиками изображенными на рисунке: http://clip.corp.mail.ru/clip/m477/1378843836-clip-33kb-YeSzBjiXK5TE.png Видно что COM порт корректно проброшен на виртуальную машину. Виртуалка управляется ОС Ubuntu Server 12.04.3 lts все последние обновления установлены.

Установлена последняя версия php:

PHP 5.3.10-1ubuntu3.8 with Suhosin-Patch (cli) (built: Sep  4 2013 20:05:42)
Copyright (c) 1997-2012 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2012 Zend Technologies
 

Используется библиотека php_serial.class.php

Пользователю www-data установлен доступ к группе dialout

Определен адрес порта на который проброшена ардуина:

eva@Eva:~$ dmesg | grep tty
[    0.000000] console [tty0] enabled
[    1.485499] 00:04: ttyS2 at I/O 0x3e8 (irq = 4) is a 16550A

Соответственно порт проброшен на /dev/ttyS2

 

Теперь после описания окружения перейду к моей задаче. К ардуино подключен датчик температуры DHT11, задача - отправить средствами php в порт еденицу - и получение обратно данных о температуре и влажности.

Ниже - скетч залитый в ардуино:

// подключаем библиотеку
#include <dht.h>

// создаём объект-сенсор
DHT sensor = DHT();

void setup()
{
    Serial.begin(9600);

    // методом attach объявляем к какому контакту подключен
    // сенсор. В нашем примере это нулевой аналоговый контакт
    sensor.attach(A0);
    //
    // после подачи питания ждём секунду до готовности сенсора к работе
    delay(1000);
}

void loop()
{
	int inByte = Serial.read();
	switch(inByte)
	{
	case '1':
		// метод update заставляет сенсор выдать текущие измерения
		sensor.update();

		switch (sensor.getLastError())
		{
			case DHT_ERROR_OK:
				char msg[128];
				// данные последнего измерения можно считать соответствующими
				// методами
				sprintf(msg, "Temperature = %dC, Humidity = %d%%", 
						sensor.getTemperatureInt(), sensor.getHumidityInt());
				Serial.println(msg);
				break;
			case DHT_ERROR_START_FAILED_1:
				Serial.println("Error: start failed (stage 1)");
				break;
			case DHT_ERROR_START_FAILED_2:
				Serial.println("Error: start failed (stage 2)");
				break;
			case DHT_ERROR_READ_TIMEOUT:
				Serial.println("Error: read timeout");
				break;
			case DHT_ERROR_CHECKSUM_FAILURE:
				Serial.println("Error: checksum error");
				break;
		}
		break;
	}
}

Если запустить соответствующее приложение "Монитор порта" и отправить еденицу - ардуино корректно вернет значение температуры и влажности.

На всякий случай убедимся что ubuntu действительно корректно видит порт и ардуино на нем. Изменим скетч убрав условие для получения данных и добавим delay в 2 секунды для отправки данных:

// подключаем библиотеку
#include <dht.h>

// создаём объект-сенсор
DHT sensor = DHT();

void setup()
{
    Serial.begin(9600);

    // методом attach объявляем к какому контакту подключен
    // сенсор. В нашем примере это нулевой аналоговый контакт
    sensor.attach(A0);
    //
    // после подачи питания ждём секунду до готовности сенсора к работе
    delay(1000);
}

void loop()
{
    // метод update заставляет сенсор выдать текущие измерения
    sensor.update();

    switch (sensor.getLastError())
    {
        case DHT_ERROR_OK:
            char msg[128];
            // данные последнего измерения можно считать соответствующими
            // методами
            sprintf(msg, "Temperature = %dC, Humidity = %d%%", 
                    sensor.getTemperatureInt(), sensor.getHumidityInt());
            Serial.println(msg);
            break;
        case DHT_ERROR_START_FAILED_1:
            Serial.println("Error: start failed (stage 1)");
            break;
        case DHT_ERROR_START_FAILED_2:
            Serial.println("Error: start failed (stage 2)");
            break;
        case DHT_ERROR_READ_TIMEOUT:
            Serial.println("Error: read timeout");
            break;
        case DHT_ERROR_CHECKSUM_FAILURE:
            Serial.println("Error: checksum error");
            break;
    }

    delay(2000);
}

Теперь если открыть "Монитор порта" - видим что каждые 2 секунды появляется новое значение температуры и влажности. Запускаем виртуальную машину и пытаемся прочитать данные:

eva@Eva:~$ cat /dev/ttyS2
ture = 24C, Humidity = 62%
Temperature = 24C, Humidity = 62%
Error: read timeout
Temperature = 24C, Humidity = 62%
Temperature = 24C, Humidity = 62%
Error: read timeout

Соответственно видно что ардуино корректно отправляет данные в порт и мы их читаем.

Возвращаем наш предыдущий скетч и пытаемся считать данные с помощью php. Код на php:

<?php
include "php_serial.class.php";
$serial = new phpSerial;
//Задаем путь к Arduino 
$serial->deviceSet("/dev/ttyS2");
//Это стандарт
$serial->confBaudRate(9600);
$serial->confParity("none");
$serial->confCharacterLength(8);
$serial->confStopBits(1);
$serial->confFlowControl("none");
$serial->deviceOpen();
//Отправляем команду
$serial->sendMessage(chr(1));
//Читаем ответ Arduino
$read = $serial->readPort();

echo "Data: " . $read . "\n";

Пробуем запустить скрипт в консоли:

eva@Eva:~/sites/eva/www$ php index.php
Data:

Ничего не считалось.

Что я предпринимал:

1. Вставка конденсатора от 10 до 50 мкф между gnd и reset. Не помогло.

2. Конфигурация порта командой stty -F /dev/ttyS2 cs8 9600 ignbrk -brkint -icrnl -imaxbel -opost -onlcr -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke noflsh -ixon -crtscts -hupcl не помогло.

Других решений в интернете мне найти не удалось. При этом несколько раз после перезагрузки внезапно скрипт начинал выдавать результат, иногда все работало даже после 2х перезагрузок виртуальной машины. Однако потом все снова перестало работать.

Прошу помощи у сообщества, проблема как мне кажется очень не тривиальная.

Если у кого то есть мысли как по другому подружить php и arduino - готов выслушать любые предложения.

 

Заранее всем спасибо за помощь!

 

 

maksim
Offline
Зарегистрирован: 12.02.2012

Монитор порта закрываете? Может так попробовать:

$serial->deviceSet("/dev/ttyACM2");

http://stackoverflow.com/questions/13114275/php-serial-port-data-return-from-arduino

 

CityCat
CityCat аватар
Offline
Зарегистрирован: 13.06.2013

antotor, а вариант с Ethernet-shield не рассматривается?

antotor
Offline
Зарегистрирован: 10.09.2013

maksim пишет:

Монитор порта закрываете? Может так попробовать:

$serial->deviceSet("/dev/ttyACM2");

http://stackoverflow.com/questions/13114275/php-serial-port-data-return-from-arduino

 

Привет. Монитор порта естественно закрыт, виртуальная машина не может быть запущена пока порт занят кем то.

У меня нет устройств на

/dev/ttyACM2

Но для успокоения совести попробовал. Результат ожидаем:

eva@Eva:~/sites/eva/www$ php index.php
PHP Warning:  Specified serial port is not valid in /home/eva/sites/eva/www/php_serial.class.php on line 111
PHP Warning:  Unable to set the baud rate : the device is either not set or opened in /home/eva/sites/eva/www/php_serial.class.php on line 204
PHP Warning:  Unable to set parity : the device is either not set or opened in /home/eva/sites/eva/www/php_serial.class.php on line 254
PHP Warning:  Unable to set length of a character : the device is either not set or opened in /home/eva/sites/eva/www/php_serial.class.php on line 298
PHP Warning:  Unable to set the length of a stop bit : the device is either not set or opened in /home/eva/sites/eva/www/php_serial.class.php on line 335
PHP Warning:  Unable to set flow control mode : the device is either not set or opened in /home/eva/sites/eva/www/php_serial.class.php on line 376
PHP Warning:  The device must be set before to be open in /home/eva/sites/eva/www/php_serial.class.php on line 137
PHP Warning:  Device must be opened in /home/eva/sites/eva/www/php_serial.class.php on line 541
PHP Warning:  Device must be opened to read it in /home/eva/sites/eva/www/php_serial.class.php on line 474
Data:

 

 

antotor
Offline
Зарегистрирован: 10.09.2013

CityCat пишет:

antotor, а вариант с Ethernet-shield не рассматривается?

Пока есть хоть малейшая надежда победить без ethernet'a - не рассматриваю его.

leshak
Offline
Зарегистрирован: 29.09.2011
  // после подачи питания ждём секунду до готовности сенсора к работе
    delay(1000);

А где в php коде учитыавние этого факта?

Когда вы открываете порт - ардуина перегружается, вызвается setup(). Значит минимум 1 сек - ничего вы от нее не услышите. Да в любом случае - после посылки команды стоит чуток подожать покурутить какой-то while(!serial.avaliable()){}

А еще попробуйте сменить

case '1':
        // метод update заставляет сенсор выдать текущие измерения
        sensor.update();
       ....

На

case '1':
case 1:
        // метод update заставляет сенсор выдать текущие измерения
        sensor.update();

Что-бы оно и на цифру один срабатывало, и на символ единицу. Вдруг $serial->sendMessage(chr(1)); отправляет, все-таки именно байт, не конвертируя в строку (лень смотреть в доку что именно он делает :)

antotor
Offline
Зарегистрирован: 10.09.2013

leshak пишет:

  // после подачи питания ждём секунду до готовности сенсора к работе
    delay(1000);

А где в php коде учитыавние этого факта?

Когда вы открываете порт - ардуина перегружается, вызвается setup(). Значит минимум 1 сек - ничего вы от нее не услышите. Да в любом случае - после посылки команды стоит чуток подожать покурутить какой-то while(!serial.avaliable()){}

А еще попробуйте сменить

case '1':
        // метод update заставляет сенсор выдать текущие измерения
        sensor.update();
       ....

На

case '1':
case 1:
        // метод update заставляет сенсор выдать текущие измерения
        sensor.update();

Что-бы оно и на цифру один срабатывало, и на символ единицу. Вдруг $serial->sendMessage(chr(1)); отправляет, все-таки именно байт, не конвертируя в строку (лень смотреть в доку что именно он делает :)

Похоже моя проблема оказалась решаемой много проще чем я думал сначала. Огромное вам спасибо. установка sleep после открытия порта и отправки команды действительно помогла, сейчас данные корректно считываются.

Однако и до этого случались периоды "просветления" когда все вдруг начинало работать, а потом вдруг ломалось, но буду надеятся что все будет хорошо.

 

Большое вам спасибо!

maxi_10
Offline
Зарегистрирован: 05.01.2012

Образцовый пост!

Все четко, понятно, и разложено по полочкам.

antotor
Offline
Зарегистрирован: 10.09.2013

maxi_10 пишет:

Образцовый пост!

Все четко, понятно, и разложено по полочкам.

7 лет в технической поддержке работал  на разных уровнях :)

antotor
Offline
Зарегистрирован: 10.09.2013

Паралельно наролил смешную штуку. Похоже на то, что ардуино самоинициируется от данных которые сам же кладет в порт. Поясню. Допустим мой скетч залитый в ардуино выглядит так - если пришла еденица - показать температуру, если пришла двойка - показать освещенность.

На вход подаю еденицу. Если среди данных о температуре будет содержаться двойка или еденица - ардуино еще раз выдаст данные по этому запросу.

eva@Eva:~/sites/eva/www$ php index.php //в пхп передаю еденицу, на что ардуино должен вернуть температуру и влажность.
Data: OK:23:60 //вернул, тут все ок, однако содержится двойка в ответе и ардуино вызывает case 2: который содержит функцию показа освещенности.

901 //показал освещенность, еденичка инициирует вызов температуры.

Error: start failed (stage 1) //дальше порт не успевает быть прочитан несколько раз и еденица вызывает функцию снова и снова пока тайминг не кончится для запроса.

Error: start failed (stage 1)

Error: start failed (stage 1)

Error: start failed (stage 1)

Error: start failed (stage 1)

Error: start failed (stage 1)

Error: start failed (stage 1)

Error: start failed (stage 1)

Error: start failed (stage 1)

Error: start failed (stage 1)

Error: start failed (stage 1)

Error: start failed (stage 1)

Error: start failed (stage 1)

Error: start failed (stage 1)

Error: start failed (stage 1)

Error: start failed (stage 1)

Error: start failed (stage 1)

А проблема крылась в двойном условии

case '1':

case 1:

Если оставить только 1 то скетч не будет работать при обращении через монитор порта. Однако корректно работает в пхп. Если же оставить case '1' то скетч будет корректно отдавать данные в монитор порта - однако не будет работать при передаче данных из пхп.

 

Оставил пока вариант case 1. пхп работает, а для проверок буду делать временные скетчи с обоими условиями.

Andrey_Y_Ostanovsky
Offline
Зарегистрирован: 03.12.2012

Осталось только понять - зачем для этого нужен целый php вместо простого эха в соответствующий порт. :)

leshak
Offline
Зарегистрирован: 29.09.2011

>А проблема крылась в двойном условии

Проблемы кроется в том почему у вас идут лишние команды. А то что "двумя разными командами можно выполнить одно действие" - это не проблема.

Можно конечно убрать двойное условие, вам же никто не мешает из php слать не 1, а именно '1' . Ну на крайнек сделать  $serial->sendMessage(chr(49)); раз выяснилось что так байты посылаются.

Вообщем избавится от двойного условия - можно. Только трабла где-то не в этом. RX/TX закорочен, где-то в настройках системы сделать loopback, какое-то echo влючено отправляющие все приходящие от дуины назад и т.п.

antotor
Offline
Зарегистрирован: 10.09.2013

Andrey_Y_Ostanovsky пишет:

Осталось только понять - зачем для этого нужен целый php вместо простого эха в соответствующий порт. :)

"Целый php" нужен в дальнейших целях, пока я только разбираюсь с взаимодействием.

antotor
Offline
Зарегистрирован: 10.09.2013

leshak пишет:

>А проблема крылась в двойном условии

Проблемы кроется в том почему у вас идут лишние команды. А то что "двумя разными командами можно выполнить одно действие" - это не проблема.

Можно конечно убрать двойное условие, вам же никто не мешает из php слать не 1, а именно '1' . Ну на крайнек сделать  $serial->sendMessage(chr(49)); раз выяснилось что так байты посылаются.

Вообщем избавится от двойного условия - можно. Только трабла где-то не в этом. RX/TX закорочен, где-то в настройках системы сделать loopback, какое-то echo влючено отправляющие все приходящие от дуины назад и т.п.

Ну, как только я избавился от второго условия - работа нормализовалась. Как разобраться в том, почему в ардуино приходят данные отраженные - моих знаний наверное не хватит. Со своей стороны я ничего в ответ не посылаю, возможно это особенность класса php_serial, я его ковырял не очень глубоко - где то в нем возможно есть что то отправляющее сигнал назад.

Andrey_Y_Ostanovsky
Offline
Зарегистрирован: 03.12.2012

antotor пишет:

Со своей стороны я ничего в ответ не посылаю, возможно это особенность класса php_serial, я его ковырял не очень глубоко - где то в нем возможно есть что то отправляющее сигнал назад.

... либо настройки порта в системе.

Сделайте stty -a -F /dev/tty... того tty порта, на котором висит ардуина. Искать директивы, начинающиеся с echo, вобщем, man stty на эту тему.

antotor
Offline
Зарегистрирован: 10.09.2013

Сделал, много чего на эхо начинается

stty -a -F /dev/ttyS2
speed 9600 baud; rows 0; columns 0; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
-parenb -parodd cs8 hupcl -cstopb cread clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl -ixon -ixoff -iuclc -ixany -imaxbel -iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke
 

Спасибо, почитаю.