Пользователь
0,0
рейтинг
27 января 2012 в 18:52

Управление Arduino с телефона из песочницы

Добрый день!

Недавно заинтересовался идеей создания «умного дома». Так как из необходимых компонентов в моем распоряжении пока что имеются только arduino и телефон на андроиде, решено было начать с создания пульта управления и связи его с остальной частью системы.

Моё видение системы выглядит так:


Думаю стоит совместить домашний и веб-серверы, прикупив статический айпишник, но на первое время сойдет и так. Начнем с простого – научимся удаленно управлять светодиодом и LCD-дисплеем.

Web-server

На веб-сервере создаем БД с двумя таблицами – leds и texts. Таблица leds содержит 2 поля – id и status. Она содержит одну запись с актуальным состоянием светодиода. Таблица texts содержит 2 поля – id и text. Она также содержит одну запись с текстом, который в данный момент отображается на LCD-дисплее.

Теперь напишем пару скриптов, которые будем вызывать с телефона и передавать информацию для БД. Пишем на php.

Скрипт led.php (управление светодиодом):
<?php
$hostname = "localhost";
$username = "имя_пользователя";
$password = "пароль";
$database = "имя_бд";

$connect_DB = mysql_connect($hostname, $username, $password); //соединяемся с БД
if (!$connect_DB) { //если не получилось соединиться
    exit;        // закрыть скрипт
}

mysql_select_db($database); //выбираем БД
changeLED(); //вызываем функцию, меняющую состояние светодиода
mysql_close($connect_DB); //закрываем соединение с БД

function changeLED() //меняем состояние светодиода
{
	$query = "SELECT `status` FROM leds WHERE `id` = '1'"; //делаем запрос с БД к таблице leds (id = 1 - наш светодиод)
	$result = mysql_query($query);
	while ($_row = mysql_fetch_array($result,MYSQL_NUM)) //обходим выборку из результата запроса
	{
		//инвертируем состояние светодиода
		$st = (int)$_row[0];
		if ($st == 0)
			$st = 1;
		else
			$st = 0;
	}
	$query = "UPDATE leds SET `status` = '$st' WHERE `id` = '1'"; //записываем инвертированное значение в БД
	$result = mysql_query($query);
}
?>

Скрипт msg.php (управление LCD-дисплеем):
<?php
$hostname = "localhost";
$username = "имя_пользователя";
$password = "пароль";
$database = "имя_бд";

$connect_DB = mysql_connect($hostname, $username, $password); //соединяемся с БД
if (!$connect_DB) { //если не получилось соединиться
    exit;        // закрыть скрипт
}

mysql_select_db($database); //выбираем БД
changeText(); //вызываем функцию, меняющую текст, отображжаемый на LCD-дисплее
mysql_close($connect_DB); //закрываем соединение с БД

function changeText()
{
	$st= $_GET["msg"]; //GET-параметр msg содержит текст, переданный с телефона
	$query = "UPDATE texts SET `text` = '$st' WHERE `id` = '1'"; //меняем соответствующее поле в таблице texts для записи с id = 1 (наш LCD-дисплей)
	$result = mysql_query($query);
}
?>

Я думаю, что из комментариев ясно, как работают эти скрипты. Это все, что находится на веб-сервере. Теперь перейдем к домашнему серверу (или говоря проще, компьютеру, к которому подключен ардуино).

Домашний сервер

На нем будет постоянно работать программка (можно даже назвать ее – демон), посылающая запросы к БД и при изменении находящейся там информации, посылающая на COM-порт с ардуино соответствующую команду. Программку напишем на языке Processing:

import processing.serial.*; //библиотека для работы с COM-портом
import de.bezier.data.sql.*; //библиотека для работы с БД MySQL

Serial port;
MySQL dbconnection;
int prevLEDState = 0; //предыдущее состояние светодиода
String prevS = ""; //предыдущий текст, отпаврленный на LCD-дисплей

void setup()
{
  port = new Serial(this, "COM4", 9600); //инициализируем COM-порт 4 (на не прицеплена ардуина), скорость обмена - 9600 бод
  port.bufferUntil('\n');
  
  String user     = "имя_пользователя";
  String pass     = "пароль";
  String database = "имя_бд";

  dbconnection = new MySQL( this, "ваш_домен.ru", database, user, pass ); //соединяемся с БД
  dbconnection.connect();
}

