GrabDuck

Работа с последовательными портами ПК при помощи библиотеки jSSC

:

В современном мире Com-порт уже не первый год считается пережитком прошлого, его уже не встретишь на современных материнских платах, ноутбуках. Ну а если не дай Бог он туда затесался, то обыватель посмотрит на такой продукт с недоумением или даже презрением, мол, какой недальновидный производитель попался, лучше бы пару USB запихал – их же всегда не хватает. Но, если посмотреть на ситуацию с другой стороны, то Com не умер, а даже наоборот, процветает, просто мы этого не замечаем. Он есть везде, в наших новомодных коммуникаторах(для связи софта с GPS приёмником), в автоматах через которые мы платим за телефон, свет и т.д. (купюроприёмник, фискальный принтер), да чего там, наверно 90-95% торгового оборудования висит на Com-е и пророчит ему долгую жизнь. Сегодня уже абсолютно не важно, что Вы втыкаете себе в компьютер, большинство нового измерительного оборудования имеют на борту USB, но на проверку это оказывается простой адаптер USB-Com. И оно понятно, интерфейс RS-232 является простым и удобным в работе, а для работы с приборами и большинством аппаратуры его вполне достаточно.

Теперь, что называется по существу. Многие могут сказать, что это за jSSC такой и зачем он вообще нужен, ведь есть javax.Comm и RXTX. Да, есть, но не всё так просто. Если вернутся к истокам появления библиотеки jSSC, то необходимо рассказать о том, как и зачем она была написана. Всё началось в конце 2008, начале 2009-го годов, тогда в организации где я работаю мне было поручено заняться автоматизацией ряда технологических процессов, и в общем-то одним из важнейших аспектов был сбор телеметрии с приборов и датчиков в режиме 24/7. Началась разработка, и в начале, всё было хорошо, даже слишком хорошо, чтобы быть правдой. Связь с приборами без особого труда была налажена при помощи RXTX-2.1.7-r2 и вроде как всё работало, но при попытке всё собрать вместе начались проблемы. Система должна была быть многопоточной, предоставлять аналог Plug’n’Play но только для Com-а, реальных устройств должно было быть 16 штук и всё должно было работать как часы. Но RXTX со мной был явно не согласен и постоянно так и норовил уронить Java машину. Из ошибок, конечно же, Exception Access Violation. Огромным количеством костылей в java коде и правкой Си-шной части RXTX удалось вроде как решить большинство проблем, но система получилась очень хрупкой, и многопоточной её можно было назвать с большой натяжкой. В этот момент выходит RXTX 2.2pre1 а затем и RXTX 2.2pre2, я тут же накинулся на него и стал смотреть в надежде что за 3 года  (RXTX-2.1.7-r2 был написан в 2006г.) многое могло измениться, и вот сейчас уж заживём на полную. Но к моему глубочайшему сожалению большинство проблем осталось на месте. И в том момент в появлении jSSC я уже не сомневался, нужно было сделать работу, а для этого нужен был простой и надёжный инструмент для работы с Com-портом. Если вдруг кого задели мои слова по поводу библиотек RXTX, прошу прощения. Я не считаю данную библиотеку плохой, и с уважением отношусь к людям, которые её написали. У неё есть своя ниша, которую она прекрасно занимает и полностью удовлетворяет, но к такой работе библиотека оказалась не готова.

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

  • Многопоточность
  • Простота
  • Скорость работы
  • Поддержка максимально возможного количества ОС семейства Windows (это очень важный момент, т.к. по сей день очень часто используют, например, Win98)

Со всеми этими вопросами удалось разобраться. На сегодняшний момент библиотека поддерживает ОС от Win98 – Win7, стабильно работает в многопоточных системах.

Теперь рассмотрим работу с ней на примерах:

Пример № 1. Получение списка портов системы.
Список портов системы возвращается в виде массива строк методом getPortNames() класса SerialPortList. Вот пример кода:

import jssc.SerialPortList;

public class Main {
public static void main(String[] args) {
String[] portNames = SerialPortList.getPortNames();
for(int i = 0; i < portNames.length; i++){
System.out.println(portNames[i]);
}
}
}

На моей машине данный код выведет в консоль следующее:
CNCA0
CNCA1
CNCB0
CNCB1
COM1
COM7
COM8
COM9
COM10
Библиотека не опрашивает все порты подряд от COM1 до COMN, т.к. это лишнее время да и, в общем, результат отнюдь не всегда будет корректен (Как видно у меня в списке есть порты типа  CNC, это всё потому, что у меня установлена программа com0com и порты CNC как раз от неё). Вместо этого данные берутся из реестра.

Пример №2. Запись данных в порт.

import jssc.SerialPort;
import jssc.SerialPortException;

public class Main {

    public static void main(String[] args) {
SerialPort serialPort = new SerialPort("COM1");
try {
serialPort.openPort();
serialPort.setParams(SerialPort.BAUDRATE_57600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
serialPort.writeBytes("Test string".getBytes());
serialPort.closePort();
}
catch (SerialPortException ex) {
System.out.println(ex);
}
}
}

Вызов метода setParams() также будет корректен со следующими парамертами:
serialPort.setParams(57600, 8, 1, 0);

Пример №3. Чтение данных из порта.

Чтение данных из порта можно организовать двумя способами. Простым вызовом метода readBytes() или при помощи EventListener-a (второй вариант предпочтительнее).

Вариант №1:

import jssc.SerialPort;
import jssc.SerialPortException;

public class Main {

