Яндекс.Метрика

    Песочница

    Управление 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, "muz-news.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 – ссылка

    Успехов всем!