void draw()
{
  //следим за информацией о светодиоде в БД
  dbconnection.query( "SELECT * FROM leds WHERE id = '1'" ); //делаем запрос к таблице leds
  while (dbconnection.next()) //обходим выборку из результата запроса
   {
    int n = dbconnection.getInt("status"); //получаем значение из поля status
    if (n != prevLEDState) //если оно изменилось по сравнению с предыдущем "тактом" работы программы, то посылаем команду на COM-порт
        {
          prevLEDState = n;
          port.write('1'); //первый переданный символ будет означать код выполняемой операции: 1 - управление светодиодом, 2 - управление LCD-дисплеем
          port.write(n);
        }
   }
  
  //следим за информацией о LCD-дисплее в БД
  dbconnection.query( "SELECT * FROM texts WHERE id = '1'" ); //делаем запрос к таблице texts
  while (dbconnection.next())//обходим выборку из результата запроса
    {
      String s = dbconnection.getString("text"); //получаем значение из поля text
      if (s != prevS)
      {
        prevS = s;
        port.write('2');
        port.write(s);        
      }
    }
  
  delay(50); //делаем задержку в 50 мс, чтобы не слать запросы непрерывно
}

Пояснять этот код я тоже не стану, все и так понятно.
Еще 1 важный момент. Чтобы программа с нашего компьютера могла обращаться к БД, расположенной на удаленном сервере, надо это разрешить. Вводим наш ip в список разрешенных:


Далее по плану – приложение для телефона.

Приложение для телефона

Телефон у меня андроиде, для него и пишем. Не буду сильно вдаваться в подробности (очень хорошо как о установке среды программирования, так и о написании первого приложения написано вот в этой статье — ссылка).

Внешний вид приложения выглядит довольно скромненько, но в данном случае это не главное:


Приведу только отрывки кода программы под Android. Функция, вызывающая скрипт, управляющий светодиодом:
public void changeLED()
    {
    	try
    	{
	    	URL url1 = new URL("http://ваш_домен.ru/led.php");
	    	HttpURLConnection urlConnection = (HttpURLConnection) url1.openConnection();
    		try {
    		     InputStream in = new BufferedInputStream(urlConnection.getInputStream());
    		}
    		    finally {
    		     urlConnection.disconnect();
    		   }
    	}
    	catch (Exception e)
    	{
    	}
    	
    } 

Функция, отсылающая текст для отображения на LCD-дисплее:
public void submitMsg()
    {
    	final EditText tt = (EditText) findViewById(R.id.editText1);
    	try
    	{
	    	URL url1 = new URL("http://ваш_домен.ru/msg.php?msg="+tt.getText());
	    	HttpURLConnection urlConnection = (HttpURLConnection) url1.openConnection();
    		try {
    		     InputStream in = new BufferedInputStream(urlConnection.getInputStream());
    		}
    		    finally {
    		     urlConnection.disconnect();
    		   }
    	}
    	catch (Exception e)
    	{
    	}
    	
    }

Ну и главная функция, в которой происходит привязка обработчиков событий к кнопкам:
public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        final Button btn1 = (Button) findViewById(R.id.button1);

        btn1.setOnClickListener(new Button.OnClickListener() {

            public void onClick(View v) // клик на кнопку
            {
            	changeLED();
            }
        });
        
        final Button btn2 = (Button) findViewById(R.id.button2);
        btn2.setOnClickListener(new Button.OnClickListener() {

            public void onClick(View v) // клик на кнопку
            {
            	submitMsg();
            }
        });
        
    }

И еще один важный момент – добавить разрешение приложению на выход в интернет. Для этого в файл AndroidManifest.xml (он находится в директории нашего андроид-приложения) надо добавить строчку:
<uses-permission android:name="android.permission.INTERNET"/>

Экспортируем наше приложение в файл APK и устанавливаем на телефон. Пульт управления умным домом готов!

Arduino

Ну и наконец последнее, но не по значению – подключение ардуино и ее прошивка. Схема подключения LCD-экрана и светодиода к Arduino Uno выглядит следующим образом:


Резистор берем на 220 Ом. Более подробно про подключение LCD-экрана можно прочитать здесь — ссылка

А вот как это все выглядит в реальности:


Правда красиво?