    public static void main(String[] args) {
SerialPort serialPort = new SerialPort("COM1");
try {
serialPort.openPort();
serialPort.setParams(SerialPort.BAUDRATE_57600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
byte[] buffer = serialPort.readBytes(8);
serialPort.closePort();
}
catch (SerialPortException ex) {
System.out.println(ex);
}
}
}

Вариант №2:

import jssc.SerialPort;
import jssc.SerialPortEvent;
import jssc.SerialPortEventListener;
import jssc.SerialPortException;

public class Main {

    private static SerialPort serialPort;

    public static void main(String[] args) {
serialPort = new SerialPort("COM1");
try {
serialPort.openPort();
serialPort.setParams(SerialPort.BAUDRATE_57600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
serialPort.setEventsMask(SerialPort.MASK_RXCHAR);
serialPort.addEventListener(new EventListener());
}
catch (SerialPortException ex) {
System.out.println(ex);
}
}

    private static class EventListener implements SerialPortEventListener {

        public void serialEvent(SerialPortEvent event) {
if(event.isRXCHAR() && event.getEventValue() == 8){
try {
byte[] buffer = serialPort.readBytes(8);
serialPort.closePort();
}
catch (SerialPortException ex) {
System.out.println(ex);
}
}
}
}
}

Теперь немного поясню, что здесь понаписано. Строка serialPort.setEventsMask(SerialPort.MASK_RXCHAR); устанавливает маску ивентов для нашего порта, фактически это список событий, на которые мы хотим реагировать. В данном случае MASK_RXCHAR будет нас извещать о приходе данных во входной буфер порта. Но кого собственно он будет об этом извещать ? А вот для этого нам и нужен класс EventListener, экземпляр которого мы передаём нашему порту - serialPort.addEventListener(new EventListener()); В этом классе нам необходимо описать метод public void serialEvent(SerialPortEvent event) именно в нём будут обрабатываться события. Так например, в нашем случае мы хотим прочитать данные из порта и ожидаем их прихода. Как только происходит какое-то событие, мы проверяем, что именно произошло, в данном случае проверяем, является ли пришедший ивент ивентом типа RXCHAR (event.isRXCHAR()) и смотрим сколько байт во входном буфере у нас есть (event.getEventValue() == 8), ведь нам нужно прочитать 8 байт. Если пришедший ивент удовлетворяет нашим требованиям, то читаем данные и закрываем порт, ну а если нет EventListener будет ожидать следующих ивентов.

Пример №4. Создание небольшой программы для взаимодействия с устройством.

Итак, предположим, что у нас есть некий прибор, который нам по запросу отдаёт данные ну, например 3-х типов по 2 байта. Запросы будут, например такие 00 00 01, 00 00 02, 00 00 03. Приступим:

import jssc.SerialPort;
import jssc.SerialPortEvent;
import jssc.SerialPortEventListener;
import jssc.SerialPortException;

public class Main {

    private static SerialPort serialPort;

    private static byte[][] requests = {{00, 00, 01}, {00, 00, 02}, {00, 00, 03}};
private static int requestNum = 0;

    public static void main(String[] args) {
serialPort = new SerialPort("COM1");
try {
serialPort.openPort();
serialPort.setParams(SerialPort.BAUDRATE_57600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
serialPort.setEventsMask(SerialPort.MASK_RXCHAR);
serialPort.addEventListener(new EventListener());
serialPort.writeBytes(requests[requestNum]);
}
catch (SerialPortException ex) {
System.out.println(ex);
}
}

    private static class EventListener implements SerialPortEventListener {

        public void serialEvent(SerialPortEvent event) {
if(event.isRXCHAR() && event.getEventValue() == 2){
try {
if(requestNum == 0){
byte[] buffer = serialPort.readBytes(2);
/*Пост обработка и вывод на форму*/
serialPort.writeBytes(requests[++requestNum]);
}
else if(requestNum == 1){
byte[] buffer = serialPort.readBytes(2);
/*Пост обработка и вывод на форму*/
serialPort.writeBytes(requests[++requestNum]);
}
else if(requestNum == 2){
byte[] buffer = serialPort.readBytes(2);
/*Пост обработка и вывод на форму*/
serialPort.writeBytes(requests[0]);
}
}
catch (SerialPortException ex) {
System.out.println(ex);
}
}
}
}
}

Пример практически идентичен 3-ему, так что, думаю, пояснений не требует. Хочется дать один совет. Не используйте сложных расчётов и ресурсоёмких операций внутри класса SerialPortEventListener(например, построение графика в реальном времени). Иначе Вы рискуете потерять данные, если произойдёт переполнение буфера. Такого рода задачи лучше выносить в отдельный поток.

С примерами пока всё, ничего сложного здесь нет, дерзайте.

Проект развивается, и обновления будут по мере необходимости. Так же рассматривается возможность портирования на Linux. Выражаю благодарность своему другу и коллеге McKnife-у с которым мы расковыривали дебри Com-а и «простыни» MSDN-а =)

Буду рад выслушать Ваши предложения и спасибо за внимание. Высылайте их на e-mail:  scream3r.org@gmail.com

Ссылка для скачивания http://scream3r.org/jssc.html