Зачем нужно соединять Java-программу на компьютере и Arduino?

от автора


Картинка rawpixel

Любой энтузиаст, строящий свои проекты на базе Arduino, рано или поздно сталкивается с необходимостью тем или иным способом обеспечить взаимосвязь этой платы со своим компьютером.

Такой симбиоз даёт возможность как управлять платой с компьютера, так и наоборот — компьютером с платы. Об этом мы и поговорим в этой статье.

Постановка задачи и общие сведения о библиотеке

В рамках этой задачи я не имею в виду стандартное общение самой платы со средой разработки, я имею в виду, что условно необходимо реализовать «прямое» общение платы и компьютера. Конечно, это подразумевает собой наличие на компьютере некой утилиты, которая и будет осуществлять подобное общение.

Подобная программа может быть написана на любом известном вам языке программирования, но так уж исторически сложилось, что я более-менее понимаю java, поэтому разговор далее будет идти только об этом языке и библиотеках под него.

Некоторое время назад для решения подобного вопроса — использовалась библиотека RxTx, однако в её работе отмечаются некоторые проблемы, ввиду необходимости установки DLL под Windows, а также проблемы со стабильностью (судя по ряду мнений активных пользователей этой библиотеки. Возможно, у вас был другой опыт).

Сегодня есть гораздо более простая и удобная в использовании библиотека, которая позволяет организовать общение через COM-порты: jSerialComm.

Она удобна тем, что обеспечивает доступ комфортным, независимым от платформы способом, без использования каких-либо дополнительных инструментов и библиотек.

Подробное описание API этой библиотеки находится по этому адресу:

Так как сама библиотека не содержит достаточно большого количества примеров использования (только самые основные), то наверняка вам пригодится и вот эта подборка, в которой собрано 20 достаточно полезных коротких примеров использования возможностей библиотеки.

Такой, например, как инициализация порта:

public Serial(SerialPort port) throws IOException {   port.setComPortTimeouts(SerialPort.TIMEOUT_READ_BLOCKING, TIME_OUT, TIME_OUT);   port.setComPortParameters(DATA_RATE, 8, SerialPort.ONE_STOP_BIT, SerialPort.NO_PARITY);   port.setFlowControl(SerialPort.FLOW_CONTROL_DISABLED);   if (port.openPort()) {     this.port = port;     output = port.getOutputStream();     input = port.getInputStream();   } else {     throw new IOException("Cannot open serial port.");   } } 

Открытие порта:

@Override public boolean openPort() throws Exception {   responseMessageHandler = new ResponseMessageHandler();   if (serialPort == null) {     throw new ConnectionException("The connection wasn't initialized");   }   return serialPort.openPort(); } 

Закрытие порта:

@Override public void closePort() throws Exception {   if (serialPort != null) {     serialPort.removeDataListener();     serialPort.closePort();   } } 

Но до начала всех манипуляций, нам необходимо вывести в консоль, и узнать, какие COM-порты у нас вообще доступны:

package main;  import com.fazecast.jSerialComm.SerialPort;  public class Starter {      public static void main(String[] args) {         System.out.println("Hello world");                  SerialPort[] ports = SerialPort.getCommPorts();                  for (SerialPort port: ports) {             System.out.println(port.getSystemPortName());         }     }  } 

Библиотека может работать в ряде режимов, которые отличаются степенью блокировки (т.е., ожидания поступления данных).

Например, режимом по умолчанию является неблокирующий: методы, читающие данные, возвратят любые данные, которые доступны. Без какого-либо ожидания:

SerialPort comPort = SerialPort.getCommPorts()[0]; comPort.openPort(); try {    while (true)    {       while (comPort.bytesAvailable() == 0)          Thread.sleep(20);        byte[] readBuffer = new byte[comPort.bytesAvailable()];       int numRead = comPort.readBytes(readBuffer, readBuffer.length);       System.out.println("Read " + numRead + " bytes.");    } } catch (Exception e) { e.printStackTrace(); } comPort.closePort();

