ESP8266 Android приложение для управления цифровыми выводами Arduino и мигания светодиодами
В данной статье вы найдете код Arduino и Android, который понадобится вам для управления цифровыми выводами вашей платы Arduino с Android смартфона через ESP8266. Код, приведенный ниже, снабжен комментариями и прост для понимания.
Код 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>
Вот и всё! Надеюсь, статья оказалась полезной. Оставляйте комментарии!