diff --git a/README.md b/README.md index 9a7cadf..7ff11e4 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,9 @@ runs Firmata protocol from your java program. - Interaction with a board and its pins in object-oriented style - Communication over serial port, network or custom transport layer - Abstraction over details of the protocol -- Provides an UI component that visualize the current state of every pin and -allows changing their mode and state -- Allows communicating with I2C devices +- Provides a UI component that visualize the current state of pins and allows +changing their mode and state +- Allows communicating with I2C devices connected to the board ## Installation @@ -22,10 +22,36 @@ Add the following dependency to `pom.xml` of your project: com.github.kurbatov firmata4j - 2.3.8 + 2.3.9 ``` +If you want to connect to the device via serial port (which is the most probable +case), please include one of the following libraries as a dependency into +the `pom.xml`: + +```xml + + + com.fazecast + jSerialComm + 2.6.2 + + + + com.fazecast + jssc + 2.9.4 + +``` + +**jssc** is an older library that worked just fine until recently. Now it reveals +[issues on **GraalVM** and latest updates of **Windows 10**](https://github.com/kurbatov/firmata4j/issues/42). +**firmata4j** was using **jssc** by default in versions prior to **2.3.9**. + +**jSerialComm** has proven itself to be working on **GraalVM** and latest +updates of **Windows 10**. + ## Usage General scenario of usage is following: ```java diff --git a/README_ru.md b/README_ru.md index b9cf9ad..d88d40c 100644 --- a/README_ru.md +++ b/README_ru.md @@ -27,6 +27,32 @@ Java. ``` +Если вы хотите подключаться к устройству по последовательному порту (что весьма +вероятно), добавьте одну из следующих библиотек в файл `pom.xml`: + +```xml + + + com.fazecast + jSerialComm + 2.6.2 + + + + com.fazecast + jssc + 2.9.4 + +``` + +**jscc** - более старая библиотека, которая работала без нареканий до последнего +времени. Сейчас обнаружились [проблемы в работе на **GraalVM** и с последними +обновлениями **Windows 10**](https://github.com/kurbatov/firmata4j/issues/42). +**firmata4j** использовала **jssc** по-умолчанию во всех версиях до **2.3.9**. + +**jSerialComm** показала себя как рабочий вариант на **GraalVM** и с последними +обновлениями **Windows 10**. + ## Использование Основной сценарий использования: ```java diff --git a/pom.xml b/pom.xml index 8dec89a..93f3b6a 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,7 @@ io.github.java-native jssc ${jssc.version} + true com.fazecast diff --git a/src/main/java/org/firmata4j/firmata/FirmataMessageFactory.java b/src/main/java/org/firmata4j/firmata/FirmataMessageFactory.java index f91cdcb..6effc35 100644 --- a/src/main/java/org/firmata4j/firmata/FirmataMessageFactory.java +++ b/src/main/java/org/firmata4j/firmata/FirmataMessageFactory.java @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) + * Copyright (c) 2014-2021 Oleg Kurbatov (o.v.kurbatov@gmail.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -212,9 +212,9 @@ public static byte[] setMode(byte pinId, Pin.Mode mode) { } /** - * Creates Firmata message to set digital values of port's pins.
+ * Creates Firmata message to set digital values of port's pins.
* Digital value should be assigned to a set of pins at once. A set of pins - * is called port.
+ * is called port.
* A port contains 8 pins. Digital value of every pin in a set transfers in * one byte. Every bit in the byte represents state of pin's output. * @@ -222,12 +222,23 @@ public static byte[] setMode(byte pinId, Pin.Mode mode) { * @param value values of port's pins * @return Firmata message to set digital output */ - public static byte[] setDigitalPinValue(byte portId, byte value) { + public static byte[] setDigitalPortValue(byte portId, byte value) { return new byte[]{(byte) (DIGITAL_MESSAGE | (portId & 0x0F)), (byte) (value & 0x7F), (byte) ((value >>> 7) & 0x7F)}; } + + /** + * Creates Firmata message to set digital values of a pin. + * + * @param pinId index of a pin + * @param value value of the pin (0 or 1) + * @return Firmata message to set the value of a digital output pin + */ + public static byte[] setDigitalPinValue(byte pinId, byte value) { + return new byte[]{SET_DIGITAL_PIN_VALUE, pinId, value}; + } /** - * Creates Firmata message to set value of an output pin in PWM mode.
+ * Creates Firmata message to set value of an output pin in PWM mode.
* If pin id is beyond 15th or value is greater than we can put into * standard analog message, extended analog message is built. * @@ -255,7 +266,7 @@ public static byte[] setAnalogPinValue(byte pinId, long value) { } /** - * Creates a message to set sampling interval.
+ * Creates a message to set sampling interval.
* The sampling interval sets how often analog data and i2c data is reported * to the client. The default value is 19 milliseconds. * @@ -272,7 +283,7 @@ public static byte[] setSamplingInterval(int value) { } /** - * Builds servo configuration message.
+ * Builds servo configuration message.
* The core idea is to just add a "config" message, then use * {@link #setMode(byte, org.firmata4j.Pin.Mode)} to attach/detach Servo * support to a pin. Then the normal {@link #setAnalogPinValue(byte, long)} diff --git a/src/main/java/org/firmata4j/firmata/FirmataPin.java b/src/main/java/org/firmata4j/firmata/FirmataPin.java index 4bf2f9b..2bfeac0 100644 --- a/src/main/java/org/firmata4j/firmata/FirmataPin.java +++ b/src/main/java/org/firmata4j/firmata/FirmataPin.java @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) + * Copyright (c) 2014-2021 Oleg Kurbatov (o.v.kurbatov@gmail.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -132,28 +132,11 @@ public synchronized void setValue(long value) throws IOException, IllegalStateEx byte[] message; long newValue; if (currentMode == Mode.OUTPUT) { - //have to calculate the value of whole port (8-pin set) the pin sits in - byte portId = (byte) (pinId / 8); - byte pinInPort = (byte) (pinId % 8); - byte portValue = 0; - for (int i = 0; i < 8; i++) { - Pin p = device.getPin(portId * 8 + i); - if (p.getMode() == Mode.OUTPUT && p.getValue() > 0) { - portValue |= 1 << i; - } - } - byte bitmask = (byte) (1 << pinInPort); - boolean val = value > 0; - if (val) { - portValue |= bitmask; - } else { - portValue &= ((byte) ~bitmask); - } - message = FirmataMessageFactory.setDigitalPinValue(portId, portValue); - newValue = val ? 1 : 0; + newValue = value > 0 ? 1 : 0; + message = FirmataMessageFactory.setDigitalPortValue(pinId, (byte) newValue); } else if (currentMode == Mode.ANALOG || currentMode == Mode.PWM || currentMode == Mode.SERVO) { - message = FirmataMessageFactory.setAnalogPinValue(pinId, value); newValue = value; + message = FirmataMessageFactory.setAnalogPinValue(pinId, newValue); } else { throw new IllegalStateException(String.format("Port %d is in %s mode and its value cannot be set.", pinId, currentMode)); } diff --git a/src/main/java/org/firmata4j/transport/JSSCTransport.java b/src/main/java/org/firmata4j/transport/JSSCTransport.java new file mode 100644 index 0000000..7be9d2e --- /dev/null +++ b/src/main/java/org/firmata4j/transport/JSSCTransport.java @@ -0,0 +1,106 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2021 Oleg Kurbatov (o.v.kurbatov@gmail.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.firmata4j.transport; + +import java.io.IOException; +import jssc.SerialPort; +import jssc.SerialPortEvent; +import jssc.SerialPortEventListener; +import jssc.SerialPortException; +import org.firmata4j.Parser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Transfers data via serial port using jssc library. + * + * @author Ali Kia + */ +public class JSSCTransport implements TransportInterface, SerialPortEventListener { + private Parser parser; + + private final SerialPort port; + + private static final Logger LOGGER = LoggerFactory.getLogger(JSSCTransport.class); + + public JSSCTransport(String portName) { + this.port = new SerialPort(portName); + } + + @Override + public void start() throws IOException { + if (!port.isOpened()) { + try { + port.openPort(); + port.setParams( + SerialPort.BAUDRATE_57600, + SerialPort.DATABITS_8, + SerialPort.STOPBITS_1, + SerialPort.PARITY_NONE); + port.setEventsMask(SerialPort.MASK_RXCHAR); + port.addEventListener(this); + } catch (SerialPortException ex) { + throw new IOException("Cannot start firmata device", ex); + } + } + } + + @Override + public void stop() throws IOException { + try { + if (port.isOpened()) { + port.purgePort(SerialPort.PURGE_RXCLEAR | SerialPort.PURGE_TXCLEAR); + port.closePort(); + } + } catch (SerialPortException ex) { + throw new IOException("Cannot properly stop firmata device", ex); + } + } + + @Override + public void write(byte[] bytes) throws IOException { + try { + port.writeBytes(bytes); + } catch (SerialPortException ex) { + throw new IOException("Cannot send message to device", ex); + } + } + + @Override + public void setParser(Parser parser) { + this.parser = parser; + } + + @Override + public void serialEvent(SerialPortEvent event) { + // queueing data from input buffer to processing by FSM logic + if (event.isRXCHAR() && event.getEventValue() > 0) { + try { + parser.parse(port.readBytes()); + } catch (SerialPortException ex) { + LOGGER.error("Cannot read from device", ex); + } + } + } +} diff --git a/src/main/java/org/firmata4j/transport/JSerialCommTransport.java b/src/main/java/org/firmata4j/transport/JSerialCommTransport.java index b32b288..a991009 100644 --- a/src/main/java/org/firmata4j/transport/JSerialCommTransport.java +++ b/src/main/java/org/firmata4j/transport/JSerialCommTransport.java @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2014-2019 Oleg Kurbatov (o.v.kurbatov@gmail.com) + * Copyright (c) 2014-2021 Oleg Kurbatov (o.v.kurbatov@gmail.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -33,71 +33,57 @@ /** * Implementation of {@link TransportInterface} based on {@link SerialPort}. - * + * * @author Norbert Sándor */ -public class JSerialCommTransport implements TransportInterface -{ +public class JSerialCommTransport implements TransportInterface { + private final SerialPort serialPort; private Parser parser; - public JSerialCommTransport(String portDescriptor) - { + public JSerialCommTransport(String portDescriptor) { serialPort = SerialPort.getCommPort(portDescriptor); } @Override - public void start() throws IOException - { - if (!serialPort.isOpen()) - { - if (serialPort.openPort()) - { - serialPort.setComPortParameters(jssc.SerialPort.BAUDRATE_57600, jssc.SerialPort.DATABITS_8, - SerialPort.ONE_STOP_BIT, SerialPort.NO_PARITY); - serialPort.addDataListener(new SerialPortDataListener() - { + public void start() throws IOException { + if (!serialPort.isOpen()) { + if (serialPort.openPort()) { + serialPort.setComPortParameters(57600, 8, SerialPort.ONE_STOP_BIT, SerialPort.NO_PARITY); + serialPort.addDataListener(new SerialPortDataListener() { @Override - public void serialEvent(SerialPortEvent event) - { - if (event.getEventType() == SerialPort.LISTENING_EVENT_DATA_RECEIVED) - { + public void serialEvent(SerialPortEvent event) { + if (event.getEventType() == SerialPort.LISTENING_EVENT_DATA_RECEIVED) { parser.parse(event.getReceivedData()); } } @Override - public int getListeningEvents() - { + public int getListeningEvents() { return SerialPort.LISTENING_EVENT_DATA_RECEIVED; } }); - } else - { + } else { throw new IOException("Cannot start firmata device: port=" + serialPort); } } } @Override - public void stop() throws IOException - { - if (serialPort.isOpen() && !serialPort.closePort()) - { + public void stop() throws IOException { + if (serialPort.isOpen() && !serialPort.closePort()) { throw new IOException("Cannot properly stop firmata device: port=" + serialPort); } } @Override - public void write(byte[] bytes) throws IOException - { + public void write(byte[] bytes) throws IOException { serialPort.writeBytes(bytes, bytes.length); } @Override - public void setParser(Parser parser) - { + public void setParser(Parser parser) { this.parser = parser; } } diff --git a/src/main/java/org/firmata4j/transport/SerialTransport.java b/src/main/java/org/firmata4j/transport/SerialTransport.java index c764b19..86133d3 100644 --- a/src/main/java/org/firmata4j/transport/SerialTransport.java +++ b/src/main/java/org/firmata4j/transport/SerialTransport.java @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2014-2018 Oleg Kurbatov (o.v.kurbatov@gmail.com) + * Copyright (c) 2014-2021 Oleg Kurbatov (o.v.kurbatov@gmail.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,86 +23,75 @@ */ package org.firmata4j.transport; -import jssc.SerialPort; -import jssc.SerialPortEvent; -import jssc.SerialPortEventListener; -import jssc.SerialPortException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import org.firmata4j.Parser; /** - * Allows connections over the serial interface. + * Transfers data over the serial interface. + * + * Using an instance of this transport requires one of the following libraries + * present in the classpath: + * + *
+ *  <dependency>
+        <groupId>com.fazecast</groupId>
+        <artifactId>jSerialComm</artifactId>
+        <version>2.6.2</version>
+    </dependency>
+ *  <dependency>
+        <groupId>org.scream3r</groupId>
+        <artifactId>jssc</artifactId>
+        <version>2.8.0</version>
+    </dependency>
+ * 
* - * @author Ali Kia + * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> */ -public class SerialTransport implements TransportInterface, SerialPortEventListener { +public class SerialTransport implements TransportInterface { - private Parser parser; - - private final SerialPort port; + private TransportInterface delegate; private static final Logger LOGGER = LoggerFactory.getLogger(SerialTransport.class); public SerialTransport(String portName) { - this.port = new SerialPort(portName); + try { + Class.forName("com.fazecast.jSerialComm.SerialPort", false, this.getClass().getClassLoader()); + delegate = new JSerialCommTransport(portName); + LOGGER.debug("Using jSerialComm transport"); + } catch (ClassNotFoundException e) { + try { + Class.forName("jssc.SerialPort", false, this.getClass().getClassLoader()); + delegate = new JSerialCommTransport(portName); + LOGGER.debug("Using jssc transport"); + } catch (ClassNotFoundException ex) { + throw new IllegalStateException( + "Serial communication library is not found in the classpath. " + + "Please make sure that there is at least one dependency " + + "as described in the javadoc of org.firmata4j.transport.SerialTransport"); + } + } } @Override public void start() throws IOException { - if (!port.isOpened()) { - try { - port.openPort(); - port.setParams( - SerialPort.BAUDRATE_57600, - SerialPort.DATABITS_8, - SerialPort.STOPBITS_1, - SerialPort.PARITY_NONE); - port.setEventsMask(SerialPort.MASK_RXCHAR); - port.addEventListener(this); - } catch (SerialPortException ex) { - throw new IOException("Cannot start firmata device", ex); - } - } + delegate.start(); } @Override public void stop() throws IOException { - try { - if (port.isOpened()) { - port.purgePort(SerialPort.PURGE_RXCLEAR | SerialPort.PURGE_TXCLEAR); - port.closePort(); - } - } catch (SerialPortException ex) { - throw new IOException("Cannot properly stop firmata device", ex); - } + delegate.stop(); } @Override public void write(byte[] bytes) throws IOException { - try { - port.writeBytes(bytes); - } catch (SerialPortException ex) { - throw new IOException("Cannot send message to device", ex); - } + delegate.write(bytes); } @Override public void setParser(Parser parser) { - this.parser = parser; - } - - @Override - public void serialEvent(SerialPortEvent event) { - // queueing data from input buffer to processing by FSM logic - if (event.isRXCHAR() && event.getEventValue() > 0) { - try { - parser.parse(port.readBytes()); - } catch (SerialPortException ex) { - LOGGER.error("Cannot read from device", ex); - } - } + delegate.setParser(parser); } }