ESP8266 Android приложение для управления цифровыми выводами Arduino и мигания светодиодами

Добавлено 27 августа 2017 в 15:00

В данной статье вы найдете код Arduino и Android, который понадобится вам для управления цифровыми выводами вашей платы Arduino с Android смартфона через ESP8266. Код, приведенный ниже, снабжен комментариями и прост для понимания.

Модуль ESP-01
Модуль ESP-01

Код Arduino

Код Arduino ищет строку +IPD всякий раз, когда есть данные, доступные в ESP8266; если строка найдена, то это означает, что устройство (в данном случае Android смартфон) подключается к ESP. Затем программа ищет строку pin=, затем считывает номер вывода, переключает состояние вывода на противоположное и посылает обратно строку "Pin x is ON/OFF".

Код ниже работает со всеми номерами выводов, но не использует выводы 0-3, поскольку они используются для последовательной связи с компьютером и ESP8266. Кроме того, для получения данных от ESP8266 и/или для отладки просто откройте окно монитора последовательного порта в Arduino IDE.

#include <SoftwareSerial.h>
 
#define DEBUG true
 
// Делает RX линию Arduino выводом 2, а TX линию Arduino выводом 3.
// Это означает, что вам необходимо подключить TX линию от ESP к выводу 2 Arduino,
// а RX линию от ESP к выводу 3 Arduino. 
SoftwareSerial esp8266(2,3); 


void setup()
{
  Serial.begin(9600);
  esp8266.begin(9600); // скорость передачи вашего ESP может отличаться
  
  pinMode(11,OUTPUT);
  digitalWrite(11,LOW);
  
  pinMode(12,OUTPUT);
  digitalWrite(12,LOW);
  
  pinMode(13,OUTPUT);
  digitalWrite(13,LOW);
  
  pinMode(10,OUTPUT);
  digitalWrite(10,LOW);
   
  // перезапустить модуль 
  sendCommand("AT+RST\r\n",2000,DEBUG);
  // настроить как точку доступа  
  sendCommand("AT+CWMODE=1\r\n",1000,DEBUG); 
  sendCommand("AT+CWJAP=\"mySSID\",\"myPassword\"\r\n",3000,DEBUG);
  delay(10000);
  // получить ip адрес
  sendCommand("AT+CIFSR\r\n",1000,DEBUG); 
  // настроить для нескольких соединений
  sendCommand("AT+CIPMUX=1\r\n",1000,DEBUG); 
  // включить сервер на порту 80
  sendCommand("AT+CIPSERVER=1,80\r\n",1000,DEBUG);
  
  Serial.println("Server Ready");
}
 
void loop()
{
  if(esp8266.available()) // проверить, послал ли esp сообщение
  {
 
    
    if(esp8266.find("+IPD,"))
    {
      // ждать, когда последовательный буфер заполнится (прочитаются все последовательные данные)  
      delay(1000);    
      // получить id подключения, чтобы мы могли отключиться
      int connectionId = esp8266.read()-48;
      // вычитаем 48 потому, что функция read() возвращает
      // десятичное значение в ASCII, а 0 (первое десятичное число) начинается с 48
                                     
          
      esp8266.find("pin="); // передвинуть курсор к "pin="
          
      int pinNumber = (esp8266.read()-48); // получить первую цифру, т.е., если вывод 13, то 1-ая цифра равна 1
      int secondNumber = (esp8266.read()-48);
      if(secondNumber>=0 && secondNumber<=9)
      {
        pinNumber*=10;
        pinNumber +=secondNumber; // получить вторую цифру, т.е., если вывод 13, то 2-ая цифра равна 3, 
                                  // и добавить ее к первой цифре
      }
     
      digitalWrite(pinNumber, !digitalRead(pinNumber)); // переключить состояние вывода    
     
      // собрать строку, которая посылается обратно на устройство, 
      // которое запросило переключение вывода
      String content;
      content = "Pin ";
      content += pinNumber;
      content += " is ";
     
      if(digitalRead(pinNumber))
      {
        content += "ON";
      }
      else
      {
        content += "OFF";
      }
     
      sendHTTPResponse(connectionId,content);
     
     // создать команду закрытия соединения
     String closeCommand = "AT+CIPCLOSE="; 
     closeCommand+=connectionId; // добавить id соединения
     closeCommand+="\r\n";
     
     sendCommand(closeCommand,1000,DEBUG); // закрыть соединение
    }
  }
}