В противовес ему бывают ситуации, когда вы заранее знаете количество данных, которые должны получить. В такой ситуации вам нужно, чтобы метод, запрашивающий данные, дождался их получения и не возвращался «хоть с каким-нибудь» значением раньше времени.

Как раз для работы в таком режиме и предназначен полностью блокирующий способ. Например, в коде ниже, метод readBytes() ждёт запрошенные 1024 байта в течение 1 секунды:

SerialPort comPort = SerialPort.getCommPorts()[0]; comPort.openPort(); comPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_BLOCKING, 1000, 0); try {    while (true)    {       byte[] readBuffer = new byte[1024];       int numRead = comPort.readBytes(readBuffer, readBuffer.length);       System.out.println("Read " + numRead + " bytes.");    } } catch (Exception e) { e.printStackTrace(); } comPort.closePort();

Если нужно, чтобы он бесконечно ждал поступления этих данных, то тогда внесите вот такое изменение в строке, где содержится setComPortTimeouts:

comPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_BLOCKING, 0, 0);

Более подробно обо всех доступных режимах вы можете прочитать здесь.

Также существует полностью асинхронный режим работы, в котором возможно прослушивание ряда событий и реагирование на них. В этом режиме тайм-ауты (всевозможные блокировки) игнорируются:

SerialPort comPort = SerialPort.getCommPorts()[0]; comPort.openPort(); comPort.addDataListener(new SerialPortDataListener() {    @Override    public int getListeningEvents() { return SerialPort.LISTENING_EVENT_DATA_AVAILABLE; }    @Override    public void serialEvent(SerialPortEvent event)    {       if (event.getEventType() != SerialPort.LISTENING_EVENT_DATA_AVAILABLE)          return;       byte[] newData = new byte[comPort.bytesAvailable()];       int numRead = comPort.readBytes(newData, newData.length);       System.out.println("Read " + numRead + " bytes.");    } });

Например, в коде выше, при наличии любых данных, доступных для чтения — будет запущен обратный вызов.

Также (при желании) вы можете использовать стандартные Java интерфейсы Inputstream, Outputstream, например, так:

SerialPort comPort = SerialPort.getCommPorts()[0]; comPort.openPort(); comPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 0, 0); InputStream in = comPort.getInputStream(); try {    for (int j = 0; j < 1000; ++j)       System.out.print((char)in.read());    in.close(); } catch (Exception e) { e.printStackTrace(); } comPort.closePort();

Подключаем Arduino к компьютеру

Случай, когда компьютер шлёт данные на Arduino

Теперь вернёмся к нашей задаче общения компьютера с Arduino. Понятно, что задача может быть двоякой: скажем так, «нисходящей» и «восходящей».

То есть, когда инициатором является компьютер или Arduino.

Хороший пример первого варианта можно посмотреть здесь. В нём содержится код как со стороны компьютера, так и со стороны Arduino. Рассмотрим компьютерную сторону:

Java-код для компьютера

