Pull to refresh

Управление Arduino с телефона

Reading time7 min
Views99K
Добрый день!

Недавно заинтересовался идеей создания «умного дома». Так как из необходимых компонентов в моем распоряжении пока что имеются только 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 – ссылка

Успехов всем!
Tags:
Hubs:
Total votes 28: ↑24 and ↓4+20
Comments41

Articles