/*
 * Name: sendData
 * Description: Функция, используемая для отправки данных на ESP8266.
 * Params: command - данные/команда для отправки; timeout - время ожидания отклика; debug - печатать в консоль?(true = да, false = нет)
 * Returns: Отклик от esp8266 (если есть отклик)
 */ 
String sendData(String command, const int timeout, boolean debug)
{
  String response = "";
    
  int dataSize = command.length();
  char data[dataSize];
  command.toCharArray(data,dataSize);
           
  esp8266.write(data,dataSize); // передача символов на esp8266
  if(debug)
  {
    Serial.println("\r\n====== HTTP Response From Arduino ======");
    Serial.write(data,dataSize);
    Serial.println("\r\n========================================");
  }
    
  long int time = millis();
    
  while( (time+timeout) > millis())
  {
    while(esp8266.available())
    {
  
      // У esp есть данные, поэтому вывести их в консоль
      char c = esp8266.read(); // прочитать следующий символ.
      response+=c;
    }  
  }
    
  if(debug)
  {
    Serial.print(response);
  }
  
  return response;
}

/*
 * Name: sendHTTPResponse
 * Description: Функция, которая посылает HTTP 200, HTML UTF-8 отклик
 */ 
void sendHTTPResponse(int connectionId, String content)
{
     
  // build HTTP response
  String httpResponse;
  String httpHeader;
  // HTTP Header
  httpHeader = "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\n"; 
  httpHeader += "Content-Length: ";
  httpHeader += content.length();
  httpHeader += "\r\n";
  httpHeader +="Connection: close\r\n\r\n";
  // Здесь в коде баг: последний символ в "content" не посылается, поэтому я добавил дополнительный пробел
  httpResponse = httpHeader + content + " ";
  sendCIPData(connectionId,httpResponse);
}
 
/*
 * Name: sendCIPDATA
 * Description: посылает команду CIPSEND=<id_подключения>,<данные>
 */
void sendCIPData(int connectionId, String data)
{
  String cipSend = "AT+CIPSEND=";
  cipSend += connectionId;
  cipSend += ",";
  cipSend +=data.length();
  cipSend +="\r\n";
  sendCommand(cipSend,1000,DEBUG);
  sendData(data,1000,DEBUG);
}
 
/*
 * Name: sendCommand
 * Description: Функция, используемая для отправки данных на ESP8266.
 * Params: command - данные/команда для отправки; timeout - время ожидания отклика; debug - печатать в консоль?(true = да, false = нет)
 * Returns: Отклик от esp8266 (если есть отклик)
*/
String sendCommand(String command, const int timeout, boolean debug)
{
  String response = "";
  
  esp8266.print(command); // передача символов на esp8266
  
  long int time = millis();
  
  while( (time+timeout) > millis())
  {
    while(esp8266.available())
    {
  
      // У esp есть данные, поэтому вывести их в консоль
      char c = esp8266.read(); // прочитать следующий символ.
      response+=c;
    }  
  }
  
  if(debug)
  {
    Serial.print(response);
  }
    
  return response;
}

Код Android приложения

Код Android приложения, приведенный ниже, был скомпилирован с помощью Android Studio и должен работать на Android устройствах с операционной системой Android 2.2 и выше.

Файл активности

Файл активности обрабатывает взаимодействие пользователя с UI (пользовательским интерфейсом). Когда нажимается кнопка, IP адрес и номер порта сохраняются, поэтому вам не нужно набирать их снова, затем по IP адресу отправляется HTTP запрос с заданными параметром pin и значение вывода.

package com.allaboutee.httphelper;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;

public class MainActivity extends Activity implements View.OnClickListener {