package de.mschoeffler.arduino.serialcomm.example01;  import java.io.IOException; import com.fazecast.jSerialComm.SerialPort;  /**  * Simple application that is part of an tutorial.  * The tutorial shows how to establish a serial connection between  * a Java and Arduino program.  * @author Michael Schoeffler (www.mschoeffler.de)  *  */ public class Startup {    public static void main(String[] args) throws IOException, InterruptedException {         // device name TODO: must be changed     SerialPort sp = SerialPort.getCommPort("/dev/ttyACM1");         // default connection settings for Arduino     sp.setComPortParameters(9600, 8, 1, 0);          // block until bytes can be written     sp.setComPortTimeouts(SerialPort.TIMEOUT_WRITE_BLOCKING, 0, 0);          if (sp.openPort()) {       System.out.println("Port is open :)");     } else {       System.out.println("Failed to open port :(");       return;     }                  for (Integer i = 0; i < 5; ++i) {                   sp.getOutputStream().write(i.byteValue());       sp.getOutputStream().flush();       System.out.println("Sent number: " + i);       Thread.sleep(1000);     }                  if (sp.closePort()) {       System.out.println("Port is closed :)");     } else {       System.out.println("Failed to close port :(");       return;     }         }  }

Мы видим, что код является полностью блокирующим (т.к. мы заранее знаем объём передаваемых данных, когда в цикле с компьютера пересылаем одну цифру на Arduino). Кроме того, следующая строка даёт нам хороший пример того, как нужно конфигурировать порт для общения с Arduino:

sp.setComPortParameters(9600, 8, 1, 0);

Также можно легко заметить (так как инициатором является компьютер), что в этом случае мы используем исходящий с компьютера поток (OutputStream):

sp.getOutputStream().write(i.byteValue()); sp.getOutputStream().flush(); 

Перейдём к стороне Arduino. Здесь всё достаточно стандартно, и для чтения используется простая конструкция:

if (Serial.available() > 0) {         byte incomingByte = 0;     incomingByte = Serial.read(); } 

Случай, когда Arduino шлёт данные на компьютер

А теперь попробуем рассмотреть обратный вариант, когда необходимо общаться, так сказать, в «восходящем» режиме — то есть Arduino шлёт сообщения компьютеру.

Для этого нам надо внести изменения в Java код, а также изменить скетч прошивки Arduino.

Начнём с кода Java. Во-первых, так как в этом случае нужно принимать поток данных, нам необходимо изменить getOutputStream на getInputStream:

byte b= (byte) sp.getInputStream().read();

Также нам необходимо изменить режим записи — на режим чтения, переключив его на полублокирующий вариант. Как можно увидеть в строке ниже, полублокирующий режим у нас включён таким образом, что мы ждём, пока хотя бы 1 байт данных не будет успешно прочитан (это нули, следующие за выражением TIMEOUT_READ_SEMI_BLOCKING):

sp.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 0, 0);

И весь код будет выглядеть следующим образом:

import com.fazecast.jSerialComm.*; import java.io.*; import java.util.ArrayList;   /**  * Based on the tutorial example,  * showing how to establish a serial connection between  * a Java and Arduino program:  * @author Michael Schoeffler (www.mschoeffler.de)  *  */  public class Main {  public static void main(String[] args) throws IOException, InterruptedException     {      SerialPort sp = SerialPort.getCommPorts()[1];       sp.setComPortParameters(9600, 8, 1, 0); // настройки для Arduino       sp.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 0, 0);         if (sp.openPort()) {           System.out.println("Port is open :)");           } else {            System.out.println("Failed to open port :(");             return;             }              //в бесконечном цикле слушаем порт             while(true)             {              byte b= (byte) sp.getInputStream().read();              System.out.println("Received data: " + b);              }       } }

Как можно заметить из кода, мы сделали так, что поступающие данные из COM-порта читаются в бесконечном цикле.

Скетч Arduino, в свою очередь, достаточно прост и выглядит следующим образом:

byte b = 23;  void setup() { Serial.begin (9600); }  void loop() {  delay(1000);  Serial.write(b);  }

Как можно заметить, с периодичностью в одну секунду мы просто пишем байты в COM-порт.

Для чего это всё можно применить?

А теперь попробуем прикинуть, для чего, собственно, вся эта последовательность телодвижений может пригодиться?

Понятно, что способ общения Arduino с компьютером является весьма полезным для целой массы применений, среди которых могут быть и разнообразные погодные станции, и управление системами робототехники и т. д.

Лично мой кейс использования выглядит приблизительно так (для чего, собственно, я это всё и затеял. Уже жду детали из Китая):

Дело в том, что я уже достаточно давно (лет 10) для просмотра телевизора и фильмов, использую видеопроектор. Телевизионный сигнал идёт с подключаемой приставки, а фильмы я смотрю с компьютера. Чтобы мне не нужно было постоянно перетыкать HDMI-кабель в гнездо видеопроектора — я купил специальный разветвитель, который после нажатия кнопки переключает один источник сигнала на другой (это всё можно было и не писать, но так ситуация будет более понятна).

Периодически, когда я смотрю телевизор, я натыкаюсь на ряд фильмов, которые идут по телевизору в SD-качестве. Несмотря на то, что я эти фильмы уже видел 100500 раз — мне всё равно хочется пересматривать их ещё.

В этот момент я вспоминаю, что доступ к этим фильмам есть у меня на компьютере и в отличном качестве. После чего — я иду к компьютеру, включаю фильм, возвращаюсь обратно, переключаю источник сигнала и смотрю фильм уже в хорошем качестве.

То есть, если бы у меня был некий способ, не вставая с дивана включать фильм на компьютере — я вполне мог бы начать смотреть его с минимальными усилиями и без лишних походов.

Тут многие, наверное, скажут: «да купи ты себе беспроводную мышь и не парься!»

На это у меня есть что ответить: раньше так и было, однако однажды у меня родилась мысль, как можно упростить это ещё сильнее!

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

Суть этого способа в двух словах заключается в следующем:

  1. мы подключаем приёмник инфракрасного излучения к Arduino,
  2. загружаем в Arduino специальный скетч, после чего нажимаем на любую нужную кнопку пульта дистанционного управления (например, на ту кнопку, которая у нас обычно ни в чём не задействована). При нажатии на эту кнопку пульт излучает определённый код, который передаётся на Arduino, и отображается в мониторе порта. Записываем этот код (например, на бумажку),
  3. далее мы загружаем в Arduino другой скетч, который является уже исполнителем определённых действий, при получении этого кода на инфракрасный приёмник.

Таким образом, совместив между собой эти два способа (то есть приём и обработку кодов нажатий определённых кнопок на пульте), на подключённой к компьютеру Arduino — с Java программой компьютере, мы можем управлять с инфракрасного пульта процессами, протекающими на компьютере!

Например, в моём случае, я хочу запускать воспроизведение фильмов на компьютере.

Тут следует сделать одну оговорку: дело в том, что пульт управления от видеопроектора у меня неродной (родной вышел из строя) и поэтому я использую программируемый универсальный пульт, в котором у меня используется, дай бог, если 35% от имеющихся на нём кнопок.

Получается, я вполне могу назначить на оставшиеся кнопки весьма широкий набор функций: добавить громкость, убавить громкость, включить воспроизведение, поставить на паузу, перемотка, переключить на другой видеофайл и т.д. и т.п.

Весь проект со стороны компьютера я вижу следующим образом: в компьютер (всегда в один и тот же USB-разъём) будет воткнута самая маленькая Arduino, которая у меня имеется, — Arduino Nano, с подключённым к ней инфракрасным приёмником. Всё это будет выполнено достаточно компактно — в форм-факторе флешки, а корпус будет изготовлен 3D печатью.

Сама java-программа будет добавлена в автозагрузку компьютера.

Скетч для Arduino будет максимально простым. Приблизительно похожим на тот, что вы уже видели выше.

В завершение хочу сказать, что я таким образом собираюсь несколько увеличить комфорт своего просмотра фильмов и телевизора на досуге, а вы же — можете использовать этот способ для каких-то своих, более глобальных целей.

Если подумать, применений этому способу может быть очень много. С ходу мне в голову приходит изготовление некоего «презентера», который позволяет запускать файлы на компьютере и выводить их на большой экран во время выступлений на сцене перед большой аудиторией. Это всего лишь одна из идей. А так, думаю, этот способ может быть многим полезен.

Удачи всем в сборке!


НЛО прилетело и оставило здесь промокод для читателей нашего блога:

15% на все тарифы VDS (кроме тарифа Прогрев) — HABRFIRSTVDS.


ссылка на оригинал статьи https://habr.com/ru/company/first/blog/681382/


Комментарии

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *