Подписаться на получение новых статей на почту:

Связываем Android (сокеты) и ESP8266 (TCP-сервер). Шаг №89

Всем привет. В данной статье мы с Вами познакомимся с принципом приема данных в ОС Android, используя socket и модуль Wi-Fi ESP8266. Добавим в приложение SmartHouse поле для вывода информации, и несколько функциональных кнопок. Но все по порядку. Для начала настроим модуль, как TCP-сервер (статья № 86 и №57 : вывод данных в браузер используя МК STM и МК AVR, соответственно). Вспомним, что мы уже умеем- это: поиск сетей, подключение к выбранной в кликабельном списке (предыдущая запись) и управление элементами SVG формата. И познакомимся с основами работы с сетью в Java.

Возвращаясь к прошлой статье, где приводилась ссылка по поводу изменения подключения к сетям (Android), я приведу еще небольшую выдержку из статьи:
For HTTP requests you can use Network#openConnection (java.net.URL), directly routing your request to this network.
For low-level socket communication, open a socket and call Network#bindSocket (java.net.Socket), or alternatively use Network#getSocketFactory.
Мы должны использовать один из двух методов. Суть подключение в следующем: Android-приложение будет TCP / IP-клиентом, ну а модуль - TCP / IP-сервером. На стороне Android – это просто сетевая связь. Использование метода зависит от прошивки ESP8266. Если он реализует HTTP-сервер вы можете использовать HttpUrlConnection и GET или POST-запросы на стороне ОС и соответствующий скрипт на стороне esp8266. Если он реализует ServerSocket. Мы можем использовать соединение Socket с Socket Client на стороне Android. В данном случае это последний вариант.

Что же такое сокет. Socket (разъём)– это программный интерфейс, механизм обмена данными между процессами, которые могут быть на различных машинах и соединены в одну сеть, или другими словами это абстрактный объект, представляющий конечную точку соединения.  Для того, чтобы мы могли общаться, нам необходимо знать адрес компьютера, на котором запущен процесс, и номер порта, по которому будет происходить обмен. Т.е. по сути сокет это соотношение IP и port, где IP используется для навигации пакета внутри интернета, а порт — для навигации пакета внутри компа (мобилки).

Сокет позволяет отдельному компьютеру обслуживать как множество различных клиентов, так и множество различных типов информации, что достигается за счет использования порта – нумерованного сокета  на определенной машине. Сокетные коммуникации происходят по определенному протоколу – IP, TCP, UDP. Мы реализуем протокол TCP/IP, который после установки резервирует первые 1024 порта для специфических протоколов, поэтому в данном коде мы используем номер порта 8080, т.к. может быть ситуация, что без доступа суперпользователя (root — права), используя порт менее 1024 будем получать исключения (Exception). TCP/IP сокеты  применяются для реализации надежных двунаправленных, постоянных соединений между точками – хостами в интернете на основе потоков, а также для подключения ввода/вывода к другим программам. В Java существует два вида сокетов TCP – для серверов и клиентов, соответственно ServerSocet and Socket. Класс Socet может быть просмотрен в любое время на предмет извлечения информации об адресе и порте, ассоциированной с ним. Например, один из них мы использовали для просмотра IP адреса сервера. - InetAddress.getByName (host), где getByName (host) один из методов-фабрик (статические методы класса возвращают экземпляр этого класса), даного класса, который передает имя хоста, причем может использоваться как адрес IPv4, так и IPv6. . Ниже код в OnCreate, где мы привязываем список к массиву, для вывода адресов:
ipList = new ArrayList ();
     adapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1, android.R.id.text1, ipList);
     listViewIp.setAdapter (adapter);
     btnScan.setOnClickListener (new View.OnClickListener () {
     @Override
     public void onClick (View v) {
           new ScanIpTask ().execute ();
     }
});
И основной.