    public final static String PREF_IP = "PREF_IP_ADDRESS";
    public final static String PREF_PORT = "PREF_PORT_NUMBER";
    // объявление кнопок и текстовых полей ввода
    private Button buttonPin11,buttonPin12,buttonPin13;
    private EditText editTextIPAddress, editTextPortNumber;
    // общие объекты параметров, используемые для сохранения IP адреса и порта, чтобы
    // пользователь не вводил их в следующий раз, когда он открывает приложение
    SharedPreferences.Editor editor;
    SharedPreferences sharedPreferences;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        sharedPreferences = getSharedPreferences("HTTP_HELPER_PREFS",Context.MODE_PRIVATE);
        editor = sharedPreferences.edit();

        // назначить кнопки
        buttonPin11 = (Button)findViewById(R.id.buttonPin11);
        buttonPin12 = (Button)findViewById(R.id.buttonPin12);
        buttonPin13 = (Button)findViewById(R.id.buttonPin13);

        // назначить поля ввода
        editTextIPAddress = (EditText)findViewById(R.id.editTextIPAddress);
        editTextPortNumber = (EditText)findViewById(R.id.editTextPortNumber);

        // назначить слушателя кнопок (этот класс)
        buttonPin11.setOnClickListener(this);
        buttonPin12.setOnClickListener(this);
        buttonPin13.setOnClickListener(this);

