В жизни embedded разработчика, часто возникает потребность взаимодействия с серийным портом
И пускай RS-232 почти полностью вытеснен современными интерфейсами, UART похоже никуда не собирается уходить

Большинство модулей (WI-FI, IoT и др.), демоплат/одноплатников работают или имеют на борту UART.



Терминальных программ огромное множество, самые заметные — Putty и termianl v1.9b
Они отлично справляются с задачами, но сложности начинаются, когда открытых соединений больше одного.

На переключение и поиск нужного окна уходит много времени.

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

Выбор был между node webkit, Qt и javaFx. Node webkit испугал возможной прожорливостью, Qt имхо долгий в разработке/отладке.





Первым делом была набросана блок схема



И уже к вечеру был относительно рабочий проект.

Наверно нет смысла выкладывать здесь весь код. Проект есть на гите.

Представлю наиболее интересную на мой взгляд часть:

public class ConnectionData {
    @FXML //-- поле с пришедшими данными
    public TextArea receiveData;
    @FXML //-- для отправки данных
    public TextField sendData;
    @FXML //-- (кнопка отправки, bind с sendDataProperty, активна если поле не пустое)
    public Button sendButton;
	// binding
    private StringProperty sendDataProperty = new SimpleStringProperty("");
    // очередь
	BlockingQueue<String> rxDataQueue = new LinkedBlockingQueue<>();

    @FXML
    public void initialize() {
        sendData.textProperty().bindBidirectional(sendDataProperty);
        sendButton.disableProperty().bind(sendDataProperty.isEmpty());

        Task<Void> task = new Task<Void>() {
            @Override
            public Void call() throws Exception { // таск для чтения данных, при новых данных создается new MessageConsumer
                Platform.runLater(() -> new MessageConsumer(rxDataQueue, receiveData, rxDataQueue.size()).start());
                return null;
            }
        };
        new Thread(task).start();
    }

    public class MessageConsumer extends AnimationTimer {
        private final BlockingQueue<String> messageQueue ;
        private final TextArea textArea ;
        private int messagesReceived = 0 ;
        public MessageConsumer(BlockingQueue<String> messageQueue, TextArea textArea, int numMessages) {
            this.messageQueue = messageQueue ;
            this.textArea = textArea ;
        }
        @Override
        public void handle(long now) {
            List<String> messages = new ArrayList<>();
            messagesReceived += messageQueue.drainTo(messages);
            messages.forEach(msg -> textArea.appendText(msg));
        }
    }

    public Button getPropertySendButton() {
        return sendButton;
    }

    public String getSendDataProperty() {
        String sendBuff = sendDataProperty.get();
        sendDataProperty.set("");
        return sendBuff;
    }

    public void clearReceiveData() { //-- по кнопке flush на форме
        receiveData.textProperty().setValue("");
    }

    public void setReceiveData(byte[] buffer) { // здесь помещаются принятые данные из порта
        try {
            rxDataQueue.add(new String(buffer, "UTF-8"));
        } catch (UnsupportedEncodingException ex) {
            System.err.print(ex);
        }
    }
}

Класс формы для вывода принятого — TextField (sendData).

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

Для этого, как не сложно догадаться, используется очередь — BlockingQueue rxDataQueue.

Данные принимаются с SerialPort (jssc) и помещаются в очередь через вызов setReceiveData.

Task task заберет принятый элемент и после отправки удалит его из очереди.

Что хочется получить дальше и что планируется из функционала:

— наверно требуется сохранение конфигов по портам
— ssh клиент (гибкий и интуитивно понятный!)
— добавление настраиваемого цвета к вкладкам
— довести интерфейс до приятного вида, css, JFoenix

Гит
Собранная сборка

Комментарии (14)


  1. hardegor
    06.05.2018 07:21

    «Простой терминал»… 200 Мбайт :)
    Почему-то при запуске через Terminal_Java.html он удаляет Terminal_Java.jnlp


  1. holomen
    06.05.2018 07:40

    SecureCRT. Вкладки и не только.
    А в двух сотнях метров оно (простейшая терминалка) должно не только вкладкой подмигивать, но и кофе сварить и принести в постель до того как.


  1. Andruwkoo
    06.05.2018 14:13

    Если я правильно понимаю, то бесконечный цикл проверки очереди и вывода принятого сообщения будет постоянно отбирать 1 ядро процессора полностью. А если будет много вкладок, то каждый поток приема съест по ядру. Поправьте меня, если я не прав. А если прав, то не думали ли вы над решением проблемы? У меня похожая ситуация произошла некоторое время назад, но победить я это не смог кроме как вставкой задержек на 5-10 мс, но это спасло не сильно и кажется, что есть более элегантное решение


    1. Khomin Автор
      06.05.2018 14:22

      Правильно, по этому сейчас немного подправил:

      Task<Void> task = new Task<Void>() {
      	@Override
      	public Void call() throws Exception {
      		Platform.runLater(() -> new MessageConsumer(
                           rxDataQueue, receiveData, rxDataQueue.size()).start());
      	return null;
      }
      

      Очередь построена на LinkedBlockingQueue (BlockingQueue)
      A Queue that additionally supports operations that wait for the queue to become non-empty when retrieving an element, and wait for space to become available in the queue when storing an element


      1. Sap_ru
        06.05.2018 14:25

        Всё равно очень плохо.


        1. Andruwkoo
          06.05.2018 16:29
          +1

          Извиняюсь, за может глупый вопрос (я по врофессии не программист, а только интересуюсь кодингом в свободное время), но как должно быть хорошо?


          1. Sap_ru
            06.05.2018 17:40
            +1

            Много замечаний.
            По событию isRXCHAR нужно читать в цикле, пока не данные не кончатся.
            RxChar вызывается в отдельном служебном потоке. Причём, это даже не Java-поток. Лучше там делать только самые простые действия. Можно сразу бросить runlater и читать из основного потока. Можно получить двоичный буфер и бросить его через runlater. Никаких тяжёлых операций там лучше не делать.
            Зачем там оверкил в виде AnimationTimer на каждый принятый буфер я не понимаю. Это основная претензия. Тут простая задача — передать данные в другой поток. И очень сложное тяжёлое решение.
            И есть сомнения в потокобезопасности кода. Вот местах типа «comPort != null» и «dataModeIsAscii.getValue()» и т.п.


  1. Sap_ru
    06.05.2018 14:30

    На неломаной Win10 с включённым UAC пробовали запустить? :)


  1. sedyh
    06.05.2018 14:33
    +1

    Не блок схема, а uml диаграмма.


  1. prs123
    06.05.2018 23:44

    А какже количество принятых/отправленных байт? Отправка/передача в HEX? Ещё очень удобна галочка «вставлять \n» автоматически, не все умеют без него работать. Ну и здорово бы иметь возможность не стирать сообщение после отправки и/или сделать набор редактируемых «горячих» кнопок.
    Думаю, если это сделать — получится ещё лучше


    1. Khomin Автор
      06.05.2018 23:47

      Спасибо!
      Да, этот функционал будет очень полезен


  1. dec123
    07.05.2018 00:39

    на Win10 в браузере Chrome не работает. В Internet Explorer требуются пляски с бубнов вокруг ActiveX, браузер ругается на отсутствие секюрности.
    под MacOC не запускается


  1. saw_tooth
    07.05.2018 08:58

    Хотел написать что где-то плачет один qt, но увидел это

    Qt имхо долгий в разработке/отладке

    и захотел каких то подробностей.


  1. serafims
    07.05.2018 12:42

    Пожалуй, это повод для запроса функционала автору программы YAT...