private class ScanIpTask extends AsyncTask<Void, String, Void>{
   static final String subnet = "192.168.4.";
   static final int lower = 1;
   static final int upper = 10;
   static final int timeout = 5000;
   @Override
   protected void onPreExecute() {
        ipList.clear();
        adapter.notifyDataSetInvalidated();
       Toast.makeText(MainActivity.this, "Scan IP...", Toast.LENGTH_LONG).show();    }
    @Override
   protected Void doInBackground(Void... params) {
        for (int i = lower; i <= upper; i++) {
       String host = subnet + i;
            try {
                InetAddress inetAddress = InetAddress.getByName(host);
                if (inetAddress.isReachable(timeout)){
                 publishProgress(inetAddress.toString());
               }
            } catch (UnknownHostException e) {
                e.printStackTrace();
           } catch (IOException e) {
               e.printStackTrace();
          }
     }
        return null;
    }    @Override
  protected void onProgressUpdate(String... values) {
        ipList.add(values[0]);
        adapter.notifyDataSetInvalidated();
        Toast.makeText(MainActivity.this, values[0], Toast.LENGTH_LONG).show();
   }
    @Override    protected void onPostExecute(Void aVoid) {
    Toast.makeText(MainActivity.this, "Done", Toast.LENGTH_LONG).show();
   }
}

Примеры поиска сетей, клиент-сервер, сервер–клиент можно просмотреть по следующей ссылке http://android-er.blogspot.com/2014/02/android-sercerclient-example-client.html. Добавляем в приложение еще один список, для просмотра адреса. Ниже скриншот.
IP-adress(1)Данный код больше для наладки, так как изначально мы узнаем адрес непосредственно из настройки модуля
AT+CIFSR
AT+CIFSR
+CIFSR:APIP,"192.168.4.1"
+CIFSR:APMAC,"1a:fe:34:d1:b9:c9"

OK

 

 

 

Работая с сокетами можно получить следующую ошибку:
java.net.SocketException: socket failed: ENONET (Machine is not on the network)
Это значит что мы слишком рано пытаемся подключиться к нему. У меня так и было, когда я пробовал в OnCreate открыть сокет. Реже бывает из-за неправильной настройки DHCP в сети, созданной ESP8266.
Создаем TextView, привязываемся и выводим данные на него. Ниже код.
<TextView
  android:id="@+id/response"
  android:layout_width="match_parent"
  android:layout_height="70dp"
  android:text="TextView" />

private class MyClientTask extends AsyncTask<Void, String, Void> {
     String dstAddress;
     int dstPort;
     String response = "";
    @Override
    protected Void doInBackground (Void... arg0) {
        Socket socket = null;     //Создаем объект сокета
        try {
            socket = new Socket (SERVERIP, SERVERPORT); //Создаем сокетное соединение
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (1024);
            byte[] buffer = new byte[1024];
            int bytesRead;
            InputStream inputStream = socket.getInputStream (); // Получаем входные потоки
            while ((bytesRead = inputStream.read (buffer)) != -1) {
                   byteArrayOutputStream.write (buffer, 0, bytesRead);
                   response = byteArrayOutputStream.toString («UTF-8»);
                   publishProgress (response); //Выводим промежуточные результаты.
                   if (isCancelled () || (FlagCancelled == true)) { /*Остановка по нажатию. Используем дополнительно флаг, ниже пояснение.*/
                          FlagCancelled = false;
                          break;
                   }
            }
       } catch (UnknownHostException e) {
           e.printStackTrace ();
           response = "UnknownHostException: " + e.toString ();
       } catch (IOException e) {
           e.printStackTrace ();
           response = «IOException: „ + e.toString ();
       }finally{
           if (socket != null){
               try {
                   socket.close (); //Закрытие сокета
               } catch (IOException e) {
                   e.printStackTrace ();
               }
           }
       }
        return null;
    }
    protected void onProgressUpdate (String... values) {
        super.onProgressUpdate (values);
        textResponse.setText (values[0]);   //Выводим данные на дисплей
    }
    @Override
    protected void onPostExecute (Void result) {
        textResponse.setText (response);
        super.onPostExecute (result);
    }
}

Обработка нажатия:
public void onClick ( View v) {
     MyClientTask myClientTask =  new MyClientTask (); }

Ниже я добавил описание некоторых ошибок, которые могут возникнуть при наладке:
connect failed: ETIMEDOUT (Connection timed out)
Здесь желательно проверить адрес модуля, можно просмотреть информацию и пропинговать с командной строки (ipconfig and ping). Убедитесь в правильности адреса и порта.

ipconfig(3)

ping(4)

ECONNREFUSED (CONNECTION refused)  - сообщение означает: «на этом ip-адресе нет сервера».
Здесь также необходимо проверить правильность порта и адреса. Может просто упасть сервер, попробуйте передерните модуль, хотя по хорошому надо прописать в контролере watchdog. Но это позже.
Также IOException: java.net.SocketException: socked failed: EACCES (Permission denied), здесь требуется прописать разрешение:
<uses-permission android:name=“android.permission.INTERNET»/>
По нажатию кнопки Read, запускаем private class MyClientTask extends AsyncTask<Void, String, Void> .  получаем результат (рис. ниже).
read_socet(5)Немного разберем AsyncTask – это класс для выполнения тяжелых задач передачи в UI-поток (user interface thread) результатов работы. Созданный разработчиками Android. Т.е. подразумевается работа с фоновыми операциями. Напрямую с данным классом работать нельзя поэтому мы создали класс-наследник MyClientTask. Более подробно можно почитать библиотеки. Имеет 4-ри метода, один из них обязательный protected Void doInBackground (Void... arg0) – который должен выполняться в новом потоке, и где должны решаться основные задачи, и не имеет доступа к UI. А также использовали protected void onPostExecute (Void result), который выполняется после основного метода, и имеет доступ к UI, что в свою очередь не дает нам мониторинг выводимого параметра, т.к. последний метод выполняется по окончанию doInBackground. Для этого в основном методе мы используем publishProgress для передачи промежуточных данных методу onProgressUpdate.

В  ходе проверки оказалось, например, если у меня контроллер в цикле передает данные, то выйти из метода doInBackground, который также в свою очередь находится в цикле оказалось не так просто. Даже использование myClientTask.cancel (true), который должен был бы произвести проверку if (isCancelled ()) {
return null либо break (); - не работает.

Т.е когда мы нажимаем на кнопку "Отменить операцию", то в методе cancel () используем параметр, равный true. В doInBackground () при работе цикла идёт проверка отмены ( isCancelled ()).
Если приложение видит, что пользователь выбрал отмену задачи, то вместо onPostExecute ()
вызывается onCancelled (), в котором и прописываем свою логику кода. В данном случае она просто бездействует, вообще неактивна. Для чего был введен флаг:
case R.id.close:
    myClientTask.cancel (true);   // Т.к. метод не срабатывает, то вводим флаг
    onStop ();

protected void onStop () {      // Для остановки потока
    FlagCancelled = true;
    super.onStop ();
}

Правда иногда требуется пару кликов по кнопке StopData. Ниже скрин приложения.
close_socet(6)Ну и напоследок вспомним настройки модуля. Режим CWMODE=2 (точка доступа), просмотр адреса сервера был выше, и стандартный набор:
AT+CIPMODE=0
AT+CIPMUX=1
AT+CIPSERVER=1,8080
AT+CIPSTO=5
AT+CIPSEND=0,5

В следующей статье мы с Вами «причешим» немного приложение. Все специфические кнопки перенесем в отдельное меню. Создадим поле ввода. И передадим информацию микроконтроллеру stm через модуль. На этом и остановимся. Всем пока.

Просмотрено 670 раз.

Я на Google+

Добавить комментарий

Ваш e-mail не будет опубликован.

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Subscribe without commenting