        // получить IP адрес и номер порта из последнего раза, когда пользователь использовал
        // приложение, или поместить пустую строку "", если это первый раз
        editTextIPAddress.setText(sharedPreferences.getString(PREF_IP,""));
        editTextPortNumber.setText(sharedPreferences.getString(PREF_PORT,""));
    }


    @Override
    public void onClick(View view) {

        // номер вывода
        String parameterValue = "";
        // получить ip адрес
        String ipAddress = editTextIPAddress.getText().toString().trim();
        // получить номер порта
        String portNumber = editTextPortNumber.getText().toString().trim();


        // сохранить IP адрес и номер порта для следующего использования приложения
        editor.putString(PREF_IP,ipAddress);    // установить значение ip адреса для сохранения
        editor.putString(PREF_PORT,portNumber); // установить номер порта для сохранения
        editor.commit(); // сохранить IP и PORT

        // получить номер порта от кнопки, которая была нажата
        if(view.getId()==buttonPin11.getId())
        {
            parameterValue = "11";
        }
        else if(view.getId()==buttonPin12.getId())
        {
            parameterValue = "12";
        }
        else
        {
            parameterValue = "13";
        }



        // выполнить HTTP запрос
        if(ipAddress.length()>0 && portNumber.length()>0) {
            new HttpRequestAsyncTask(
                    view.getContext(), parameterValue, ipAddress, portNumber, "pin"
            ).execute();
        }
    }

    /**
     * Description: Послать HTTP Get запрос на указанные ip адрес и порт.
     * Также послать параметр "parameterName" со значением "parameterValue".
     * @param parameterValue номер порта, у которого необходимо изменить состояние
     * @param ipAddress ip адрес, на который необходимо послать запрос
     * @param portNumber номер порта ip адреса
     * @param parameterName
     * @return Текст ответа с ip адреса или сообщение ERROR, если не получилось получить ответ
     */
    public String sendRequest(String parameterValue, String ipAddress, String portNumber, String parameterName) {
        String serverResponse = "ERROR";

        try {

            HttpClient httpclient = new DefaultHttpClient(); // создать HTTP клиента
            // установить URL, например, http://myIpaddress:myport/?pin=13 (например, переключить вывод 13)
            URI website = new URI("http://"+ipAddress+":"+portNumber+"/?"+parameterName+"="+parameterValue);
            HttpGet getRequest = new HttpGet(); // создать объект HTTP GET
            getRequest.setURI(website);         // установить URL для GET запроса
            HttpResponse response = httpclient.execute(getRequest); // выполнить запрос
            // получить ответ сервера с заданным ip адресом
            InputStream content = null;
            content = response.getEntity().getContent();
            BufferedReader in = new BufferedReader(new InputStreamReader(
                    content
            ));
            serverResponse = in.readLine();
            // Закрыть соединение
            content.close();
        } catch (ClientProtocolException e) {
            // ошибка HTTP
            serverResponse = e.getMessage();
            e.printStackTrace();
        } catch (IOException e) {
            // ошибка ввода/вывода
            serverResponse = e.getMessage();
            e.printStackTrace();
        } catch (URISyntaxException e) {
            // ошибка синтаксиса URL
            serverResponse = e.getMessage();
            e.printStackTrace();
        }
        // вернуть текст отклика сервера
        return serverResponse;
    }


    /**
     * AsyncTask необходим для выполнения HTTP запроса в фоне, чтобы они не блокировали
     * пользовательский интерфейс.
     */
    private class HttpRequestAsyncTask extends AsyncTask<Void, Void, Void> {

        // объявить необходимые переменные
        private String requestReply,ipAddress, portNumber;
        private Context context;
        private AlertDialog alertDialog;
        private String parameter;
        private String parameterValue;

        /**
         * Description: Конструктор класса asyncTask. Назначить значения, используемые в других методах.
         * @param context контекст приложения, необходим для создания диалога
         * @param parameterValue номер вывода для переключения
         * @param ipAddress ip адрес, на который необходимо послать запрос
         * @param portNumber номер порта ip адреса
         */
        public HttpRequestAsyncTask(Context context, String parameterValue, String ipAddress, String portNumber, String parameter)
        {
            this.context = context;

            alertDialog = new AlertDialog.Builder(this.context)
                    .setTitle("HTTP Response From IP Address:")
                    .setCancelable(true)
                    .create();

            this.ipAddress = ipAddress;
            this.parameterValue = parameterValue;
            this.portNumber = portNumber;
            this.parameter = parameter;
        }

        /**
         * Name: doInBackground
         * Description: Отправляет запрос на ip адрес
         * @param voids
         * @return
         */
        @Override
        protected Void doInBackground(Void... voids) {
            alertDialog.setMessage("Data sent, waiting for reply from server...");
            if(!alertDialog.isShowing())
            {
                alertDialog.show();
            }
            requestReply = sendRequest(parameterValue,ipAddress,portNumber, parameter);
            return null;
        }

        /**
         * Name: onPostExecute
         * Description: Данная функция выполняется после возвращения ответа на HTTP запрос на ip адрес.
         * Функция устанавливает сообщение диалога с текстом ответа от сервера и отображает диалог,
         * если он уже не показан (в случае, если он был закрыт случайно);
         * @param aVoid void параметр
         */
        @Override
        protected void onPostExecute(Void aVoid) {
            alertDialog.setMessage(requestReply);
            if(!alertDialog.isShowing())
            {
                alertDialog.show(); // показать диалог
            }
        }

        /**
         * Name: onPreExecute
         * Description: Данная функция выполняется перед отправкой HTTP запроса на ip адрес.
         * Функция установит сообщение диалога и отобразит диалоговое окно.
         */
        @Override
        protected void onPreExecute() {
            alertDialog.setMessage("Sending data to server, please wait...");
            if(!alertDialog.isShowing())
            {
                alertDialog.show();
            }
        }

    }
}

Файл макета

Этот файл просто создает пользовательский интерфейс (кнопки и поля ввода с подсказками).

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical">


        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:text="IP Address:"
            android:id="@+id/textView" />

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="e.g. 192.168.0.10"
            android:id="@+id/editTextIPAddress" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Port Number:"
            android:id="@+id/textView2" />

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="number"
            android:ems="10"
            android:hint="e.g. 80"
            android:id="@+id/editTextPortNumber" />

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Pin 11"
            android:id="@+id/buttonPin11" />

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Pin 12"
            android:id="@+id/buttonPin12" />

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Pin 13"
            android:id="@+id/buttonPin13" />
    </LinearLayout>

</ScrollView>

Файл манифеста

Единственное изменение, которое я должен был внести в файл манифеста, – это добавить разрешение работы с интернетом для работы с HTTP запросами.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.allaboutee.httphelper" >
 
    <uses-permission android:name="android.permission.INTERNET" />
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".HomeActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
 
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
 
</manifest>
 

Вот и всё! Надеюсь, статья оказалась полезной. Оставляйте комментарии!


На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.


Сообщить об ошибке