Задача ардуино состоит в прослушивании того, что программа-демон на домашнем сервере посылает на COM-порт, к которому и подключена ардуино (хотя фактически подключение идет по USB-кабелю, но компьютер распознает его как последовательный порт). После получения каких-либо данных с компьютера, контроллер по первому символу переданной информации распознает код команды (т.е. чем сейчас предстоит управлять – LCD-дисплеем или светодиодом). Далее в зависимости от кода и следующей за ним информации выполняется либо включение/выключение светодиода, либо вывод на дисплей переданного сообщения. Итак, вот собственно код:

#include <LiquidCrystal.h> //встроенная библиотека для работы с LCD-дисплеем

boolean isExecuting = false; //переменная, отражающая, что уже идет выполнение какой-то команды
//Cразу поясню, для чего это нужно. За каждый "такт" цикла loop ардуино считывает с COM-порта код одного символа.
//Поэтому строка будет передаваться за несколько тактов. При этом перед каждой из двух возможных команд (смена состояния светодиода и передача текста на дисплей)
//передается код этой команды (1 и 2 соответственно). Чтобы отделить коды команд от передаваемой далее информации (состояния светодиода или текста для дисплея),
//используется эта переменная.

LiquidCrystal lcd(4,5,10,11,12,13); //инициализация дисплея

int ledPin = 8; //номер пина ардуино, на к которому подсоединен светодиод
int prevLEDStatus = 0; //предыдущий статус светодиода (вкл/выкл)
int newLEDStatus = 0; //новый статус светодиода
int cmd = 0; //код выполняемой команды

void setup()
{
  Serial.begin(9600); //инициализация COM-порта (9600 - скорость обмена в бодах)
  pinMode(ledPin,OUTPUT); //инициализация 8-го пина ардуино как выхода
  lcd.begin(20,4); //инициализация LCD-дисплея (4 строки по 20 символов)
}

void loop()
{
  if (Serial.available() > 0) //если на COM-порт пришла какая-то информация
    {
      if (isExecuting == false) //если в данный момент не идет выполнение никакой команды
        {
          cmd = Serial.read() - '0'; //считываем код выполняемой команды
          isExecuting = true; //теперь переменная показывает, что началось выполнение команды
        }
      
      if (cmd == 1) //управление светодиодом
       {
         newLEDStatus = (int) Serial.read(); //считываем новый статус светодиода
         if (newLEDStatus != prevLEDStatus) //если он изменился по сравнению с текущим статусом, то меняем текущий статус
          {
            digitalWrite(ledPin,newLEDStatus); 
            prevLEDStatus = newLEDStatus;
          }
       }
      else //управление дисплеем
       {
         if (isExecuting == false) //если в данный момент не идет выполнение никакой команды
          {
            lcd.clear(); //очищаем экран
          }
         else
          {
            lcd.print((char)Serial.read()); //выводим символ на дисплей
          }
       }
    }
 else //если на COM-порт не пришла никакая информация
   {
     delay(50); //делаем задержку в 50 мс
     if (Serial.available() <= 0) //если информации по-прежнему нет
       isExecuting = false; //считаем, что никакая команда не выполняется
   }
    
}

Я думаю, пояснений он не требует, так как я очень подробно все расписал в комментариях. Единственное, что стоит отметить, так это некоторые ограничения на передаваемые для вывода на дисплей строки. Они не должны содержать пробелов (это ограничение накладывается несовершенством моего алгоритма) и не должны содержать кириллицы (т.к. она поддерживается не всеми дисплеями, а если и поддерживается, то требует передачи кодов символов в своей собственной кодировке, преобразовывать символы в которую нет никакого желания).

Заключение

Ну вот и все. Оказалось, что это довольно просто.
Видео того как все работает:


И напоследок приведу ссылки на ресурсы, которые использовал:
Очень хорошие уроки по ардуино — ссылка
Ссылка на среду Processing — ссылка
Еще раз ссылка на статью по созданию первого приложения на Android – ссылка

Успехов всем!
Константин @Kefis
карма
3,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое

Комментарии (41)

  • +1
    По-моему к Arduino даже были Ethernet-шильды. Так что в идеале её саму можно научить цепляться к серверу.
    • 0
      Да, есть и Ethernet и wifi шилды. Но в наличии у меня пока нет ни того, ни другого. Да и когда надо будет управлять чем-то большим, чем дисплей и светодиод, компьютер будет как нельзя кстати.
      • 0
        ИМХО, ПК в таких целях (если только речь не об умном доме целиком) — слишком много. Кстати, а с КПК пробовали играться в качестве ядра системы управления? По-моему некий компромисс между ПК и контроллером все же.
        • 0
          С КПК не пробовал. А вообще интересный вариант — и шума нету как от компьютера, и возможностей больше, чем у контроллера. Пока хочу подключить в качестве ядра старенький компьютер. А потом в идеале — raspberry.
          • 0
            Тогда мой совет — ищите либо с USB-хостом, либо с аппаратным COM-ом — очень облегчает жизнь. Но без контроллера в качестве GPIO платы там все равно не обойтись. Можно еще использовать IrDA и Bluetooth — но накладные расходы выше.
            • 0
              Возьмите ARM на заметку (Cortex-M3), там и нормальная производительности 100 МГЦ большое количество различных функций, и на ebay и куча различных отладок со всем чем надо))) Экран 3.2 + тач+Ethernet+232 х2+CAN x2+ 485 + USB host и все это за 70$ с доставкой)
              • +2
                скиньте линк пожалуйста
              • 0
                Да-да, плюсуюсь по поводу линка. И еще — он обычно прошит чем-то? Или на усмотрение утопающих? Вот только ebay меня настораживает.
                • 0
                  Запрос на ебай """ ARM LCD board """, я заказывал ARM NXP LPC1768 Development Board, (у этого продавца или нет не помню… но точно эту плату),
                  к.с если интересно сделаю мини обзор.
                  • 0
                    Спасибо! По-моему на этом чипе даже .NET MF идет.
  • 0
    С КПК не пробовал. А вообще интересный вариант — и шума нету как от компьютера, и возможностей больше, чем у контроллера. Пока хочу подключить в качестве ядра старенький компьютер. А потом в идеале — raspberry.
  • –1
    Спасибо за интересную статью!
    Очень все доходчиво и познавательно!
    • 0
      Вам спасибо!
  • +1
    Я реализовываю похожую систему. Только в качестве домашнего сервера — роутер на прошивке OpenWRT. На роутере крутится легкий веб-сервер Lighttpd. Камень преткновения — считывание информации с Arduino. Не нашел ничего адекватного, чтобы и роутер не загнулся от недостатка памяти и производительности, и работало на низком уровне. Пробовал и perl, и bash, и даже скрипты на Lua. Чтобы было понятно — почти всё в embedded linux обрезано донельзя. Причем проблем писать в порт нет, чтобы послать управляющую команду Arduino, проблема именно в считывании данных с датчиков. Кстати, как и некоторые отписавшиеся смотрю в сторону замены роутера на raspberry pi.
    • –1
      P.S. приятно видеть ссылку на свою статью про Android.
  • 0
    Неплохая реализация! Пара комментариев:

    1. Запрашивать базу раз в 50 мс + время самого запроса — это немного жестоко, может быть стоит снизить частоту до хотя бы раз в секунду? Хотя, понятное дело, зависит от применения.

    2. Лазить в базу напрямую через веб нежелательно — установка соединения с базой занимает время, плюс есть определенные вопросы по безопасности (которые приходится решать селективным доступом по IP — а если домашний IP поменяется?). ИМХО было бы лучше закрыть доступ к базе извне (читай — перекрыть порт 3306), и отдавать информацию через промежуточный PHP скрипт, естественно с каким-то контролем доступа (пусть даже пароль через $_GET). Можно еще конечно SSL прикрутить, чтобы не светиться, но это для совсем серьезных случаев.

    • +1
      1. 50 мс — это просто наобум взятая цифра. Тут действительно, все зависит от применения.
      2. Вариант с промежуточным скриптом очень интересен. Я думаю, что даже наверное лучше оставить веб-сервер и домашний компьютер разделенными (не покупать внешний ip для компьютера и не размещать БД на нем). Именно в целях безопасности, чтобы все что могло воздействовать на компьютер — это была информация по запросу из БД.

      P.S. По поводу безопасности при написании статьи я вообще не задумывался и в одной из программ засветил свой домен с БД. Кто-то очень внимательно видимо прочел статью и уже отправил мне послание. =)
      • 0
        Да, лучше это разделить, и как уже написано, реализовать запрос с самой Ардуино… ну или переползти на Raspberry, но это уже другая реализация получится.
      • 0
        Послание было дружественным?
        • 0
          Совсем нет.
  • 0
    А какую практическую пользу это может принести?
    • 0
      Как первое что пришло в голову («спасибо» активной рекламе) — управлять коптером/вертолетом прямо с мобильного телефона. Вариантов применения — безумное количество.
      • 0
        Управление, например, освещением или включением/выключением телевизора позволит создать видимость того, что в помещении кто-то находится. А включать/выключать диод и выводить сообщение на ЖК дисплей — от этого какая практическая польза?

        И, мне кажется, с мобильного будет крайне неудобно управлять вертолётом. Да и возможные задержки сигнала не пойдёт на пользу.
        • +1
          Диод и дисплей были в учебных целях. Вместо у них уже можно подключать, например, освещение.

          А насчет управления вертолетом с мобильника — наоборот очень удобно. Особенно если привязать его к гироскопу в телефоне. Однажды видел, как с планшетника так управляли квадрокоптером — очень впечатлило.
  • 0
    Лучше делать так: у мобильника и устройства есть два jabber-аккаунта. Jabber-клиенты примитивны и есть для всего. Всё.
    • 0
      Если вы на андроиде залогинены в гуглоаккаунт, то у вас уже есть активный джаббер на телефоне. Но не знаю, есть ли простой доступ другим программам, или проще поднять отдельный джаббер.
      • 0
        Передавать специфические команды через пользовательский гуглталк не лучшая идея.
    • 0
      А сырые сокеты с readLine/writeLine в цикле разве не проще? Один же фиг полученную команду потом надо отпарсить.
      • 0
        Смысл в том чтобы в моменты потери связи команды бы накапливались в каком-нибудь буфере. Сокеты же требуют прямого сетевого подключения между мобильником и конечным устройством.
        • 0
          А что мешает реализовать socket-based сервер, и простой message-oriented протокол. И пусть себе накапливаются. Jabber — это XML. А XML в embedded — это всегда оверхед. Вы же не будете спорить, что обработка бинарного формата, или простых строк куда проще?
          • 0
            Дык фишка в том, что в Jabber это всё уже есть, тем более не нужно подымать свой сервер.
            • 0
              Для работы с телеметрией я бы даже свой поднял, имхо публичные сервисы — это менее безопасно.
              • 0
                Какой аспект безопасности вас тревожит?
  • +1
    езренет шилд стоит 7 баксов. рекомендую.
    из минусов очень тяжелая библиотека tcp/ip стека, занимает около 10кбайт памяти.
    делал себе езернет контроллер для IR пульта. принимает команды с любого пульта и формирует соотвествующий http запрос в сеть. глюки не замечены.
    • 0
      Где можно найти такой дешевый Ethernet Shield? На всём ebay я самое дешевое что видел $16. На Dealextreme и того дороже — $20.

      За $6.5 видел только чисто кусок текстолита с вытравленными дорожками (PCB), видимо, чтобы напаять нужные детали самостоятельно. Вы может спутали с ним?
      • +1
        я вот такой использовал. www.ebay.com/itm/280706213546. если внимательно читать документацию проблем нет. из нюансов 1) потребляет значительный ток по шине 3.3 вольта. не все ардуины могут столько выдать. 2) логические уровни 3.3в, но ардуина их понимает, преобразователь уровней не обязательно 3) не забыть подтянуть ресет к +5в.
        • 0
          Класс, спасибо
  • +1
    А чем сокеты обычные не угодили? Баз данных, web сервер для этого как то уж слишком.
    • 0
      Да ничем, просто это альтернативный вариант. А вообще, как уже выше написали, оптимальным наверное было бы использовать ethernet-шилд.
  • 0
    Стив бы такую архитектуру не одобрил. WifiShield + TCP/IP stack и для особо утонченных autodiscovery via Zeroconf. Надо чтобы было проще.
  • 0
    >import de.bezier.data.sql.*; //библиотека для работы с БД MySQL
    У меня почему-то на эту строчку ругается и говорит, что «No library found for de.bezier.data.sql», хотя, вроде, папку libraries создал в корне папки скетчбука, .zip с библиотекой распаковал, переименовал в SQLibrary распакованную папку, как сказано у автора тут bezier.de/processing/libs/sql/.

    Автор, вы дополнительно что-то с этим моментом колдовали, или у вас без проблем прошло?

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.