И пускай 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)
holomen
06.05.2018 07:40SecureCRT. Вкладки и не только.
А в двух сотнях метров оно (простейшая терминалка) должно не только вкладкой подмигивать, но и кофе сварить и принести в постель до того как.
Andruwkoo
06.05.2018 14:13Если я правильно понимаю, то бесконечный цикл проверки очереди и вывода принятого сообщения будет постоянно отбирать 1 ядро процессора полностью. А если будет много вкладок, то каждый поток приема съест по ядру. Поправьте меня, если я не прав. А если прав, то не думали ли вы над решением проблемы? У меня похожая ситуация произошла некоторое время назад, но победить я это не смог кроме как вставкой задержек на 5-10 мс, но это спасло не сильно и кажется, что есть более элегантное решение
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
Sap_ru
06.05.2018 14:25Всё равно очень плохо.
Andruwkoo
06.05.2018 16:29+1Извиняюсь, за может глупый вопрос (я по врофессии не программист, а только интересуюсь кодингом в свободное время), но как должно быть хорошо?
Sap_ru
06.05.2018 17:40+1Много замечаний.
По событию isRXCHAR нужно читать в цикле, пока не данные не кончатся.
RxChar вызывается в отдельном служебном потоке. Причём, это даже не Java-поток. Лучше там делать только самые простые действия. Можно сразу бросить runlater и читать из основного потока. Можно получить двоичный буфер и бросить его через runlater. Никаких тяжёлых операций там лучше не делать.
Зачем там оверкил в виде AnimationTimer на каждый принятый буфер я не понимаю. Это основная претензия. Тут простая задача — передать данные в другой поток. И очень сложное тяжёлое решение.
И есть сомнения в потокобезопасности кода. Вот местах типа «comPort != null» и «dataModeIsAscii.getValue()» и т.п.
prs123
06.05.2018 23:44А какже количество принятых/отправленных байт? Отправка/передача в HEX? Ещё очень удобна галочка «вставлять \n» автоматически, не все умеют без него работать. Ну и здорово бы иметь возможность не стирать сообщение после отправки и/или сделать набор редактируемых «горячих» кнопок.
Думаю, если это сделать — получится ещё лучше
dec123
07.05.2018 00:39на Win10 в браузере Chrome не работает. В Internet Explorer требуются пляски с бубнов вокруг ActiveX, браузер ругается на отсутствие секюрности.
под MacOC не запускается
saw_tooth
07.05.2018 08:58Хотел написать что где-то плачет один qt, но увидел это
Qt имхо долгий в разработке/отладке
и захотел каких то подробностей.
hardegor
«Простой терминал»… 200 Мбайт :)
Почему-то при запуске через Terminal_Java.html он удаляет Terminal_Java.jnlp