From 59ad9be7673ff073e2b0998a5e7116f5a80b7872 Mon Sep 17 00:00:00 2001 From: dogmaphobic Date: Sat, 18 Apr 2015 14:04:07 -0400 Subject: [PATCH] First android commit. --- QGCApplication.pro | 81 +- QGCCommon.pri | 4 + QGCSetup.pri | 10 +- android/AndroidManifest.xml | 67 + android/res/drawable-ldpi/icon.png | Bin 0 -> 49305 bytes android/res/xml/device_filter.xml | 40 + .../usbserial/driver/CdcAcmSerialDriver.java | 252 +++ .../driver/CommonUsbSerialDriver.java | 156 ++ .../usbserial/driver/Cp2102SerialDriver.java | 292 ++++ .../usbserial/driver/FtdiSerialDriver.java | 552 +++++++ .../driver/ProlificSerialDriver.java | 523 +++++++ .../hoho/android/usbserial/driver/UsbId.java | 69 + .../usbserial/driver/UsbSerialDriver.java | 228 +++ .../usbserial/driver/UsbSerialProber.java | 250 +++ .../driver/UsbSerialRuntimeException.java | 46 + .../qgchelper/UsbDeviceJNI.java | 637 ++++++++ .../qgchelper/UsbIoManager.java | 218 +++ .../src/qextserialport_unix.cpp | 4 + libs/qtandroidserialport/src/qserialport.cpp | 1375 +++++++++++++++++ libs/qtandroidserialport/src/qserialport.h | 287 ++++ .../src/qserialport_android.cpp | 867 +++++++++++ .../src/qserialport_android_p.h | 133 ++ libs/qtandroidserialport/src/qserialport_p.h | 338 ++++ .../src/qserialportinfo.cpp | 292 ++++ .../qtandroidserialport/src/qserialportinfo.h | 95 ++ .../src/qserialportinfo_android.cpp | 158 ++ .../src/qserialportinfo_p.h | 95 ++ .../src/qtandroidserialport.pri | 28 + src/QGC.h | 3 + src/QGCFileDialog.cc | 10 + src/QGCMessageBox.h | 4 + src/VehicleSetup/PX4Bootloader.cc | 4 + src/VehicleSetup/PX4FirmwareUpgradeThread.cc | 4 + src/comm/LinkConfiguration.cc | 6 + src/comm/LinkManager.cc | 8 + src/comm/LinkManager.h | 2 + src/comm/SerialLink.cc | 6 + src/comm/SerialLink.h | 4 + src/main.cc | 4 + src/qgcunittest/UnitTest.h | 2 + src/ui/MainWindow.cc | 15 +- src/ui/MainWindow.h | 4 + src/ui/SerialConfigurationWindow.cc | 5 + src/ui/SettingsDialog.cc | 12 + src/ui/SettingsDialog.h | 4 + src/ui/configuration/SerialSettingsDialog.cc | 5 + src/ui/configuration/SerialSettingsDialog.h | 4 + src/ui/configuration/terminalconsole.cpp | 5 + src/ui/configuration/terminalconsole.h | 4 + src/ui/designer/QGCParamSlider.h | 2 + src/ui/designer/QGCToolWidgetComboBox.h | 3 +- src/ui/toolbar/MainToolBar.cc | 5 + src/ui/toolbar/MainToolBar.qml | 75 +- 53 files changed, 7242 insertions(+), 55 deletions(-) create mode 100644 android/AndroidManifest.xml create mode 100644 android/res/drawable-ldpi/icon.png create mode 100644 android/res/xml/device_filter.xml create mode 100644 android/src/com/hoho/android/usbserial/driver/CdcAcmSerialDriver.java create mode 100644 android/src/com/hoho/android/usbserial/driver/CommonUsbSerialDriver.java create mode 100644 android/src/com/hoho/android/usbserial/driver/Cp2102SerialDriver.java create mode 100644 android/src/com/hoho/android/usbserial/driver/FtdiSerialDriver.java create mode 100644 android/src/com/hoho/android/usbserial/driver/ProlificSerialDriver.java create mode 100644 android/src/com/hoho/android/usbserial/driver/UsbId.java create mode 100644 android/src/com/hoho/android/usbserial/driver/UsbSerialDriver.java create mode 100644 android/src/com/hoho/android/usbserial/driver/UsbSerialProber.java create mode 100644 android/src/com/hoho/android/usbserial/driver/UsbSerialRuntimeException.java create mode 100644 android/src/org/qgroundcontrol/qgchelper/UsbDeviceJNI.java create mode 100644 android/src/org/qgroundcontrol/qgchelper/UsbIoManager.java create mode 100644 libs/qtandroidserialport/src/qserialport.cpp create mode 100644 libs/qtandroidserialport/src/qserialport.h create mode 100644 libs/qtandroidserialport/src/qserialport_android.cpp create mode 100644 libs/qtandroidserialport/src/qserialport_android_p.h create mode 100644 libs/qtandroidserialport/src/qserialport_p.h create mode 100644 libs/qtandroidserialport/src/qserialportinfo.cpp create mode 100644 libs/qtandroidserialport/src/qserialportinfo.h create mode 100644 libs/qtandroidserialport/src/qserialportinfo_android.cpp create mode 100644 libs/qtandroidserialport/src/qserialportinfo_p.h create mode 100644 libs/qtandroidserialport/src/qtandroidserialport.pri diff --git a/QGCApplication.pro b/QGCApplication.pro index 6dbe2fa68..9d57a8219 100644 --- a/QGCApplication.pro +++ b/QGCApplication.pro @@ -61,12 +61,15 @@ QT += network \ qml \ quick \ quickwidgets \ - serialport \ sql \ svg \ widgets \ xml \ +!AndroidBuild { + QT += serialport +} + contains(DEFINES, QGC_NOTIFY_TUNES_ENABLED) { QT += multimedia } @@ -78,6 +81,11 @@ QT += testlib # OS Specific settings # +AndroidBuild { + DEFINES += __android__ + DEFINES += __STDC_LIMIT_MACROS +} + MacBuild { QMAKE_INFO_PLIST = Custom-Info.plist ICON = $$BASEDIR/resources/icons/macx.icns @@ -173,9 +181,6 @@ FORMS += \ src/ui/designer/QGCToolWidgetComboBox.ui \ src/ui/designer/QGCXYPlot.ui \ src/ui/HDDisplay.ui \ - src/ui/JoystickAxis.ui \ - src/ui/JoystickButton.ui \ - src/ui/JoystickWidget.ui \ src/ui/Linechart.ui \ src/ui/MainWindow.ui \ src/ui/map/QGCMapTool.ui \ @@ -229,6 +234,13 @@ FORMS += \ src/ui/WaypointList.ui \ src/ui/WaypointViewOnlyView.ui \ +!AndroidBuild { +FORMS += \ + src/ui/JoystickButton.ui \ + src/ui/JoystickAxis.ui \ + src/ui/JoystickWidget.ui +} + HEADERS += \ src/audio/QGCAudioWorker.h \ src/CmdLineOptParser.h \ @@ -250,7 +262,6 @@ HEADERS += \ src/comm/TCPLink.h \ src/comm/UDPLink.h \ src/GAudioOutput.h \ - src/input/JoystickInput.h \ src/LogCompressor.h \ src/MG.h \ src/QGC.h \ @@ -281,7 +292,6 @@ HEADERS += \ src/uas/UASParameterCommsMgr.h \ src/uas/UASParameterDataModel.h \ src/uas/UASWaypointManager.h \ - src/ui/CameraView.h \ src/ui/configuration/ApmHighlighter.h \ src/ui/configuration/console.h \ src/ui/configuration/SerialSettingsDialog.h \ @@ -300,9 +310,6 @@ HEADERS += \ src/ui/HDDisplay.h \ src/ui/HSIDisplay.h \ src/ui/HUD.h \ - src/ui/JoystickAxis.h \ - src/ui/JoystickButton.h \ - src/ui/JoystickWidget.h \ src/ui/linechart/ChartPlot.h \ src/ui/linechart/IncrementalPlot.h \ src/ui/linechart/LinechartPlot.h \ @@ -376,6 +383,15 @@ HEADERS += \ src/ViewWidgets/ViewWidgetController.h \ src/Waypoint.h \ +!AndroidBuild { +HEADERS += \ + src/input/JoystickInput.h \ + src/ui/JoystickAxis.h \ + src/ui/JoystickButton.h \ + src/ui/JoystickWidget.h \ + src/ui/CameraView.h \ +} + SOURCES += \ src/audio/QGCAudioWorker.cpp \ src/CmdLineOptParser.cc \ @@ -393,7 +409,6 @@ SOURCES += \ src/comm/TCPLink.cc \ src/comm/UDPLink.cc \ src/GAudioOutput.cc \ - src/input/JoystickInput.cc \ src/LogCompressor.cc \ src/main.cc \ src/QGC.cc \ @@ -418,7 +433,6 @@ SOURCES += \ src/uas/UASParameterCommsMgr.cc \ src/uas/UASParameterDataModel.cc \ src/uas/UASWaypointManager.cc \ - src/ui/CameraView.cc \ src/ui/configuration/ApmHighlighter.cc \ src/ui/configuration/console.cpp \ src/ui/configuration/SerialSettingsDialog.cc \ @@ -437,9 +451,6 @@ SOURCES += \ src/ui/HDDisplay.cc \ src/ui/HSIDisplay.cc \ src/ui/HUD.cc \ - src/ui/JoystickAxis.cc \ - src/ui/JoystickButton.cc \ - src/ui/JoystickWidget.cc \ src/ui/linechart/ChartPlot.cc \ src/ui/linechart/IncrementalPlot.cc \ src/ui/linechart/LinechartPlot.cc \ @@ -513,6 +524,15 @@ SOURCES += \ src/ViewWidgets/ViewWidgetController.cc \ src/Waypoint.cc \ +!AndroidBuild { +SOURCES += \ + src/input/JoystickInput.cc \ + src/ui/JoystickAxis.cc \ + src/ui/JoystickButton.cc \ + src/ui/JoystickWidget.cc \ + src/ui/CameraView.cc +} + # # Unit Test specific configuration goes here # @@ -524,6 +544,11 @@ SOURCES += \ DebugBuild|WindowsDebugAndRelease { +HEADERS += src/QmlControls/QmlTestWidget.h +SOURCES += src/QmlControls/QmlTestWidget.cc + +!AndroidBuild { + INCLUDEPATH += \ src/qgcunittest @@ -550,7 +575,6 @@ HEADERS += \ src/qgcunittest/MockLinkMissionItemHandler.h \ src/qgcunittest/PX4RCCalibrationTest.h \ src/qgcunittest/UnitTest.h \ - src/QmlControls/QmlTestWidget.h \ src/VehicleSetup/SetupViewTest.h \ SOURCES += \ @@ -575,10 +599,10 @@ SOURCES += \ src/qgcunittest/MockLinkMissionItemHandler.cc \ src/qgcunittest/PX4RCCalibrationTest.cc \ src/qgcunittest/UnitTest.cc \ - src/QmlControls/QmlTestWidget.cc \ src/VehicleSetup/SetupViewTest.cc \ -} +} # DebugBuild|WindowsDebugAndRelease +} # AndroidBuild # # AutoPilot Plugin Support @@ -658,3 +682,26 @@ SOURCES += \ src/FactSystem/FactSystem.cc \ src/FactSystem/FactValidator.cc \ src/FactSystem/ParameterLoader.cc \ + +# Android + +AndroidBuild { + include($$PWD/libs/qtandroidserialport/src/qtandroidserialport.pri) + message("Adding Serial Java Classes") + QT += androidextras + ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android + OTHER_FILES += \ + $$PWD/android/AndroidManifest.xml \ + $$PWD/android/res/xml/device_filter.xml \ + $$PWD/android/src/com/hoho/android/usbserial/driver/CdcAcmSerialDriver.java \ + $$PWD/android/src/com/hoho/android/usbserial/driver/CommonUsbSerialDriver.java \ + $$PWD/android/src/com/hoho/android/usbserial/driver/Cp2102SerialDriver.java \ + $$PWD/android/src/com/hoho/android/usbserial/driver/FtdiSerialDriver.java \ + $$PWD/android/src/com/hoho/android/usbserial/driver/ProlificSerialDriver.java \ + $$PWD/android/src/com/hoho/android/usbserial/driver/UsbId.java \ + $$PWD/android/src/com/hoho/android/usbserial/driver/UsbSerialDriver.java \ + $$PWD/android/src/com/hoho/android/usbserial/driver/UsbSerialProber.java \ + $$PWD/android/src/com/hoho/android/usbserial/driver/UsbSerialRuntimeException.java \ + $$PWD/android/src/org/qgroundcontrol/qgchelper/UsbDeviceJNI.java \ + $$PWD/android/src/org/qgroundcontrol/qgchelper/UsbIoManager.java +} diff --git a/QGCCommon.pri b/QGCCommon.pri index b1e891af7..ce362f1fa 100644 --- a/QGCCommon.pri +++ b/QGCCommon.pri @@ -30,6 +30,10 @@ linux { linux-g++ | linux-g++-64 { message("Linux build") CONFIG += LinuxBuild + } else : android-g++ { + message("Android build") + CONFIG += AndroidBuild + warning("Android build is experimental and not fully functional") } else { error("Unsuported Linux toolchain, only GCC 32- or 64-bit is supported") } diff --git a/QGCSetup.pri b/QGCSetup.pri index ae7e716af..cce994557 100644 --- a/QGCSetup.pri +++ b/QGCSetup.pri @@ -19,6 +19,10 @@ QMAKE_POST_LINK += echo "Copying files" +AndroidBuild { + INSTALLS += $$DESTDIR +} + # # Copy the application resources to the associated place alongside the application # @@ -40,8 +44,10 @@ WindowsBuild { BASEDIR_COPY_RESOURCE_LIST = $$replace(BASEDIR,"/","\\") QMAKE_POST_LINK += $$escape_expand(\\n) $$QMAKE_COPY_DIR \"$$BASEDIR_COPY_RESOURCE_LIST\\flightgear\" \"$$DESTDIR_COPY_RESOURCE_LIST\\flightgear\" } else { - # Make sure to keep both side of this if using the same set of directories - QMAKE_POST_LINK += && $$QMAKE_COPY_DIR $$BASEDIR/flightgear $$DESTDIR_COPY_RESOURCE_LIST + !AndroidBuild { + # Make sure to keep both sides of this if using the same set of directories + QMAKE_POST_LINK += && $$QMAKE_COPY_DIR $$BASEDIR/flightgear $$DESTDIR_COPY_RESOURCE_LIST + } } # diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml new file mode 100644 index 000000000..21fcc1884 --- /dev/null +++ b/android/AndroidManifest.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/res/drawable-ldpi/icon.png b/android/res/drawable-ldpi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f487d95c0fb9f40af5ecd2d2feea9473bcd30a0a GIT binary patch literal 49305 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 + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/src/com/hoho/android/usbserial/driver/CdcAcmSerialDriver.java b/android/src/com/hoho/android/usbserial/driver/CdcAcmSerialDriver.java new file mode 100644 index 000000000..3dec53a9a --- /dev/null +++ b/android/src/com/hoho/android/usbserial/driver/CdcAcmSerialDriver.java @@ -0,0 +1,252 @@ +package com.hoho.android.usbserial.driver; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; +import android.util.Log; + +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * USB CDC/ACM serial driver implementation. + * + * @author mike wakerly (opensource@hoho.com) + * @see Universal + * Serial Bus Class Definitions for Communication Devices, v1.1 + */ +public class CdcAcmSerialDriver extends CommonUsbSerialDriver { + + private final String TAG = CdcAcmSerialDriver.class.getSimpleName(); + + private UsbInterface mControlInterface; + private UsbInterface mDataInterface; + + private UsbEndpoint mControlEndpoint; + private UsbEndpoint mReadEndpoint; + private UsbEndpoint mWriteEndpoint; + + private boolean mRts = false; + private boolean mDtr = false; + + private static final int USB_RECIP_INTERFACE = 0x01; + private static final int USB_RT_ACM = UsbConstants.USB_TYPE_CLASS | USB_RECIP_INTERFACE; + + private static final int SET_LINE_CODING = 0x20; // USB CDC 1.1 section 6.2 + private static final int GET_LINE_CODING = 0x21; + private static final int SET_CONTROL_LINE_STATE = 0x22; + private static final int SEND_BREAK = 0x23; + + public CdcAcmSerialDriver(UsbDevice device, UsbDeviceConnection connection) { + super(device, connection); + } + + @Override + public void open() throws IOException { + Log.d(TAG, "claiming interfaces, count=" + mDevice.getInterfaceCount()); + + Log.d(TAG, "Claiming control interface."); + mControlInterface = mDevice.getInterface(0); + Log.d(TAG, "Control iface=" + mControlInterface); + // class should be USB_CLASS_COMM + + if (!mConnection.claimInterface(mControlInterface, true)) { + throw new IOException("Could not claim control interface."); + } + mControlEndpoint = mControlInterface.getEndpoint(0); + Log.d(TAG, "Control endpoint direction: " + mControlEndpoint.getDirection()); + + Log.d(TAG, "Claiming data interface."); + mDataInterface = mDevice.getInterface(1); + Log.d(TAG, "data iface=" + mDataInterface); + // class should be USB_CLASS_CDC_DATA + + if (!mConnection.claimInterface(mDataInterface, true)) { + throw new IOException("Could not claim data interface."); + } + mReadEndpoint = mDataInterface.getEndpoint(1); + Log.d(TAG, "Read endpoint direction: " + mReadEndpoint.getDirection()); + mWriteEndpoint = mDataInterface.getEndpoint(0); + Log.d(TAG, "Write endpoint direction: " + mWriteEndpoint.getDirection()); + } + + private int sendAcmControlMessage(int request, int value, byte[] buf) { + return mConnection.controlTransfer( + USB_RT_ACM, request, value, 0, buf, buf != null ? buf.length : 0, 5000); + } + + @Override + public void close() throws IOException { + mConnection.close(); + } + + @Override + public int read(byte[] dest, int timeoutMillis) throws IOException { + final int numBytesRead; + synchronized (mReadBufferLock) { + int readAmt = Math.min(dest.length, mReadBuffer.length); + numBytesRead = mConnection.bulkTransfer(mReadEndpoint, mReadBuffer, readAmt, + timeoutMillis); + if (numBytesRead < 0) { + // This sucks: we get -1 on timeout, not 0 as preferred. + // We *should* use UsbRequest, except it has a bug/api oversight + // where there is no way to determine the number of bytes read + // in response :\ -- http://b.android.com/28023 + return 0; + } + System.arraycopy(mReadBuffer, 0, dest, 0, numBytesRead); + } + return numBytesRead; + } + + @Override + public int write(byte[] src, int timeoutMillis) throws IOException { + // TODO(mikey): Nearly identical to FtdiSerial write. Refactor. + int offset = 0; + + while (offset < src.length) { + final int writeLength; + final int amtWritten; + + synchronized (mWriteBufferLock) { + final byte[] writeBuffer; + + writeLength = Math.min(src.length - offset, mWriteBuffer.length); + if (offset == 0) { + writeBuffer = src; + } else { + // bulkTransfer does not support offsets, make a copy. + System.arraycopy(src, offset, mWriteBuffer, 0, writeLength); + writeBuffer = mWriteBuffer; + } + + amtWritten = mConnection.bulkTransfer(mWriteEndpoint, writeBuffer, writeLength, + timeoutMillis); + } + if (amtWritten <= 0) { + throw new IOException("Error writing " + writeLength + + " bytes at offset " + offset + " length=" + src.length); + } + + //Log.d(TAG, "Wrote amt=" + amtWritten + " attempted=" + writeLength); + offset += amtWritten; + } + return offset; + } + + @Override + public void setParameters(int baudRate, int dataBits, int stopBits, int parity) { + byte stopBitsByte; + switch (stopBits) { + case STOPBITS_1: stopBitsByte = 0; break; + case STOPBITS_1_5: stopBitsByte = 1; break; + case STOPBITS_2: stopBitsByte = 2; break; + default: throw new IllegalArgumentException("Bad value for stopBits: " + stopBits); + } + + byte parityBitesByte; + switch (parity) { + case PARITY_NONE: parityBitesByte = 0; break; + case PARITY_ODD: parityBitesByte = 1; break; + case PARITY_EVEN: parityBitesByte = 2; break; + case PARITY_MARK: parityBitesByte = 3; break; + case PARITY_SPACE: parityBitesByte = 4; break; + default: throw new IllegalArgumentException("Bad value for parity: " + parity); + } + + byte[] msg = { + (byte) ( baudRate & 0xff), + (byte) ((baudRate >> 8 ) & 0xff), + (byte) ((baudRate >> 16) & 0xff), + (byte) ((baudRate >> 24) & 0xff), + stopBitsByte, + parityBitesByte, + (byte) dataBits}; + sendAcmControlMessage(SET_LINE_CODING, 0, msg); + } + + @Override + public boolean getCD() throws IOException { + return false; // TODO + } + + @Override + public boolean getCTS() throws IOException { + return false; // TODO + } + + @Override + public boolean getDSR() throws IOException { + return false; // TODO + } + + @Override + public boolean getDTR() throws IOException { + return mDtr; + } + + @Override + public void setDTR(boolean value) throws IOException { + mDtr = value; + setDtrRts(); + } + + @Override + public boolean getRI() throws IOException { + return false; // TODO + } + + @Override + public boolean getRTS() throws IOException { + return mRts; + } + + @Override + public void setRTS(boolean value) throws IOException { + mRts = value; + setDtrRts(); + } + + private void setDtrRts() { + int value = (mRts ? 0x2 : 0) | (mDtr ? 0x1 : 0); + sendAcmControlMessage(SET_CONTROL_LINE_STATE, value, null); + } + + public static Map getSupportedDevices() { + final Map supportedDevices = new LinkedHashMap(); + supportedDevices.put(Integer.valueOf(UsbId.VENDOR_ARDUINO), + new int[] { + UsbId.ARDUINO_UNO, + UsbId.ARDUINO_UNO_R3, + UsbId.ARDUINO_MEGA_2560, + UsbId.ARDUINO_MEGA_2560_R3, + UsbId.ARDUINO_SERIAL_ADAPTER, + UsbId.ARDUINO_SERIAL_ADAPTER_R3, + UsbId.ARDUINO_MEGA_ADK, + UsbId.ARDUINO_MEGA_ADK_R3, + UsbId.ARDUINO_LEONARDO, + }); + supportedDevices.put(Integer.valueOf(UsbId.VENDOR_VAN_OOIJEN_TECH), + new int[] { + UsbId.VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL, + }); + supportedDevices.put(Integer.valueOf(UsbId.VENDOR_ATMEL), + new int[] { + UsbId.ATMEL_LUFA_CDC_DEMO_APP, + }); + supportedDevices.put(Integer.valueOf(UsbId.VENDOR_LEAFLABS), + new int[] { + UsbId.LEAFLABS_MAPLE, + }); + supportedDevices.put(Integer.valueOf(UsbId.VENDOR_PX4), + new int[] { + UsbId.DEVICE_PX4FMU, + }); + return supportedDevices; + } + +} diff --git a/android/src/com/hoho/android/usbserial/driver/CommonUsbSerialDriver.java b/android/src/com/hoho/android/usbserial/driver/CommonUsbSerialDriver.java new file mode 100644 index 000000000..734933a22 --- /dev/null +++ b/android/src/com/hoho/android/usbserial/driver/CommonUsbSerialDriver.java @@ -0,0 +1,156 @@ +/* Copyright 2013 Google Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * Project home page: http://code.google.com/p/usb-serial-for-android/ + */ + +package com.hoho.android.usbserial.driver; + +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; + +import java.io.IOException; + +/** + * A base class shared by several driver implementations. + * + * @author mike wakerly (opensource@hoho.com) + */ +abstract class CommonUsbSerialDriver implements UsbSerialDriver { + + public static final int DEFAULT_READ_BUFFER_SIZE = 16 * 1024; + public static final int DEFAULT_WRITE_BUFFER_SIZE = 16 * 1024; + + protected final UsbDevice mDevice; + protected final UsbDeviceConnection mConnection; + + protected final Object mReadBufferLock = new Object(); + protected final Object mWriteBufferLock = new Object(); + + /** Internal read buffer. Guarded by {@link #mReadBufferLock}. */ + protected byte[] mReadBuffer; + + /** Internal write buffer. Guarded by {@link #mWriteBufferLock}. */ + protected byte[] mWriteBuffer; + + public CommonUsbSerialDriver(UsbDevice device, UsbDeviceConnection connection) { + mDevice = device; + mConnection = connection; + + mReadBuffer = new byte[DEFAULT_READ_BUFFER_SIZE]; + mWriteBuffer = new byte[DEFAULT_WRITE_BUFFER_SIZE]; + } + + /** + * Returns the currently-bound USB device. + * + * @return the device + */ + @Override + public final UsbDevice getDevice() { + return mDevice; + } + + /** + * Returns the currently-bound USB device connection. + * + * @return the device connection + */ + @Override + public final UsbDeviceConnection getDeviceConnection() { + return mConnection; + } + + + + /** + * Sets the size of the internal buffer used to exchange data with the USB + * stack for read operations. Most users should not need to change this. + * + * @param bufferSize the size in bytes + */ + public final void setReadBufferSize(int bufferSize) { + synchronized (mReadBufferLock) { + if (bufferSize == mReadBuffer.length) { + return; + } + mReadBuffer = new byte[bufferSize]; + } + } + + /** + * Sets the size of the internal buffer used to exchange data with the USB + * stack for write operations. Most users should not need to change this. + * + * @param bufferSize the size in bytes + */ + public final void setWriteBufferSize(int bufferSize) { + synchronized (mWriteBufferLock) { + if (bufferSize == mWriteBuffer.length) { + return; + } + mWriteBuffer = new byte[bufferSize]; + } + } + + @Override + public abstract void open() throws IOException; + + @Override + public abstract void close() throws IOException; + + @Override + public abstract int read(final byte[] dest, final int timeoutMillis) throws IOException; + + @Override + public abstract int write(final byte[] src, final int timeoutMillis) throws IOException; + + @Override + public abstract void setParameters( + int baudRate, int dataBits, int stopBits, int parity) throws IOException; + + @Override + public abstract boolean getCD() throws IOException; + + @Override + public abstract boolean getCTS() throws IOException; + + @Override + public abstract boolean getDSR() throws IOException; + + @Override + public abstract boolean getDTR() throws IOException; + + @Override + public abstract void setDTR(boolean value) throws IOException; + + @Override + public abstract boolean getRI() throws IOException; + + @Override + public abstract boolean getRTS() throws IOException; + + @Override + public abstract void setRTS(boolean value) throws IOException; + + @Override + public boolean purgeHwBuffers(boolean flushReadBuffers, boolean flushWriteBuffers) throws IOException { + return !flushReadBuffers && !flushWriteBuffers; + } + +} + diff --git a/android/src/com/hoho/android/usbserial/driver/Cp2102SerialDriver.java b/android/src/com/hoho/android/usbserial/driver/Cp2102SerialDriver.java new file mode 100644 index 000000000..aa151ba1a --- /dev/null +++ b/android/src/com/hoho/android/usbserial/driver/Cp2102SerialDriver.java @@ -0,0 +1,292 @@ +package com.hoho.android.usbserial.driver; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; +import android.util.Log; + +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; + +public class Cp2102SerialDriver extends CommonUsbSerialDriver { + + private static final String TAG = Cp2102SerialDriver.class.getSimpleName(); + + private static final int DEFAULT_BAUD_RATE = 9600; + + private static final int USB_WRITE_TIMEOUT_MILLIS = 5000; + + /* + * Configuration Request Types + */ + private static final int REQTYPE_HOST_TO_DEVICE = 0x41; + + /* + * Configuration Request Codes + */ + private static final int SILABSER_IFC_ENABLE_REQUEST_CODE = 0x00; + private static final int SILABSER_SET_BAUDDIV_REQUEST_CODE = 0x01; + private static final int SILABSER_SET_LINE_CTL_REQUEST_CODE = 0x03; + private static final int SILABSER_SET_MHS_REQUEST_CODE = 0x07; + private static final int SILABSER_SET_BAUDRATE = 0x1E; + private static final int SILABSER_FLUSH_REQUEST_CODE = 0x12; + + private static final int FLUSH_READ_CODE = 0x0a; + private static final int FLUSH_WRITE_CODE = 0x05; + + /* + * SILABSER_IFC_ENABLE_REQUEST_CODE + */ + private static final int UART_ENABLE = 0x0001; + private static final int UART_DISABLE = 0x0000; + + /* + * SILABSER_SET_BAUDDIV_REQUEST_CODE + */ + private static final int BAUD_RATE_GEN_FREQ = 0x384000; + + /* + * SILABSER_SET_MHS_REQUEST_CODE + */ + private static final int MCR_DTR = 0x0001; + private static final int MCR_RTS = 0x0002; + private static final int MCR_ALL = 0x0003; + + private static final int CONTROL_WRITE_DTR = 0x0100; + private static final int CONTROL_WRITE_RTS = 0x0200; + + private UsbEndpoint mReadEndpoint; + private UsbEndpoint mWriteEndpoint; + + public Cp2102SerialDriver(UsbDevice device, UsbDeviceConnection connection) { + super(device, connection); + } + + private int setConfigSingle(int request, int value) { + return mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request, value, + 0, null, 0, USB_WRITE_TIMEOUT_MILLIS); + } + + @Override + public void open() throws IOException { + boolean opened = false; + try { + for (int i = 0; i < mDevice.getInterfaceCount(); i++) { + UsbInterface usbIface = mDevice.getInterface(i); + if (mConnection.claimInterface(usbIface, true)) { + Log.d(TAG, "claimInterface " + i + " SUCCESS"); + } else { + Log.d(TAG, "claimInterface " + i + " FAIL"); + } + } + + UsbInterface dataIface = mDevice.getInterface(mDevice.getInterfaceCount() - 1); + for (int i = 0; i < dataIface.getEndpointCount(); i++) { + UsbEndpoint ep = dataIface.getEndpoint(i); + if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) { + if (ep.getDirection() == UsbConstants.USB_DIR_IN) { + mReadEndpoint = ep; + } else { + mWriteEndpoint = ep; + } + } + } + + setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_ENABLE); + setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, MCR_ALL | CONTROL_WRITE_DTR | CONTROL_WRITE_RTS); + setConfigSingle(SILABSER_SET_BAUDDIV_REQUEST_CODE, BAUD_RATE_GEN_FREQ / DEFAULT_BAUD_RATE); +// setParameters(DEFAULT_BAUD_RATE, DEFAULT_DATA_BITS, DEFAULT_STOP_BITS, DEFAULT_PARITY); + opened = true; + } finally { + if (!opened) { + close(); + } + } + } + + @Override + public void close() throws IOException { + setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_DISABLE); + mConnection.close(); + } + + @Override + public int read(byte[] dest, int timeoutMillis) throws IOException { + final int numBytesRead; + synchronized (mReadBufferLock) { + int readAmt = Math.min(dest.length, mReadBuffer.length); + numBytesRead = mConnection.bulkTransfer(mReadEndpoint, mReadBuffer, readAmt, + timeoutMillis); + if (numBytesRead < 0) { + // This sucks: we get -1 on timeout, not 0 as preferred. + // We *should* use UsbRequest, except it has a bug/api oversight + // where there is no way to determine the number of bytes read + // in response :\ -- http://b.android.com/28023 + return 0; + } + System.arraycopy(mReadBuffer, 0, dest, 0, numBytesRead); + } + return numBytesRead; + } + + @Override + public int write(byte[] src, int timeoutMillis) throws IOException { + int offset = 0; + + while (offset < src.length) { + final int writeLength; + final int amtWritten; + + synchronized (mWriteBufferLock) { + final byte[] writeBuffer; + + writeLength = Math.min(src.length - offset, mWriteBuffer.length); + if (offset == 0) { + writeBuffer = src; + } else { + // bulkTransfer does not support offsets, make a copy. + System.arraycopy(src, offset, mWriteBuffer, 0, writeLength); + writeBuffer = mWriteBuffer; + } + + amtWritten = mConnection.bulkTransfer(mWriteEndpoint, writeBuffer, writeLength, + timeoutMillis); + } + if (amtWritten <= 0) { + throw new IOException("Error writing " + writeLength + + " bytes at offset " + offset + " length=" + src.length); + } + + //Log.d(TAG, "Wrote amt=" + amtWritten + " attempted=" + writeLength); + offset += amtWritten; + } + return offset; + } + + private void setBaudRate(int baudRate) throws IOException { + byte[] data = new byte[] { + (byte) ( baudRate & 0xff), + (byte) ((baudRate >> 8 ) & 0xff), + (byte) ((baudRate >> 16) & 0xff), + (byte) ((baudRate >> 24) & 0xff) + }; + int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_BAUDRATE, + 0, 0, data, 4, USB_WRITE_TIMEOUT_MILLIS); + if (ret < 0) { + throw new IOException("Error setting baud rate."); + } + } + + @Override + public void setParameters(int baudRate, int dataBits, int stopBits, int parity) + throws IOException { + setBaudRate(baudRate); + + int configDataBits = 0; + switch (dataBits) { + case DATABITS_5: + configDataBits |= 0x0500; + break; + case DATABITS_6: + configDataBits |= 0x0600; + break; + case DATABITS_7: + configDataBits |= 0x0700; + break; + case DATABITS_8: + configDataBits |= 0x0800; + break; + default: + configDataBits |= 0x0800; + break; + } + setConfigSingle(SILABSER_SET_LINE_CTL_REQUEST_CODE, configDataBits); + + int configParityBits = 0; // PARITY_NONE + switch (parity) { + case PARITY_ODD: + configParityBits |= 0x0010; + break; + case PARITY_EVEN: + configParityBits |= 0x0020; + break; + } + setConfigSingle(SILABSER_SET_LINE_CTL_REQUEST_CODE, configParityBits); + + int configStopBits = 0; + switch (stopBits) { + case STOPBITS_1: + configStopBits |= 0; + break; + case STOPBITS_2: + configStopBits |= 2; + break; + } + setConfigSingle(SILABSER_SET_LINE_CTL_REQUEST_CODE, configStopBits); + } + + @Override + public boolean getCD() throws IOException { + return false; + } + + @Override + public boolean getCTS() throws IOException { + return false; + } + + @Override + public boolean getDSR() throws IOException { + return false; + } + + @Override + public boolean getDTR() throws IOException { + return true; + } + + @Override + public void setDTR(boolean value) throws IOException { + } + + @Override + public boolean getRI() throws IOException { + return false; + } + + @Override + public boolean getRTS() throws IOException { + return true; + } + + @Override + public boolean purgeHwBuffers(boolean purgeReadBuffers, + boolean purgeWriteBuffers) throws IOException { + int value = (purgeReadBuffers ? FLUSH_READ_CODE : 0) + | (purgeWriteBuffers ? FLUSH_WRITE_CODE : 0); + + if (value != 0) { + setConfigSingle(SILABSER_FLUSH_REQUEST_CODE, value); + } + + return true; + } + + @Override + public void setRTS(boolean value) throws IOException { + } + + public static Map getSupportedDevices() { + final Map supportedDevices = new LinkedHashMap(); + supportedDevices.put(Integer.valueOf(UsbId.VENDOR_SILAB), + new int[] { + UsbId.SILAB_CP2102 + }); + return supportedDevices; + } + + +} diff --git a/android/src/com/hoho/android/usbserial/driver/FtdiSerialDriver.java b/android/src/com/hoho/android/usbserial/driver/FtdiSerialDriver.java new file mode 100644 index 000000000..ad3627ff3 --- /dev/null +++ b/android/src/com/hoho/android/usbserial/driver/FtdiSerialDriver.java @@ -0,0 +1,552 @@ +/* Copyright 2011 Google Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * Project home page: http://code.google.com/p/usb-serial-for-android/ + */ + +package com.hoho.android.usbserial.driver; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbRequest; +import android.util.Log; + + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * A {@link CommonUsbSerialDriver} implementation for a variety of FTDI devices + *

+ * This driver is based on + * libftdi, and is + * copyright and subject to the following terms: + * + *

+ *   Copyright (C) 2003 by Intra2net AG
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License
+ *   version 2.1 as published by the Free Software Foundation;
+ *
+ *   opensource@intra2net.com
+ *   http://www.intra2net.com/en/developer/libftdi
+ * 
+ * + *

+ *

+ * Some FTDI devices have not been tested; see later listing of supported and + * unsupported devices. Devices listed as "supported" support the following + * features: + *

    + *
  • Read and write of serial data (see {@link #read(byte[], int)} and + * {@link #write(byte[], int)}. + *
  • Setting baud rate (see {@link #setBaudRate(int)}). + *
+ *

+ *

+ * Supported and tested devices: + *

    + *
  • {@value DeviceType#TYPE_R}
  • + *
+ *

+ *

+ * Unsupported but possibly working devices (please contact the author with + * feedback or patches): + *

    + *
  • {@value DeviceType#TYPE_2232C}
  • + *
  • {@value DeviceType#TYPE_2232H}
  • + *
  • {@value DeviceType#TYPE_4232H}
  • + *
  • {@value DeviceType#TYPE_AM}
  • + *
  • {@value DeviceType#TYPE_BM}
  • + *
+ *

+ * + * @author mike wakerly (opensource@hoho.com) + * @see USB Serial + * for Android project page + * @see FTDI Homepage + * @see libftdi + */ +public class FtdiSerialDriver extends CommonUsbSerialDriver { + + public static final int USB_TYPE_STANDARD = 0x00 << 5; + public static final int USB_TYPE_CLASS = 0x00 << 5; + public static final int USB_TYPE_VENDOR = 0x00 << 5; + public static final int USB_TYPE_RESERVED = 0x00 << 5; + + public static final int USB_RECIP_DEVICE = 0x00; + public static final int USB_RECIP_INTERFACE = 0x01; + public static final int USB_RECIP_ENDPOINT = 0x02; + public static final int USB_RECIP_OTHER = 0x03; + + public static final int USB_ENDPOINT_IN = 0x80; + public static final int USB_ENDPOINT_OUT = 0x00; + + public static final int USB_WRITE_TIMEOUT_MILLIS = 5000; + public static final int USB_READ_TIMEOUT_MILLIS = 5000; + + // From ftdi.h + /** + * Reset the port. + */ + private static final int SIO_RESET_REQUEST = 0; + + /** + * Set the modem control register. + */ + private static final int SIO_MODEM_CTRL_REQUEST = 1; + + /** + * Set flow control register. + */ + private static final int SIO_SET_FLOW_CTRL_REQUEST = 2; + + /** + * Set baud rate. + */ + private static final int SIO_SET_BAUD_RATE_REQUEST = 3; + + /** + * Set the data characteristics of the port. + */ + private static final int SIO_SET_DATA_REQUEST = 4; + + private static final int SIO_RESET_SIO = 0; + private static final int SIO_RESET_PURGE_RX = 1; + private static final int SIO_RESET_PURGE_TX = 2; + + public static final int FTDI_DEVICE_OUT_REQTYPE = + UsbConstants.USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_OUT; + + public static final int FTDI_DEVICE_IN_REQTYPE = + UsbConstants.USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN; + + /** + * Length of the modem status header, transmitted with every read. + */ + private static final int MODEM_STATUS_HEADER_LENGTH = 2; + + private final String TAG = FtdiSerialDriver.class.getSimpleName(); + + private DeviceType mType; + + /** + * FTDI chip types. + */ + private static enum DeviceType { + TYPE_BM, TYPE_AM, TYPE_2232C, TYPE_R, TYPE_2232H, TYPE_4232H; + } + + private int mInterface = 0; /* INTERFACE_ANY */ + + private int mMaxPacketSize = 64; // TODO(mikey): detect + + /** + * Due to http://b.android.com/28023 , we cannot use UsbRequest async reads + * since it gives no indication of number of bytes read. Set this to + * {@code true} on platforms where it is fixed. + */ + private static final boolean ENABLE_ASYNC_READS = false; + + /** + * Filter FTDI status bytes from buffer + * @param src The source buffer (which contains status bytes) + * @param dest The destination buffer to write the status bytes into (can be src) + * @param totalBytesRead Number of bytes read to src + * @param maxPacketSize The USB endpoint max packet size + * @return The number of payload bytes + */ + private final int filterStatusBytes(byte[] src, byte[] dest, int totalBytesRead, int maxPacketSize) { + final int packetsCount = totalBytesRead / maxPacketSize + 1; + for (int packetIdx = 0; packetIdx < packetsCount; ++packetIdx) { + final int count = (packetIdx == (packetsCount - 1)) + ? (totalBytesRead % maxPacketSize) - MODEM_STATUS_HEADER_LENGTH + : maxPacketSize - MODEM_STATUS_HEADER_LENGTH; + if (count > 0) { + System.arraycopy(src, + packetIdx * maxPacketSize + MODEM_STATUS_HEADER_LENGTH, + dest, + packetIdx * (maxPacketSize - MODEM_STATUS_HEADER_LENGTH), + count); + } + } + + return totalBytesRead - (packetsCount * 2); + } + + /** + * Constructor. + * + * @param usbDevice the {@link UsbDevice} to use + * @param usbConnection the {@link UsbDeviceConnection} to use + * @throws UsbSerialRuntimeException if the given device is incompatible + * with this driver + */ + public FtdiSerialDriver(UsbDevice usbDevice, UsbDeviceConnection usbConnection) { + super(usbDevice, usbConnection); + mType = null; + } + + public void reset() throws IOException { + int result = mConnection.controlTransfer(FTDI_DEVICE_OUT_REQTYPE, SIO_RESET_REQUEST, + SIO_RESET_SIO, 0 /* index */, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Reset failed: result=" + result); + } + + // TODO(mikey): autodetect. + mType = DeviceType.TYPE_R; + } + + @Override + public void open() throws IOException { + boolean opened = false; + try { + for (int i = 0; i < mDevice.getInterfaceCount(); i++) { + if (mConnection.claimInterface(mDevice.getInterface(i), true)) { + Log.d(TAG, "claimInterface " + i + " SUCCESS"); + } else { + throw new IOException("Error claiming interface " + i); + } + } + reset(); + opened = true; + } finally { + if (!opened) { + close(); + } + } + } + + @Override + public void close() { + mConnection.close(); + } + + @Override + public int read(byte[] dest, int timeoutMillis) throws IOException { + final UsbEndpoint endpoint = mDevice.getInterface(0).getEndpoint(0); + + if (ENABLE_ASYNC_READS) { + final int readAmt; + synchronized (mReadBufferLock) { + // mReadBuffer is only used for maximum read size. + readAmt = Math.min(dest.length, mReadBuffer.length); + } + + final UsbRequest request = new UsbRequest(); + request.initialize(mConnection, endpoint); + + final ByteBuffer buf = ByteBuffer.wrap(dest); + if (!request.queue(buf, readAmt)) { + throw new IOException("Error queueing request."); + } + + final UsbRequest response = mConnection.requestWait(); + if (response == null) { + throw new IOException("Null response"); + } + + final int payloadBytesRead = buf.position() - MODEM_STATUS_HEADER_LENGTH; + if (payloadBytesRead > 0) { + return payloadBytesRead; + } else { + return 0; + } + } else { + final int totalBytesRead; + + synchronized (mReadBufferLock) { + final int readAmt = Math.min(dest.length, mReadBuffer.length); + totalBytesRead = mConnection.bulkTransfer(endpoint, mReadBuffer, + readAmt, timeoutMillis); + + if (totalBytesRead < MODEM_STATUS_HEADER_LENGTH) { + throw new IOException("Expected at least " + MODEM_STATUS_HEADER_LENGTH + " bytes"); + } + + return filterStatusBytes(mReadBuffer, dest, totalBytesRead, endpoint.getMaxPacketSize()); + } + } + } + + @Override + public int write(byte[] src, int timeoutMillis) throws IOException { + final UsbEndpoint endpoint = mDevice.getInterface(0).getEndpoint(1); + int offset = 0; + + while (offset < src.length) { + final int writeLength; + final int amtWritten; + + synchronized (mWriteBufferLock) { + final byte[] writeBuffer; + + writeLength = Math.min(src.length - offset, mWriteBuffer.length); + if (offset == 0) { + writeBuffer = src; + } else { + // bulkTransfer does not support offsets, make a copy. + System.arraycopy(src, offset, mWriteBuffer, 0, writeLength); + writeBuffer = mWriteBuffer; + } + + amtWritten = mConnection.bulkTransfer(endpoint, writeBuffer, writeLength, + timeoutMillis); + } + + if (amtWritten <= 0) { + throw new IOException("Error writing " + writeLength + + " bytes at offset " + offset + " length=" + src.length); + } + + //Log.d(TAG, "Wrote amtWritten=" + amtWritten + " attempted=" + writeLength); + offset += amtWritten; + } + return offset; + } + + private int setBaudRate(int baudRate) throws IOException { + long[] vals = convertBaudrate(baudRate); + long actualBaudrate = vals[0]; + long index = vals[1]; + long value = vals[2]; + int result = mConnection.controlTransfer(FTDI_DEVICE_OUT_REQTYPE, + SIO_SET_BAUD_RATE_REQUEST, (int) value, (int) index, + null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Setting baudrate failed: result=" + result); + } + return (int) actualBaudrate; + } + + @Override + public void setParameters(int baudRate, int dataBits, int stopBits, int parity) + throws IOException { + setBaudRate(baudRate); + + int config = dataBits; + + switch (parity) { + case PARITY_NONE: + config |= (0x00 << 8); + break; + case PARITY_ODD: + config |= (0x01 << 8); + break; + case PARITY_EVEN: + config |= (0x02 << 8); + break; + case PARITY_MARK: + config |= (0x03 << 8); + break; + case PARITY_SPACE: + config |= (0x04 << 8); + break; + default: + throw new IllegalArgumentException("Unknown parity value: " + parity); + } + + switch (stopBits) { + case STOPBITS_1: + config |= (0x00 << 11); + break; + case STOPBITS_1_5: + config |= (0x01 << 11); + break; + case STOPBITS_2: + config |= (0x02 << 11); + break; + default: + throw new IllegalArgumentException("Unknown stopBits value: " + stopBits); + } + + int result = mConnection.controlTransfer(FTDI_DEVICE_OUT_REQTYPE, + SIO_SET_DATA_REQUEST, config, 0 /* index */, + null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Setting parameters failed: result=" + result); + } + } + + private long[] convertBaudrate(int baudrate) { + // TODO(mikey): Braindead transcription of libfti method. Clean up, + // using more idiomatic Java where possible. + int divisor = 24000000 / baudrate; + int bestDivisor = 0; + int bestBaud = 0; + int bestBaudDiff = 0; + int fracCode[] = { + 0, 3, 2, 4, 1, 5, 6, 7 + }; + + for (int i = 0; i < 2; i++) { + int tryDivisor = divisor + i; + int baudEstimate; + int baudDiff; + + if (tryDivisor <= 8) { + // Round up to minimum supported divisor + tryDivisor = 8; + } else if (mType != DeviceType.TYPE_AM && tryDivisor < 12) { + // BM doesn't support divisors 9 through 11 inclusive + tryDivisor = 12; + } else if (divisor < 16) { + // AM doesn't support divisors 9 through 15 inclusive + tryDivisor = 16; + } else { + if (mType == DeviceType.TYPE_AM) { + // TODO + } else { + if (tryDivisor > 0x1FFFF) { + // Round down to maximum supported divisor value (for + // BM) + tryDivisor = 0x1FFFF; + } + } + } + + // Get estimated baud rate (to nearest integer) + baudEstimate = (24000000 + (tryDivisor / 2)) / tryDivisor; + + // Get absolute difference from requested baud rate + if (baudEstimate < baudrate) { + baudDiff = baudrate - baudEstimate; + } else { + baudDiff = baudEstimate - baudrate; + } + + if (i == 0 || baudDiff < bestBaudDiff) { + // Closest to requested baud rate so far + bestDivisor = tryDivisor; + bestBaud = baudEstimate; + bestBaudDiff = baudDiff; + if (baudDiff == 0) { + // Spot on! No point trying + break; + } + } + } + + // Encode the best divisor value + long encodedDivisor = (bestDivisor >> 3) | (fracCode[bestDivisor & 7] << 14); + // Deal with special cases for encoded value + if (encodedDivisor == 1) { + encodedDivisor = 0; // 3000000 baud + } else if (encodedDivisor == 0x4001) { + encodedDivisor = 1; // 2000000 baud (BM only) + } + + // Split into "value" and "index" values + long value = encodedDivisor & 0xFFFF; + long index; + if (mType == DeviceType.TYPE_2232C || mType == DeviceType.TYPE_2232H + || mType == DeviceType.TYPE_4232H) { + index = (encodedDivisor >> 8) & 0xffff; + index &= 0xFF00; + index |= 0 /* TODO mIndex */; + } else { + index = (encodedDivisor >> 16) & 0xffff; + } + + // Return the nearest baud rate + return new long[] { + bestBaud, index, value + }; + } + + @Override + public boolean getCD() throws IOException { + return false; + } + + @Override + public boolean getCTS() throws IOException { + return false; + } + + @Override + public boolean getDSR() throws IOException { + return false; + } + + @Override + public boolean getDTR() throws IOException { + return false; + } + + @Override + public void setDTR(boolean value) throws IOException { + } + + @Override + public boolean getRI() throws IOException { + return false; + } + + @Override + public boolean getRTS() throws IOException { + return false; + } + + @Override + public void setRTS(boolean value) throws IOException { + } + + @Override + public boolean purgeHwBuffers(boolean purgeReadBuffers, boolean purgeWriteBuffers) throws IOException { + if (purgeReadBuffers) { + int result = mConnection.controlTransfer(FTDI_DEVICE_OUT_REQTYPE, SIO_RESET_REQUEST, + SIO_RESET_PURGE_RX, 0 /* index */, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Flushing RX failed: result=" + result); + } + } + + if (purgeWriteBuffers) { + int result = mConnection.controlTransfer(FTDI_DEVICE_OUT_REQTYPE, SIO_RESET_REQUEST, + SIO_RESET_PURGE_TX, 0 /* index */, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Flushing RX failed: result=" + result); + } + } + + return true; + } + + public static Map getSupportedDevices() { + final Map supportedDevices = new LinkedHashMap(); + supportedDevices.put(Integer.valueOf(UsbId.VENDOR_FTDI), + new int[] { + UsbId.FTDI_FT232R, + UsbId.FTDI_FT231X, + }); + /* + supportedDevices.put(Integer.valueOf(UsbId.VENDOR_PX4), + new int[] { + UsbId.DEVICE_PX4FMU + }); + */ + return supportedDevices; + } + +} diff --git a/android/src/com/hoho/android/usbserial/driver/ProlificSerialDriver.java b/android/src/com/hoho/android/usbserial/driver/ProlificSerialDriver.java new file mode 100644 index 000000000..d6e2d1338 --- /dev/null +++ b/android/src/com/hoho/android/usbserial/driver/ProlificSerialDriver.java @@ -0,0 +1,523 @@ +/* This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * Project home page: http://code.google.com/p/usb-serial-for-android/ + */ + +/* + * Ported to usb-serial-for-android + * by Felix Hädicke + * + * Based on the pyprolific driver written + * by Emmanuel Blot + * See https://github.com/eblot/pyftdi + */ + +package com.hoho.android.usbserial.driver; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; +import android.util.Log; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.LinkedHashMap; +import java.util.Map; + +public class ProlificSerialDriver extends CommonUsbSerialDriver { + private static final int USB_READ_TIMEOUT_MILLIS = 1000; + private static final int USB_WRITE_TIMEOUT_MILLIS = 5000; + + private static final int USB_RECIP_INTERFACE = 0x01; + + private static final int PROLIFIC_VENDOR_READ_REQUEST = 0x01; + private static final int PROLIFIC_VENDOR_WRITE_REQUEST = 0x01; + + private static final int PROLIFIC_VENDOR_OUT_REQTYPE = UsbConstants.USB_DIR_OUT + | UsbConstants.USB_TYPE_VENDOR; + + private static final int PROLIFIC_VENDOR_IN_REQTYPE = UsbConstants.USB_DIR_IN + | UsbConstants.USB_TYPE_VENDOR; + + private static final int PROLIFIC_CTRL_OUT_REQTYPE = UsbConstants.USB_DIR_OUT + | UsbConstants.USB_TYPE_CLASS | USB_RECIP_INTERFACE; + + private static final int WRITE_ENDPOINT = 0x02; + private static final int READ_ENDPOINT = 0x83; + private static final int INTERRUPT_ENDPOINT = 0x81; + + private static final int FLUSH_RX_REQUEST = 0x08; + private static final int FLUSH_TX_REQUEST = 0x09; + + private static final int SET_LINE_REQUEST = 0x20; + private static final int SET_CONTROL_REQUEST = 0x22; + + private static final int CONTROL_DTR = 0x01; + private static final int CONTROL_RTS = 0x02; + + private static final int STATUS_FLAG_CD = 0x01; + private static final int STATUS_FLAG_DSR = 0x02; + private static final int STATUS_FLAG_RI = 0x08; + private static final int STATUS_FLAG_CTS = 0x80; + + private static final int STATUS_BUFFER_SIZE = 10; + private static final int STATUS_BYTE_IDX = 8; + + private static final int DEVICE_TYPE_HX = 0; + private static final int DEVICE_TYPE_0 = 1; + private static final int DEVICE_TYPE_1 = 2; + + private int mDeviceType = DEVICE_TYPE_HX; + + private UsbEndpoint mReadEndpoint; + private UsbEndpoint mWriteEndpoint; + private UsbEndpoint mInterruptEndpoint; + + private int mControlLinesValue = 0; + + private int mBaudRate = -1, mDataBits = -1, mStopBits = -1, mParity = -1; + + private int mStatus = 0; + private volatile Thread mReadStatusThread = null; + private final Object mReadStatusThreadLock = new Object(); + boolean mStopReadStatusThread = false; + private IOException mReadStatusException = null; + + private final String TAG = ProlificSerialDriver.class.getSimpleName(); + + private final byte[] inControlTransfer(int requestType, int request, + int value, int index, int length) throws IOException { + byte[] buffer = new byte[length]; + int result = mConnection.controlTransfer(requestType, request, value, + index, buffer, length, USB_READ_TIMEOUT_MILLIS); + if (result != length) { + throw new IOException( + String.format("ControlTransfer with value 0x%x failed: %d", + value, result)); + } + return buffer; + } + + private final void outControlTransfer(int requestType, int request, + int value, int index, byte[] data) throws IOException { + int length = (data == null) ? 0 : data.length; + int result = mConnection.controlTransfer(requestType, request, value, + index, data, length, USB_WRITE_TIMEOUT_MILLIS); + if (result != length) { + throw new IOException( + String.format("ControlTransfer with value 0x%x failed: %d", + value, result)); + } + } + + private final byte[] vendorIn(int value, int index, int length) + throws IOException { + return inControlTransfer(PROLIFIC_VENDOR_IN_REQTYPE, + PROLIFIC_VENDOR_READ_REQUEST, value, index, length); + } + + private final void vendorOut(int value, int index, byte[] data) + throws IOException { + outControlTransfer(PROLIFIC_VENDOR_OUT_REQTYPE, + PROLIFIC_VENDOR_WRITE_REQUEST, value, index, data); + } + + private final void ctrlOut(int request, int value, int index, byte[] data) + throws IOException { + outControlTransfer(PROLIFIC_CTRL_OUT_REQTYPE, request, value, index, + data); + } + + private void doBlackMagic() throws IOException { + vendorIn(0x8484, 0, 1); + vendorOut(0x0404, 0, null); + vendorIn(0x8484, 0, 1); + vendorIn(0x8383, 0, 1); + vendorIn(0x8484, 0, 1); + vendorOut(0x0404, 1, null); + vendorIn(0x8484, 0, 1); + vendorIn(0x8383, 0, 1); + vendorOut(0, 1, null); + vendorOut(1, 0, null); + vendorOut(2, (mDeviceType == DEVICE_TYPE_HX) ? 0x44 : 0x24, null); + } + + private void resetDevice() throws IOException { + purgeHwBuffers(true, true); + } + + private void setControlLines(int newControlLinesValue) throws IOException { + ctrlOut(SET_CONTROL_REQUEST, newControlLinesValue, 0, null); + mControlLinesValue = newControlLinesValue; + } + + private final void readStatusThreadFunction() { + try { + while (!mStopReadStatusThread) { + byte[] buffer = new byte[STATUS_BUFFER_SIZE]; + int readBytesCount = mConnection.bulkTransfer(mInterruptEndpoint, + buffer, + STATUS_BUFFER_SIZE, + 500); + if (readBytesCount > 0) { + if (readBytesCount == STATUS_BUFFER_SIZE) { + mStatus = buffer[STATUS_BYTE_IDX] & 0xff; + } else { + throw new IOException( + String.format("Invalid CTS / DSR / CD / RI status buffer received, expected %d bytes, but received %d", + STATUS_BUFFER_SIZE, + readBytesCount)); + } + } + } + } catch (IOException e) { + mReadStatusException = e; + } + } + + private final int getStatus() throws IOException { + if ((mReadStatusThread == null) && (mReadStatusException == null)) { + synchronized (mReadStatusThreadLock) { + if (mReadStatusThread == null) { + byte[] buffer = new byte[STATUS_BUFFER_SIZE]; + int readBytes = mConnection.bulkTransfer(mInterruptEndpoint, + buffer, + STATUS_BUFFER_SIZE, + 100); + if (readBytes != STATUS_BUFFER_SIZE) { + Log.w(TAG, "Could not read initial CTS / DSR / CD / RI status"); + } else { + mStatus = buffer[STATUS_BYTE_IDX] & 0xff; + } + + mReadStatusThread = new Thread(new Runnable() { + @Override + public void run() { + readStatusThreadFunction(); + } + }); + mReadStatusThread.setDaemon(true); + mReadStatusThread.start(); + } + } + } + + /* throw and clear an exception which occured in the status read thread */ + IOException readStatusException = mReadStatusException; + if (mReadStatusException != null) { + mReadStatusException = null; + throw readStatusException; + } + + return mStatus; + } + + private final boolean testStatusFlag(int flag) throws IOException { + return ((getStatus() & flag) == flag); + } + + public ProlificSerialDriver(UsbDevice device, UsbDeviceConnection connection) { + super(device, connection); + } + + @Override + public void open() throws IOException { + UsbInterface usbInterface = mDevice.getInterface(0); + + if (!mConnection.claimInterface(usbInterface, true)) { + throw new IOException("Error claiming Prolific interface 0"); + } + + boolean openSuccessful = false; + try { + for (int i = 0; i < usbInterface.getEndpointCount(); ++i) { + UsbEndpoint currentEndpoint = usbInterface.getEndpoint(i); + + switch (currentEndpoint.getAddress()) { + case READ_ENDPOINT: + mReadEndpoint = currentEndpoint; + break; + + case WRITE_ENDPOINT: + mWriteEndpoint = currentEndpoint; + break; + + case INTERRUPT_ENDPOINT: + mInterruptEndpoint = currentEndpoint; + break; + } + } + + if (mDevice.getDeviceClass() == 0x02) { + mDeviceType = DEVICE_TYPE_0; + } else { + try { + Method getRawDescriptorsMethod + = mConnection.getClass().getMethod("getRawDescriptors"); + byte[] rawDescriptors + = (byte[]) getRawDescriptorsMethod.invoke(mConnection); + byte maxPacketSize0 = rawDescriptors[7]; + if (maxPacketSize0 == 64) { + mDeviceType = DEVICE_TYPE_HX; + } else if ((mDevice.getDeviceClass() == 0x00) + || (mDevice.getDeviceClass() == 0xff)) { + mDeviceType = DEVICE_TYPE_1; + } else { + Log.w(TAG, "Could not detect PL2303 subtype, " + + "Assuming that it is a HX device"); + mDeviceType = DEVICE_TYPE_HX; + } + } catch (NoSuchMethodException e) { + Log.w(TAG, "Method UsbDeviceConnection.getRawDescriptors, " + + "required for PL2303 subtype detection, not " + + "available! Assuming that it is a HX device"); + mDeviceType = DEVICE_TYPE_HX; + } catch (Exception e) { + Log.e(TAG, "An unexpected exception occured while trying " + + "to detect PL2303 subtype", e); + } + } + + setControlLines(mControlLinesValue); + resetDevice(); + + doBlackMagic(); + openSuccessful = true; + } finally { + if (!openSuccessful) { + try { + mConnection.releaseInterface(usbInterface); + } catch (Exception ingored) { + // Do not cover possible exceptions + } + } + } + } + + @Override + public void close() throws IOException { + try { + mStopReadStatusThread = true; + synchronized (mReadStatusThreadLock) { + if (mReadStatusThread != null) { + try { + mReadStatusThread.join(); + } catch (Exception e) { + Log.w(TAG, "An error occured while waiting for status read thread", e); + } + } + } + + resetDevice(); + } finally { + mConnection.releaseInterface(mDevice.getInterface(0)); + } + } + + @Override + public int read(byte[] dest, int timeoutMillis) throws IOException { + synchronized (mReadBufferLock) { + int readAmt = Math.min(dest.length, mReadBuffer.length); + int numBytesRead = mConnection.bulkTransfer(mReadEndpoint, mReadBuffer, + readAmt, timeoutMillis); + if (numBytesRead < 0) { + return 0; + } + System.arraycopy(mReadBuffer, 0, dest, 0, numBytesRead); + return numBytesRead; + } + } + + @Override + public int write(byte[] src, int timeoutMillis) throws IOException { + int offset = 0; + + while (offset < src.length) { + final int writeLength; + final int amtWritten; + + synchronized (mWriteBufferLock) { + final byte[] writeBuffer; + + writeLength = Math.min(src.length - offset, mWriteBuffer.length); + if (offset == 0) { + writeBuffer = src; + } else { + // bulkTransfer does not support offsets, make a copy. + System.arraycopy(src, offset, mWriteBuffer, 0, writeLength); + writeBuffer = mWriteBuffer; + } + + amtWritten = mConnection.bulkTransfer(mWriteEndpoint, + writeBuffer, writeLength, timeoutMillis); + } + + if (amtWritten <= 0) { + throw new IOException("Error writing " + writeLength + + " bytes at offset " + offset + " length=" + + src.length); + } + + offset += amtWritten; + } + return offset; + } + + @Override + public void setParameters(int baudRate, int dataBits, int stopBits, + int parity) throws IOException { + if ((mBaudRate == baudRate) && (mDataBits == dataBits) + && (mStopBits == stopBits) && (mParity == parity)) { + // Make sure no action is performed if there is nothing to change + return; + } + + byte[] lineRequestData = new byte[7]; + + lineRequestData[0] = (byte) (baudRate & 0xff); + lineRequestData[1] = (byte) ((baudRate >> 8) & 0xff); + lineRequestData[2] = (byte) ((baudRate >> 16) & 0xff); + lineRequestData[3] = (byte) ((baudRate >> 24) & 0xff); + + switch (stopBits) { + case STOPBITS_1: + lineRequestData[4] = 0; + break; + + case STOPBITS_1_5: + lineRequestData[4] = 1; + break; + + case STOPBITS_2: + lineRequestData[4] = 2; + break; + + default: + throw new IllegalArgumentException("Unknown stopBits value: " + stopBits); + } + + switch (parity) { + case PARITY_NONE: + lineRequestData[5] = 0; + break; + + case PARITY_ODD: + lineRequestData[5] = 1; + break; + + case PARITY_EVEN: + lineRequestData[5] = 2; + break; + + case PARITY_MARK: + lineRequestData[5] = 3; + break; + + case PARITY_SPACE: + lineRequestData[5] = 4; + break; + + default: + throw new IllegalArgumentException("Unknown parity value: " + parity); + } + + lineRequestData[6] = (byte) dataBits; + + ctrlOut(SET_LINE_REQUEST, 0, 0, lineRequestData); + + resetDevice(); + + mBaudRate = baudRate; + mDataBits = dataBits; + mStopBits = stopBits; + mParity = parity; + } + + @Override + public boolean getCD() throws IOException { + return testStatusFlag(STATUS_FLAG_CD); + } + + @Override + public boolean getCTS() throws IOException { + return testStatusFlag(STATUS_FLAG_CTS); + } + + @Override + public boolean getDSR() throws IOException { + return testStatusFlag(STATUS_FLAG_DSR); + } + + @Override + public boolean getDTR() throws IOException { + return ((mControlLinesValue & CONTROL_DTR) == CONTROL_DTR); + } + + @Override + public void setDTR(boolean value) throws IOException { + int newControlLinesValue; + if (value) { + newControlLinesValue = mControlLinesValue | CONTROL_DTR; + } else { + newControlLinesValue = mControlLinesValue & ~CONTROL_DTR; + } + setControlLines(newControlLinesValue); + } + + @Override + public boolean getRI() throws IOException { + return testStatusFlag(STATUS_FLAG_RI); + } + + @Override + public boolean getRTS() throws IOException { + return ((mControlLinesValue & CONTROL_RTS) == CONTROL_RTS); + } + + @Override + public void setRTS(boolean value) throws IOException { + int newControlLinesValue; + if (value) { + newControlLinesValue = mControlLinesValue | CONTROL_RTS; + } else { + newControlLinesValue = mControlLinesValue & ~CONTROL_RTS; + } + setControlLines(newControlLinesValue); + } + + @Override + public boolean purgeHwBuffers(boolean purgeReadBuffers, boolean purgeWriteBuffers) throws IOException { + if (purgeReadBuffers) { + vendorOut(FLUSH_RX_REQUEST, 0, null); + } + + if (purgeWriteBuffers) { + vendorOut(FLUSH_TX_REQUEST, 0, null); + } + + return true; + } + + public static Map getSupportedDevices() { + final Map supportedDevices = new LinkedHashMap(); + supportedDevices.put(Integer.valueOf(UsbId.VENDOR_PROLIFIC), + new int[] { UsbId.PROLIFIC_PL2303, }); + return supportedDevices; + } +} + diff --git a/android/src/com/hoho/android/usbserial/driver/UsbId.java b/android/src/com/hoho/android/usbserial/driver/UsbId.java new file mode 100644 index 000000000..618fbc844 --- /dev/null +++ b/android/src/com/hoho/android/usbserial/driver/UsbId.java @@ -0,0 +1,69 @@ +/* Copyright 2012 Google Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * Project home page: http://code.google.com/p/usb-serial-for-android/ + */ +package com.hoho.android.usbserial.driver; + +/** + * Registry of USB vendor/product ID constants. + * + * Culled from various sources; see + * usb.ids for one listing. + * + * @author mike wakerly (opensource@hoho.com) + */ +public final class UsbId { + + public static final int VENDOR_FTDI = 0x0403; + public static final int FTDI_FT232R = 0x6001; + public static final int FTDI_FT231X = 0x6015; + + public static final int VENDOR_PX4 = 0x26AC; + public static final int DEVICE_PX4FMU = 0x11; + + public static final int VENDOR_ATMEL = 0x03EB; + public static final int ATMEL_LUFA_CDC_DEMO_APP = 0x2044; + + public static final int VENDOR_ARDUINO = 0x2341; + public static final int ARDUINO_UNO = 0x0001; + public static final int ARDUINO_MEGA_2560 = 0x0010; + public static final int ARDUINO_SERIAL_ADAPTER = 0x003b; + public static final int ARDUINO_MEGA_ADK = 0x003f; + public static final int ARDUINO_MEGA_2560_R3 = 0x0042; + public static final int ARDUINO_UNO_R3 = 0x0043; + public static final int ARDUINO_MEGA_ADK_R3 = 0x0044; + public static final int ARDUINO_SERIAL_ADAPTER_R3 = 0x0044; + public static final int ARDUINO_LEONARDO = 0x8036; + + public static final int VENDOR_VAN_OOIJEN_TECH = 0x16c0; + public static final int VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL = 0x0483; + + public static final int VENDOR_LEAFLABS = 0x1eaf; + public static final int LEAFLABS_MAPLE = 0x0004; + + public static final int VENDOR_SILAB = 0x10c4; + public static final int SILAB_CP2102 = 0xea60; + + public static final int VENDOR_PROLIFIC = 0x067b; + public static final int PROLIFIC_PL2303 = 0x2303; + + private UsbId() { + throw new IllegalAccessError("Non-instantiable class."); + } + +} diff --git a/android/src/com/hoho/android/usbserial/driver/UsbSerialDriver.java b/android/src/com/hoho/android/usbserial/driver/UsbSerialDriver.java new file mode 100644 index 000000000..ed4426fc4 --- /dev/null +++ b/android/src/com/hoho/android/usbserial/driver/UsbSerialDriver.java @@ -0,0 +1,228 @@ +/* Copyright 2011 Google Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * Project home page: http://code.google.com/p/usb-serial-for-android/ + */ + +package com.hoho.android.usbserial.driver; + +import java.io.IOException; + +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; + +/** + * Driver interface for a USB serial device. + * + * @author mike wakerly (opensource@hoho.com) + */ +public interface UsbSerialDriver { + + /** 5 data bits. */ + public static final int DATABITS_5 = 5; + + /** 6 data bits. */ + public static final int DATABITS_6 = 6; + + /** 7 data bits. */ + public static final int DATABITS_7 = 7; + + /** 8 data bits. */ + public static final int DATABITS_8 = 8; + + /** No flow control. */ + public static final int FLOWCONTROL_NONE = 0; + + /** RTS/CTS input flow control. */ + public static final int FLOWCONTROL_RTSCTS_IN = 1; + + /** RTS/CTS output flow control. */ + public static final int FLOWCONTROL_RTSCTS_OUT = 2; + + /** XON/XOFF input flow control. */ + public static final int FLOWCONTROL_XONXOFF_IN = 4; + + /** XON/XOFF output flow control. */ + public static final int FLOWCONTROL_XONXOFF_OUT = 8; + + /** No parity. */ + public static final int PARITY_NONE = 0; + + /** Odd parity. */ + public static final int PARITY_ODD = 1; + + /** Even parity. */ + public static final int PARITY_EVEN = 2; + + /** Mark parity. */ + public static final int PARITY_MARK = 3; + + /** Space parity. */ + public static final int PARITY_SPACE = 4; + + /** 1 stop bit. */ + public static final int STOPBITS_1 = 1; + + /** 1.5 stop bits. */ + public static final int STOPBITS_1_5 = 3; + + /** 2 stop bits. */ + public static final int STOPBITS_2 = 2; + + /** + * Returns the currently-bound USB device. + * + * @return the device + */ + public UsbDevice getDevice(); + + + /** + * Returns the currently-bound USB device. + * + * @return the device + */ + public UsbDeviceConnection getDeviceConnection(); + + /** + * Opens and initializes the device as a USB serial device. Upon success, + * caller must ensure that {@link #close()} is eventually called. + * + * @throws IOException on error opening or initializing the device. + */ + public void open() throws IOException; + + /** + * Closes the serial device. + * + * @throws IOException on error closing the device. + */ + public void close() throws IOException; + + /** + * Reads as many bytes as possible into the destination buffer. + * + * @param dest the destination byte buffer + * @param timeoutMillis the timeout for reading + * @return the actual number of bytes read + * @throws IOException if an error occurred during reading + */ + public int read(final byte[] dest, final int timeoutMillis) throws IOException; + + /** + * Writes as many bytes as possible from the source buffer. + * + * @param src the source byte buffer + * @param timeoutMillis the timeout for writing + * @return the actual number of bytes written + * @throws IOException if an error occurred during writing + */ + public int write(final byte[] src, final int timeoutMillis) throws IOException; + + /** + * Sets various serial port parameters. + * + * @param baudRate baud rate as an integer, for example {@code 115200}. + * @param dataBits one of {@link #DATABITS_5}, {@link #DATABITS_6}, + * {@link #DATABITS_7}, or {@link #DATABITS_8}. + * @param stopBits one of {@link #STOPBITS_1}, {@link #STOPBITS_1_5}, or + * {@link #STOPBITS_2}. + * @param parity one of {@link #PARITY_NONE}, {@link #PARITY_ODD}, + * {@link #PARITY_EVEN}, {@link #PARITY_MARK}, or + * {@link #PARITY_SPACE}. + * @throws IOException on error setting the port parameters + */ + public void setParameters( + int baudRate, int dataBits, int stopBits, int parity) throws IOException; + + /** + * Gets the CD (Carrier Detect) bit from the underlying UART. + * + * @return the current state, or {@code false} if not supported. + * @throws IOException if an error occurred during reading + */ + public boolean getCD() throws IOException; + + /** + * Gets the CTS (Clear To Send) bit from the underlying UART. + * + * @return the current state, or {@code false} if not supported. + * @throws IOException if an error occurred during reading + */ + public boolean getCTS() throws IOException; + + /** + * Gets the DSR (Data Set Ready) bit from the underlying UART. + * + * @return the current state, or {@code false} if not supported. + * @throws IOException if an error occurred during reading + */ + public boolean getDSR() throws IOException; + + /** + * Gets the DTR (Data Terminal Ready) bit from the underlying UART. + * + * @return the current state, or {@code false} if not supported. + * @throws IOException if an error occurred during reading + */ + public boolean getDTR() throws IOException; + + /** + * Sets the DTR (Data Terminal Ready) bit on the underlying UART, if + * supported. + * + * @param value the value to set + * @throws IOException if an error occurred during writing + */ + public void setDTR(boolean value) throws IOException; + + /** + * Gets the RI (Ring Indicator) bit from the underlying UART. + * + * @return the current state, or {@code false} if not supported. + * @throws IOException if an error occurred during reading + */ + public boolean getRI() throws IOException; + + /** + * Gets the RTS (Request To Send) bit from the underlying UART. + * + * @return the current state, or {@code false} if not supported. + * @throws IOException if an error occurred during reading + */ + public boolean getRTS() throws IOException; + + /** + * Sets the RTS (Request To Send) bit on the underlying UART, if + * supported. + * + * @param value the value to set + * @throws IOException if an error occurred during writing + */ + public void setRTS(boolean value) throws IOException; + + /** + * Flush non-transmitted output data and / or non-read input data + * @param flushRX {@code true} to flush non-transmitted output data + * @param flushTX {@code true} to flush non-read input data + * @return {@code true} if the operation was successful, or + * {@code false} if the operation is not supported by the driver or device + * @throws IOException if an error occurred during flush + */ + public boolean purgeHwBuffers(boolean flushRX, boolean flushTX) throws IOException; + +} diff --git a/android/src/com/hoho/android/usbserial/driver/UsbSerialProber.java b/android/src/com/hoho/android/usbserial/driver/UsbSerialProber.java new file mode 100644 index 000000000..0cfb366e4 --- /dev/null +++ b/android/src/com/hoho/android/usbserial/driver/UsbSerialProber.java @@ -0,0 +1,250 @@ +/* Copyright 2011 Google Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * Project home page: http://code.google.com/p/usb-serial-for-android/ + */ + +package com.hoho.android.usbserial.driver; + +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbManager; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * Helper class which finds compatible {@link UsbDevice}s and creates + * {@link UsbSerialDriver} instances. + * + *

+ * You don't need a Prober to use the rest of the library: it is perfectly + * acceptable to instantiate driver instances manually. The Prober simply + * provides convenience functions. + * + *

+ * For most drivers, the corresponding {@link #probe(UsbManager, UsbDevice)} + * method will either return an empty list (device unknown / unsupported) or a + * singleton list. However, multi-port drivers may return multiple instances. + * + * @author mike wakerly (opensource@hoho.com) + */ +public enum UsbSerialProber { + + // TODO(mikey): Too much boilerplate. + + /** + * Prober for {@link FtdiSerialDriver}. + * + * @see FtdiSerialDriver + */ + FTDI_SERIAL { + @Override + public List probe(final UsbManager manager, final UsbDevice usbDevice) { + if (!testIfSupported(usbDevice, FtdiSerialDriver.getSupportedDevices())) { + return Collections.emptyList(); + } + final UsbDeviceConnection connection = manager.openDevice(usbDevice); + if (connection == null) { + return Collections.emptyList(); + } + final UsbSerialDriver driver = new FtdiSerialDriver(usbDevice, connection); + return Collections.singletonList(driver); + } + }, + + CDC_ACM_SERIAL { + @Override + public List probe(UsbManager manager, UsbDevice usbDevice) { + if (!testIfSupported(usbDevice, CdcAcmSerialDriver.getSupportedDevices())) { + return Collections.emptyList(); + } + final UsbDeviceConnection connection = manager.openDevice(usbDevice); + if (connection == null) { + return Collections.emptyList(); + } + final UsbSerialDriver driver = new CdcAcmSerialDriver(usbDevice, connection); + return Collections.singletonList(driver); + } + }, + + SILAB_SERIAL { + @Override + public List probe(final UsbManager manager, final UsbDevice usbDevice) { + if (!testIfSupported(usbDevice, Cp2102SerialDriver.getSupportedDevices())) { + return Collections.emptyList(); + } + final UsbDeviceConnection connection = manager.openDevice(usbDevice); + if (connection == null) { + return Collections.emptyList(); + } + final UsbSerialDriver driver = new Cp2102SerialDriver(usbDevice, connection); + return Collections.singletonList(driver); + } + }, + + PROLIFIC_SERIAL { + @Override + public List probe(final UsbManager manager, final UsbDevice usbDevice) { + if (!testIfSupported(usbDevice, ProlificSerialDriver.getSupportedDevices())) { + return Collections.emptyList(); + } + final UsbDeviceConnection connection = manager.openDevice(usbDevice); + if (connection == null) { + return Collections.emptyList(); + } + final UsbSerialDriver driver = new ProlificSerialDriver(usbDevice, connection); + return Collections.singletonList(driver); + } + }; + + /** + * Tests the supplied {@link UsbDevice} for compatibility with this enum + * member, returning one or more driver instances if compatible. + * + * @param manager the {@link UsbManager} to use + * @param usbDevice the raw {@link UsbDevice} to use + * @return zero or more {@link UsbSerialDriver}, depending on compatibility + * (never {@code null}). + */ + protected abstract List probe(final UsbManager manager, final UsbDevice usbDevice); + + /** + * Creates and returns a new {@link UsbSerialDriver} instance for the first + * compatible {@link UsbDevice} found on the bus. If none are found, + * returns {@code null}. + * + *

+ * The order of devices is undefined, therefore if there are multiple + * devices on the bus, the chosen device may not be predictable (clients + * should use {@link #findAllDevices(UsbManager)} instead). + * + * @param usbManager the {@link UsbManager} to use. + * @return the first available {@link UsbSerialDriver}, or {@code null} if + * none are available. + */ + public static UsbSerialDriver findFirstDevice(final UsbManager usbManager) { + for (final UsbDevice usbDevice : usbManager.getDeviceList().values()) { + for (final UsbSerialProber prober : values()) { + final List probedDevices = prober.probe(usbManager, usbDevice); + if (!probedDevices.isEmpty()) { + return probedDevices.get(0); + } + } + } + return null; + } + + /** + * Creates a new {@link UsbSerialDriver} instance for all compatible + * {@link UsbDevice}s found on the bus. If no compatible devices are found, + * the list will be empty. + * + * @param usbManager + * @return + */ + public static List findAllDevices(final UsbManager usbManager) { + final List result = new ArrayList(); + Log.i("QGC_UsbSerialProber", "Looking for USB devices"); + // For each UsbDevice, call probe() for each prober. + for (final UsbDevice usbDevice : usbManager.getDeviceList().values()) { + Log.i("QGC_UsbSerialProber", "Probing device: " + usbDevice.getDeviceName() + " mid: " + usbDevice.getVendorId() + " pid: " + usbDevice.getDeviceId()); + result.addAll(probeSingleDevice(usbManager, usbDevice)); + } + return result; + } + + /** + * Special method for testing a specific device for driver support, + * returning any compatible driver(s). + * + *

+ * Clients should ordinarily use {@link #findAllDevices(UsbManager)}, which + * operates against the entire bus of devices. This method is useful when + * testing against only a single target is desired. + * + * @param usbManager the {@link UsbManager} to use. + * @param usbDevice the device to test against. + * @return a list containing zero or more {@link UsbSerialDriver} instances. + */ + public static List probeSingleDevice(final UsbManager usbManager, + UsbDevice usbDevice) + { + final List result = new ArrayList(); + for (final UsbSerialProber prober : values()) { + final List probedDevices = prober.probe(usbManager, usbDevice); + result.addAll(probedDevices); + } + return result; + } + + /** + * Deprecated; Use {@link #findFirstDevice(UsbManager)}. + * + * @param usbManager + * @return + */ + @Deprecated + public static UsbSerialDriver acquire(final UsbManager usbManager) { + return findFirstDevice(usbManager); + } + + /** + * Deprecated; use {@link #probeSingleDevice(UsbManager, UsbDevice)}. + * + * @param usbManager + * @param usbDevice + * @return + */ + @Deprecated + public static UsbSerialDriver acquire(final UsbManager usbManager, final UsbDevice usbDevice) { + final List probedDevices = probeSingleDevice(usbManager, usbDevice); + if (!probedDevices.isEmpty()) { + return probedDevices.get(0); + } + return null; + } + + /** + * Returns {@code true} if the given device is found in the driver's + * vendor/product map. + * + * @param usbDevice the device to test + * @param supportedDevices map of vendor IDs to product ID(s) + * @return {@code true} if supported + */ + private static boolean testIfSupported(final UsbDevice usbDevice, + final Map supportedDevices) { + final int[] supportedProducts = supportedDevices.get( + Integer.valueOf(usbDevice.getVendorId())); + if (supportedProducts == null) { + return false; + } + + final int productId = usbDevice.getProductId(); + for (int supportedProductId : supportedProducts) { + if (productId == supportedProductId) { + return true; + } + } + return false; + } + +} diff --git a/android/src/com/hoho/android/usbserial/driver/UsbSerialRuntimeException.java b/android/src/com/hoho/android/usbserial/driver/UsbSerialRuntimeException.java new file mode 100644 index 000000000..b48607c59 --- /dev/null +++ b/android/src/com/hoho/android/usbserial/driver/UsbSerialRuntimeException.java @@ -0,0 +1,46 @@ +/* + * Copyright 2011 Google Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +package com.hoho.android.usbserial.driver; + +/** + * Generic unchecked exception for the usbserial package. + * + * @author mike wakerly (opensource@hoho.com) + */ +@SuppressWarnings("serial") +public class UsbSerialRuntimeException extends RuntimeException { + + public UsbSerialRuntimeException() { + super(); + } + + public UsbSerialRuntimeException(String detailMessage, Throwable throwable) { + super(detailMessage, throwable); + } + + public UsbSerialRuntimeException(String detailMessage) { + super(detailMessage); + } + + public UsbSerialRuntimeException(Throwable throwable) { + super(throwable); + } + +} diff --git a/android/src/org/qgroundcontrol/qgchelper/UsbDeviceJNI.java b/android/src/org/qgroundcontrol/qgchelper/UsbDeviceJNI.java new file mode 100644 index 000000000..79d2597a1 --- /dev/null +++ b/android/src/org/qgroundcontrol/qgchelper/UsbDeviceJNI.java @@ -0,0 +1,637 @@ +package org.qgroundcontrol.qgchelper; + +/* Copyright 2013 Google Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * Project home page: http://code.google.com/p/usb-serial-for-android/ + */ +/////////////////////////////////////////////////////////////////////////////////////////// +// Written by: Mike Goza April 2014 +// +// These routines interface with the Android USB Host devices for serial port communication. +// The code uses the usb-serial-for-android software library. The UsbDeviceJNI class is the +// interface to the C++ routines through jni calls. Do not change the functions without also +// changing the corresponding calls in the C++ routines or you will break the interface. +// +//////////////////////////////////////////////////////////////////////////////////////////// + +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.io.IOException; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.hardware.usb.*; +import android.widget.Toast; +import android.util.Log; + +import com.hoho.android.usbserial.driver.*; +import org.qtproject.qt5.android.bindings.QtActivity; +import org.qtproject.qt5.android.bindings.QtApplication; + +public class UsbDeviceJNI extends QtActivity +{ + public static int BAD_PORT = 0; + private static UsbDeviceJNI m_instance; + private static UsbManager m_manager; // ANDROID USB HOST CLASS + private static List m_devices; // LIST OF CURRENT DEVICES + private static HashMap m_openedDevices; // LIST OF OPENED DEVICES + private static HashMap m_ioManager; // THREADS FOR LISTENING FOR INCOMING DATA + private static HashMap m_userData; // CORRESPONDING USER DATA FOR OPENED DEVICES. USED IN DISCONNECT CALLBACK + // USED TO DETECT WHEN A DEVICE HAS BEEN UNPLUGGED + private BroadcastReceiver m_UsbReceiver = null; + private final static ExecutorService m_Executor = Executors.newSingleThreadExecutor(); + + private final static UsbIoManager.Listener m_Listener = + new UsbIoManager.Listener() + { + @Override + public void onRunError(Exception eA, int userDataA) + { + nativeDeviceException(userDataA, eA.getMessage()); + } + + @Override + public void onNewData(final byte[] dataA, int userDataA) + { + nativeDeviceNewData(userDataA, dataA); + } + }; + + private static final String TAG = "QGC_UsbDeviceJNI"; + + // NATIVE C++ FUNCTION THAT WILL BE CALLED IF THE DEVICE IS UNPLUGGED + private static native void nativeDeviceHasDisconnected(int userDataA); + private static native void nativeDeviceException(int userDataA, String messageA); + private static native void nativeDeviceNewData(int userDataA, byte[] dataA); + + //////////////////////////////////////////////////////////////////////////////////////////////// + // + // Constructor. Only used once to create the initial instance for the static functions. + // + //////////////////////////////////////////////////////////////////////////////////////////////// + public UsbDeviceJNI() + { + m_instance = this; + m_openedDevices = new HashMap(); + m_userData = new HashMap(); + m_ioManager = new HashMap(); + Log.i(TAG, "Instance created"); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////// + // + // Find all current devices that match the device filter described in the androidmanifest.xml and the + // device_filter.xml + // + ///////////////////////////////////////////////////////////////////////////////////////////////////////// + private static boolean getCurrentDevices() + { + if (m_instance == null) + return false; + + if (m_manager == null) + m_manager = (UsbManager)m_instance.getSystemService(Context.USB_SERVICE); + + if (m_devices != null) + m_devices.clear(); + + m_devices = UsbSerialProber.findAllDevices(m_manager); + + return true; + } + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////// + // + // List all available devices that are not already open. It returns the serial port info + // in a : separated string array. Each string entry consists of the following: + // + // DeviceName:Company:ProductId:VendorId + // + ///////////////////////////////////////////////////////////////////////////////////////////////////////// + public static String[] availableDevicesInfo() + { + // GET THE LIST OF CURRENT DEVICES + if (!getCurrentDevices()) + { + Log.e(TAG, "UsbDeviceJNI instance not present"); + return null; + } + + // MAKE SURE WE HAVE ENTRIES + if (m_devices.size() <= 0) + { + //Log.e(TAG, "No USB devices found"); + return null; + } + + if (m_openedDevices == null) + { + Log.e(TAG, "m_openedDevices is null"); + return null; + } + + int countL = 0; + int iL; + + // CHECK FOR ALREADY OPENED DEVICES AND DON"T INCLUDE THEM IN THE COUNT + for (iL=0; iL 0) + { + final Listener listener = getListener(); + if (listener != null) + { + final byte[] data = new byte[len]; + mReadBuffer.get(data, 0, len); + listener.onNewData(data, mUserData); + } + mReadBuffer.clear(); + } +/* + // Handle outgoing data. + byte[] outBuff = null; + synchronized (mWriteBuffer) + { + if (mWriteBuffer.position() > 0) + { + len = mWriteBuffer.position(); + outBuff = new byte[len]; + mWriteBuffer.rewind(); + mWriteBuffer.get(outBuff, 0, len); + mWriteBuffer.clear(); + } + } + if (outBuff != null) + mDriver.write(outBuff, READ_WAIT_MILLIS); +*/ + } +} + diff --git a/libs/qextserialport/src/qextserialport_unix.cpp b/libs/qextserialport/src/qextserialport_unix.cpp index 97d7ac1a3..78c486568 100755 --- a/libs/qextserialport/src/qextserialport_unix.cpp +++ b/libs/qextserialport/src/qextserialport_unix.cpp @@ -120,7 +120,11 @@ bool QextSerialPortPrivate::close_sys() bool QextSerialPortPrivate::flush_sys() { +#ifdef __android__ + ::ioctl(fd, TCSADRAIN, ¤tTermios); +#else ::tcdrain(fd); +#endif return true; } diff --git a/libs/qtandroidserialport/src/qserialport.cpp b/libs/qtandroidserialport/src/qserialport.cpp new file mode 100644 index 000000000..992f06bfc --- /dev/null +++ b/libs/qtandroidserialport/src/qserialport.cpp @@ -0,0 +1,1375 @@ +/**************************************************************************** +** +** Copyright (C) 2011-2012 Denis Shienkov +** Copyright (C) 2011 Sergey Belyashov +** Copyright (C) 2012 Laszlo Papp +** Copyright (C) 2012 Andre Hartmann +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtSerialPort module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qserialport.h" +#include "qserialportinfo.h" +#include "qserialportinfo_p.h" + +#ifdef Q_OS_WINCE +#include "qserialport_wince_p.h" +#elif defined (Q_OS_WIN) +#include "qserialport_win_p.h" +#elif defined (Q_OS_SYMBIAN) +#include "qserialport_symbian_p.h" +#elif defined (Q_OS_ANDROID) +#include "qserialport_android_p.h" +#elif defined (Q_OS_UNIX) +#include "qserialport_unix_p.h" +#else +#error Unsupported OS +#endif + +#ifndef SERIALPORT_BUFFERSIZE +# define SERIALPORT_BUFFERSIZE 16384 +#endif + +#include + +QT_BEGIN_NAMESPACE + +QSerialPortPrivateData::QSerialPortPrivateData(QSerialPort *q) + : readBufferMaxSize(0) + , readBuffer(SERIALPORT_BUFFERSIZE) + , writeBuffer(SERIALPORT_BUFFERSIZE) + , error(QSerialPort::NoError) + , inputBaudRate(9600) + , outputBaudRate(9600) + , dataBits(QSerialPort::Data8) + , parity(QSerialPort::NoParity) + , stopBits(QSerialPort::OneStop) + , flowControl(QSerialPort::NoFlowControl) + , policy(QSerialPort::IgnorePolicy) +#if QT_DEPRECATED_SINCE(5,3) + , settingsRestoredOnClose(true) +#endif + , q_ptr(q) +{ +} + +int QSerialPortPrivateData::timeoutValue(int msecs, int elapsed) +{ + if (msecs == -1) + return msecs; + msecs -= elapsed; + return qMax(msecs, 0); +} + +/*! + \class QSerialPort + + \brief Provides functions to access serial ports. + + \reentrant + \ingroup serialport-main + \inmodule QtSerialPort + \since 5.1 + + You can get information about the available serial ports using the + QSerialPortInfo helper class, which allows an enumeration of all the serial + ports in the system. This is useful to obtain the correct name of the + serial port you want to use. You can pass an object + of the helper class as an argument to the setPort() or setPortName() + methods to assign the desired serial device. + + After setting the port, you can open it in read-only (r/o), write-only + (w/o), or read-write (r/w) mode using the open() method. + + \note The serial port is always opened with exclusive access + (that is, no other process or thread can access an already opened serial port). + + Having successfully opened, QSerialPort tries to determine the current + configuration of the port and initializes itself. You can reconfigure the + port to the desired setting using the setBaudRate(), setDataBits(), + setParity(), setStopBits(), and setFlowControl() methods. + + There are a couple of properties to work with the pinout signals namely: + QSerialPort::dataTerminalReady, QSerialPort::requestToSend. It is also + possible to use the pinoutSignals() method to query the current pinout + signals set. + + Once you know that the ports are ready to read or write, you can + use the read() or write() methods. Alternatively the + readLine() and readAll() convenience methods can also be invoked. + If not all the data is read at once, the remaining data will + be available for later as new incoming data is appended to the + QSerialPort's internal read buffer. You can limit the size of the read + buffer using setReadBufferSize(). + + Use the close() method to close the port and cancel the I/O operations. + + See the following example: + + \code + int numRead = 0, numReadTotal = 0; + char buffer[50]; + + forever { + numRead = serial.read(buffer, 50); + + // Do whatever with the array + + numReadTotal += numRead; + if (numRead == 0 && !serial.waitForReadyRead()) + break; + } + \endcode + + If \l{QIODevice::}{waitForReadyRead()} returns false, the + connection has been closed or an error has occurred. + + Programming with a blocking serial port is radically different from + programming with a non-blocking serial port. A blocking serial port + does not require an event loop and typically leads to simpler code. + However, in a GUI application, blocking serial port should only be + used in non-GUI threads, to avoid freezing the user interface. + + For more details about these approaches, refer to the + \l {Examples}{example} applications. + + The QSerialPort class can also be used with QTextStream and QDataStream's + stream operators (operator<<() and operator>>()). There is one issue to be + aware of, though: make sure that enough data is available before attempting + to read by using the operator>>() overloaded operator. + + \sa QSerialPortInfo +*/ + +/*! + \enum QSerialPort::Direction + + This enum describes the possible directions of the data transmission. + + \note This enumeration is used for setting the baud rate of the device + separately for each direction on some operating systems (for example, + POSIX-like). + + \value Input Input direction. + \value Output Output direction. + \value AllDirections Simultaneously in two directions. +*/ + +/*! + \enum QSerialPort::BaudRate + + This enum describes the baud rate which the communication device operates + with. + + \note Only the most common standard baud rates are listed in this enum. + + \value Baud1200 1200 baud. + \value Baud2400 2400 baud. + \value Baud4800 4800 baud. + \value Baud9600 9600 baud. + \value Baud19200 19200 baud. + \value Baud38400 38400 baud. + \value Baud57600 57600 baud. + \value Baud115200 115200 baud. + \value UnknownBaud Unknown baud. This value is obsolete. It is provided to + keep old source code working. We strongly advise against + using it in new code. + + \sa QSerialPort::baudRate +*/ + +/*! + \enum QSerialPort::DataBits + + This enum describes the number of data bits used. + + \value Data5 The number of data bits in each character is 5. It + is used for Baudot code. It generally only makes + sense with older equipment such as teleprinters. + \value Data6 The number of data bits in each character is 6. It + is rarely used. + \value Data7 The number of data bits in each character is 7. It + is used for true ASCII. It generally only makes + sense with older equipment such as teleprinters. + \value Data8 The number of data bits in each character is 8. It + is used for most kinds of data, as this size matches + the size of a byte. It is almost universally used in + newer applications. + \value UnknownDataBits Unknown number of bits. This value is obsolete. It + is provided to keep old source code working. We + strongly advise against using it in new code. + + \sa QSerialPort::dataBits +*/ + +/*! + \enum QSerialPort::Parity + + This enum describes the parity scheme used. + + \value NoParity No parity bit it sent. This is the most common + parity setting. Error detection is handled by the + communication protocol. + \value EvenParity The number of 1 bits in each character, including + the parity bit, is always even. + \value OddParity The number of 1 bits in each character, including + the parity bit, is always odd. It ensures that at + least one state transition occurs in each character. + \value SpaceParity Space parity. The parity bit is sent in the space + signal condition. It does not provide error + detection information. + \value MarkParity Mark parity. The parity bit is always set to the + mark signal condition (logical 1). It does not + provide error detection information. + \value UnknownParity Unknown parity. This value is obsolete. It is + provided to keep old source code working. We + strongly advise against using it in new code. + + \sa QSerialPort::parity +*/ + +/*! + \enum QSerialPort::StopBits + + This enum describes the number of stop bits used. + + \value OneStop 1 stop bit. + \value OneAndHalfStop 1.5 stop bits. This is only for the Windows platform. + \value TwoStop 2 stop bits. + \value UnknownStopBits Unknown number of stop bits. This value is obsolete. + It is provided to keep old source code working. We + strongly advise against using it in new code. + + \sa QSerialPort::stopBits +*/ + +/*! + \enum QSerialPort::FlowControl + + This enum describes the flow control used. + + \value NoFlowControl No flow control. + \value HardwareControl Hardware flow control (RTS/CTS). + \value SoftwareControl Software flow control (XON/XOFF). + \value UnknownFlowControl Unknown flow control. This value is obsolete. It + is provided to keep old source code working. We + strongly advise against using it in new code. + + \sa QSerialPort::flowControl +*/ + +/*! + \enum QSerialPort::PinoutSignal + + This enum describes the possible RS-232 pinout signals. + + \value NoSignal No line active + \value TransmittedDataSignal TxD (Transmitted Data). This value is + obsolete. It is provided to keep old + source code working. We strongly + advise against using it in new code. + \value ReceivedDataSignal RxD (Received Data). This value is + obsolete. It is provided to keep old + source code working. We strongly + advise against using it in new code. + \value DataTerminalReadySignal DTR (Data Terminal Ready). + \value DataCarrierDetectSignal DCD (Data Carrier Detect). + \value DataSetReadySignal DSR (Data Set Ready). + \value RingIndicatorSignal RNG (Ring Indicator). + \value RequestToSendSignal RTS (Request To Send). + \value ClearToSendSignal CTS (Clear To Send). + \value SecondaryTransmittedDataSignal STD (Secondary Transmitted Data). + \value SecondaryReceivedDataSignal SRD (Secondary Received Data). + + \sa pinoutSignals(), QSerialPort::dataTerminalReady, + QSerialPort::requestToSend +*/ + +/*! + \enum QSerialPort::DataErrorPolicy + \obsolete + + This enum describes the policies for the received symbols + while parity errors were detected. + + \value SkipPolicy Skips the bad character. + \value PassZeroPolicy Replaces bad character with zero. + \value IgnorePolicy Ignores the error for a bad character. + \value StopReceivingPolicy Stops data reception on error. + \value UnknownPolicy Unknown policy. + + \sa QSerialPort::dataErrorPolicy +*/ + +/*! + \enum QSerialPort::SerialPortError + + This enum describes the errors that may be contained by the + QSerialPort::error property. + + \value NoError No error occurred. + + \value DeviceNotFoundError An error occurred while attempting to + open an non-existing device. + + \value PermissionError An error occurred while attempting to + open an already opened device by another + process or a user not having enough permission + and credentials to open. + + \value OpenError An error occurred while attempting to open an + already opened device in this object. + + \value NotOpenError This error occurs when an operation is executed + that can only be successfully performed if the + device is open. This value was introduced in + QtSerialPort 5.2. + + \value ParityError Parity error detected by the hardware while + reading data. + + \value FramingError Framing error detected by the hardware while + reading data. + + \value BreakConditionError Break condition detected by the hardware on + the input line. + + \value WriteError An I/O error occurred while writing the data. + + \value ReadError An I/O error occurred while reading the data. + + \value ResourceError An I/O error occurred when a resource becomes + unavailable, e.g. when the device is + unexpectedly removed from the system. + + \value UnsupportedOperationError The requested device operation is not + supported or prohibited by the running operating + system. + + \value TimeoutError A timeout error occurred. This value was + introduced in QtSerialPort 5.2. + + \value UnknownError An unidentified error occurred. + \sa QSerialPort::error +*/ + + + +/*! + Constructs a new serial port object with the given \a parent. +*/ +QSerialPort::QSerialPort(QObject *parent) + : QIODevice(parent) + , d_ptr(new QSerialPortPrivate(this)) +{} + +/*! + Constructs a new serial port object with the given \a parent + to represent the serial port with the specified \a name. + + The name should have a specific format; see the setPort() method. +*/ +QSerialPort::QSerialPort(const QString &name, QObject *parent) + : QIODevice(parent) + , d_ptr(new QSerialPortPrivate(this)) +{ + setPortName(name); +} + +/*! + Constructs a new serial port object with the given \a parent + to represent the serial port with the specified helper class + \a serialPortInfo. +*/ +QSerialPort::QSerialPort(const QSerialPortInfo &serialPortInfo, QObject *parent) + : QIODevice(parent) + , d_ptr(new QSerialPortPrivate(this)) +{ + setPort(serialPortInfo); +} + +/*! + Closes the serial port, if necessary, and then destroys object. +*/ +QSerialPort::~QSerialPort() +{ + /**/ + if (isOpen()) + close(); + delete d_ptr; +} + +/*! + Sets the \a name of the serial port. + + The name of the serial port can be passed as either a short name or + the long system location if necessary. + + \sa portName(), QSerialPortInfo +*/ +void QSerialPort::setPortName(const QString &name) +{ + Q_D(QSerialPort); + d->systemLocation = QSerialPortInfoPrivate::portNameToSystemLocation(name); +} + +/*! + Sets the port stored in the serial port info instance \a serialPortInfo. + + \sa portName(), QSerialPortInfo +*/ +void QSerialPort::setPort(const QSerialPortInfo &serialPortInfo) +{ + Q_D(QSerialPort); + d->systemLocation = serialPortInfo.systemLocation(); +} + +/*! + Returns the name set by setPort() or passed to the QSerialPort constructor. + This name is short, i.e. it is extracted and converted from the internal + variable system location of the device. The conversion algorithm is + platform specific: + \table + \header + \li Platform + \li Brief Description + \row + \li Windows + \li Removes the prefix "\\\\.\\" or "//./" from the system location + and returns the remainder of the string. + \row + \li Windows CE + \li Removes the suffix ":" from the system location + and returns the remainder of the string. + \row + \li Symbian + \li Returns the system location as it is, + as it is equivalent to the port name. + \row + \li Unix, BSD + \li Removes the prefix "/dev/" from the system location + and returns the remainder of the string. + \endtable + + \sa setPort(), QSerialPortInfo::portName() +*/ +QString QSerialPort::portName() const +{ + Q_D(const QSerialPort); + return QSerialPortInfoPrivate::portNameFromSystemLocation(d->systemLocation); +} + +/*! + \reimp + + Opens the serial port using OpenMode \a mode, and then returns true if + successful; otherwise returns false and sets an error code which can be + obtained by calling the error() method. + + \note The method returns false if opening the port is successful, but could + not set any of the port settings successfully. In that case, the port is + closed automatically not to leave the port around with incorrect settings. + + \warning The \a mode has to be QIODevice::ReadOnly, QIODevice::WriteOnly, + or QIODevice::ReadWrite. Other modes are unsupported. + + \sa QIODevice::OpenMode, setPort() +*/ +bool QSerialPort::open(OpenMode mode) +{ + Q_D(QSerialPort); + + if (isOpen()) { + setError(QSerialPort::OpenError); + return false; + } + + // Define while not supported modes. + static const OpenMode unsupportedModes = Append | Truncate | Text | Unbuffered; + if ((mode & unsupportedModes) || mode == NotOpen) { + setError(QSerialPort::UnsupportedOperationError); + return false; + } + + clearError(); + if (!d->open(mode)) + return false; + + if (!d->setBaudRate() + || !d->setDataBits(d->dataBits) + || !d->setParity(d->parity) + || !d->setStopBits(d->stopBits) + || !d->setFlowControl(d->flowControl)) { + d->close(); + return false; + } + + QIODevice::open(mode); + return true; +} + +/*! + \reimp + + \note The serial port has to be open before trying to close it; otherwise + sets the NotOpenError error code. + + \sa QIODevice::close() +*/ +void QSerialPort::close() +{ + Q_D(QSerialPort); + if (!isOpen()) { + setError(QSerialPort::NotOpenError); + return; + } + + QIODevice::close(); + d->close(); +} + +/*! + \property QSerialPort::settingsRestoredOnClose + \brief the flag which specifies to restore the previous settings when closing + the serial port. + \obsolete + + If this flag is true, the settings will be restored; otherwise not. + The default state of the QSerialPort class is to restore the + settings. +*/ +#if QT_DEPRECATED_SINCE(5,3) +void QSerialPort::setSettingsRestoredOnClose(bool restore) +{ + Q_D(QSerialPort); + + if (d->settingsRestoredOnClose != restore) { + d->settingsRestoredOnClose = restore; + emit settingsRestoredOnCloseChanged(d->settingsRestoredOnClose); + } +} + +bool QSerialPort::settingsRestoredOnClose() const +{ + Q_D(const QSerialPort); + return d->settingsRestoredOnClose; +} +#endif // QT_DEPRECATED_SINCE(5,3) +/*! + \fn void QSerialPort::settingsRestoredOnCloseChanged(bool restore) + \obsolete + + This signal is emitted after the flag which specifies to restore the + previous settings while closing the serial port has been changed. The new + flag which specifies to restore the previous settings while closing the serial + port is passed as \a restore. + + \sa QSerialPort::settingsRestoredOnClose +*/ + +/*! + \property QSerialPort::baudRate + \brief the data baud rate for the desired direction + + If the setting is successful or set before opening the port, returns true; + otherwise returns false and sets an error code which can be obtained by + accessing the value of the QSerialPort::error property. To set the baud + rate, use the enumeration QSerialPort::BaudRate or any positive qint32 + value. + + \note If the setting is set before opening the port, the actual serial port + setting is done automatically in the \l{QSerialPort::open()} method right + after that the opening of the port succeeds. + + \warning Setting the AllDirections flag is only supported on + the Windows, Windows CE, and Symbian platforms. + + \warning Returns equal baud rate in any direction on Windows, Windows CE, and + Symbian. + + The default value is Baud9600, i.e. 9600 bits per second. +*/ +bool QSerialPort::setBaudRate(qint32 baudRate, Directions directions) +{ + Q_D(QSerialPort); + + if (!isOpen() || d->setBaudRate(baudRate, directions)) { + if (directions & QSerialPort::Input) { + if (d->inputBaudRate != baudRate) + d->inputBaudRate = baudRate; + else + directions &= ~QSerialPort::Input; + } + + if (directions & QSerialPort::Output) { + if (d->outputBaudRate != baudRate) + d->outputBaudRate = baudRate; + else + directions &= ~QSerialPort::Output; + } + + if (directions) + emit baudRateChanged(baudRate, directions); + + return true; + } + + return false; +} + +qint32 QSerialPort::baudRate(Directions directions) const +{ + Q_D(const QSerialPort); + if (directions == QSerialPort::AllDirections) + return d->inputBaudRate == d->outputBaudRate ? + d->inputBaudRate : -1; + return directions & QSerialPort::Input ? d->inputBaudRate : d->outputBaudRate; +} + +/*! + \fn void QSerialPort::baudRateChanged(qint32 baudRate, Directions directions) + + This signal is emitted after the baud rate has been changed. The new baud + rate is passed as \a baudRate and directions as \a directions. + + \sa QSerialPort::baudRate +*/ + +/*! + \property QSerialPort::dataBits + \brief the data bits in a frame + + If the setting is successful or set before opening the port, returns + true; otherwise returns false and sets an error code which can be obtained + by accessing the value of the QSerialPort::error property. + + \note If the setting is set before opening the port, the actual serial port + setting is done automatically in the \l{QSerialPort::open()} method right + after that the opening of the port succeeds. + + The default value is Data8, i.e. 8 data bits. +*/ +bool QSerialPort::setDataBits(DataBits dataBits) +{ + Q_D(QSerialPort); + + if (!isOpen() || d->setDataBits(dataBits)) { + if (d->dataBits != dataBits) { + d->dataBits = dataBits; + emit dataBitsChanged(d->dataBits); + } + return true; + } + + return false; +} + +QSerialPort::DataBits QSerialPort::dataBits() const +{ + Q_D(const QSerialPort); + return d->dataBits; +} + +/*! + \fn void QSerialPort::dataBitsChanged(DataBits dataBits) + + This signal is emitted after the data bits in a frame has been changed. The + new data bits in a frame is passed as \a dataBits. + + \sa QSerialPort::dataBits +*/ + + +/*! + \property QSerialPort::parity + \brief the parity checking mode + + If the setting is successful or set before opening the port, returns true; + otherwise returns false and sets an error code which can be obtained by + accessing the value of the QSerialPort::error property. + + \note If the setting is set before opening the port, the actual serial port + setting is done automatically in the \l{QSerialPort::open()} method right + after that the opening of the port succeeds. + + The default value is NoParity, i.e. no parity. +*/ +bool QSerialPort::setParity(Parity parity) +{ + Q_D(QSerialPort); + + if (!isOpen() || d->setParity(parity)) { + if (d->parity != parity) { + d->parity = parity; + emit parityChanged(d->parity); + } + return true; + } + + return false; +} + +QSerialPort::Parity QSerialPort::parity() const +{ + Q_D(const QSerialPort); + return d->parity; +} + +/*! + \fn void QSerialPort::parityChanged(Parity parity) + + This signal is emitted after the parity checking mode has been changed. The + new parity checking mode is passed as \a parity. + + \sa QSerialPort::parity +*/ + +/*! + \property QSerialPort::stopBits + \brief the number of stop bits in a frame + + If the setting is successful or set before opening the port, returns true; + otherwise returns false and sets an error code which can be obtained by + accessing the value of the QSerialPort::error property. + + \note If the setting is set before opening the port, the actual serial port + setting is done automatically in the \l{QSerialPort::open()} method right + after that the opening of the port succeeds. + + The default value is OneStop, i.e. 1 stop bit. +*/ +bool QSerialPort::setStopBits(StopBits stopBits) +{ + Q_D(QSerialPort); + + if (!isOpen() || d->setStopBits(stopBits)) { + if (d->stopBits != stopBits) { + d->stopBits = stopBits; + emit stopBitsChanged(d->stopBits); + } + return true; + } + + return false; +} + +QSerialPort::StopBits QSerialPort::stopBits() const +{ + Q_D(const QSerialPort); + return d->stopBits; +} + +/*! + \fn void QSerialPort::stopBitsChanged(StopBits stopBits) + + This signal is emitted after the number of stop bits in a frame has been + changed. The new number of stop bits in a frame is passed as \a stopBits. + + \sa QSerialPort::stopBits +*/ + +/*! + \property QSerialPort::flowControl + \brief the desired flow control mode + + If the setting is successful or set before opening the port, returns true; + otherwise returns false and sets an error code which can be obtained by + accessing the value of the QSerialPort::error property. + + \note If the setting is set before opening the port, the actual serial port + setting is done automatically in the \l{QSerialPort::open()} method right + after that the opening of the port succeeds. + + The default value is NoFlowControl, i.e. no flow control. +*/ +bool QSerialPort::setFlowControl(FlowControl flowControl) +{ + Q_D(QSerialPort); + + if (!isOpen() || d->setFlowControl(flowControl)) { + if (d->flowControl != flowControl) { + d->flowControl = flowControl; + emit flowControlChanged(d->flowControl); + } + return true; + } + + return false; +} + +QSerialPort::FlowControl QSerialPort::flowControl() const +{ + Q_D(const QSerialPort); + return d->flowControl; +} + +/*! + \fn void QSerialPort::flowControlChanged(FlowControl flow) + + This signal is emitted after the flow control mode has been changed. The + new flow control mode is passed as \a flow. + + \sa QSerialPort::flowControl +*/ + +/*! + \property QSerialPort::dataTerminalReady + \brief the state (high or low) of the line signal DTR + + Returns true on success, false otherwise. + If the flag is true then the DTR signal is set to high; otherwise low. + + \note The serial port has to be open before trying to set or get this + property; otherwise false is returned and the error code is set to + NotOpenError. + + \sa pinoutSignals() +*/ +bool QSerialPort::setDataTerminalReady(bool set) +{ + Q_D(QSerialPort); + + if (!isOpen()) { + setError(QSerialPort::NotOpenError); + qWarning("%s: device not open", Q_FUNC_INFO); + return false; + } + + const bool dataTerminalReady = isDataTerminalReady(); + const bool retval = d->setDataTerminalReady(set); + if (retval && (dataTerminalReady != set)) + emit dataTerminalReadyChanged(set); + + return retval; +} + +bool QSerialPort::isDataTerminalReady() +{ + Q_D(QSerialPort); + return d->pinoutSignals() & QSerialPort::DataTerminalReadySignal; +} + +/*! + \fn void QSerialPort::dataTerminalReadyChanged(bool set) + + This signal is emitted after the state (high or low) of the line signal DTR + has been changed. The new the state (high or low) of the line signal DTR is + passed as \a set. + + \sa QSerialPort::dataTerminalReady +*/ + +/*! + \property QSerialPort::requestToSend + \brief the state (high or low) of the line signal RTS + + Returns true on success, false otherwise. + If the flag is true then the RTS signal is set to high; otherwise low. + + \note The serial port has to be open before trying to set or get this + property; otherwise false is returned and the error code is set to + NotOpenError. + + \sa pinoutSignals() +*/ +bool QSerialPort::setRequestToSend(bool set) +{ + Q_D(QSerialPort); + + if (!isOpen()) { + setError(QSerialPort::NotOpenError); + qWarning("%s: device not open", Q_FUNC_INFO); + return false; + } + + const bool requestToSend = isRequestToSend(); + const bool retval = d->setRequestToSend(set); + if (retval && (requestToSend != set)) + emit requestToSendChanged(set); + + return retval; +} + +bool QSerialPort::isRequestToSend() +{ + Q_D(QSerialPort); + return d->pinoutSignals() & QSerialPort::RequestToSendSignal; +} + +/*! + \fn void QSerialPort::requestToSendChanged(bool set) + + This signal is emitted after the state (high or low) of the line signal RTS + has been changed. The new the state (high or low) of the line signal RTS is + passed as \a set. + + \sa QSerialPort::requestToSend +*/ + +/*! + Returns the state of the line signals in a bitmap format. + + From this result, it is possible to allocate the state of the + desired signal by applying a mask "AND", where the mask is + the desired enumeration value from QSerialPort::PinoutSignals. + + \note This method performs a system call, thus ensuring that the line signal + states are returned properly. This is necessary when the underlying + operating systems cannot provide proper notifications about the changes. + + \note The serial port has to be open before trying to get the pinout + signals; otherwise returns NoSignal and sets the NotOpenError error code. + + \sa QSerialPort::dataTerminalReady, QSerialPort::requestToSend +*/ +QSerialPort::PinoutSignals QSerialPort::pinoutSignals() +{ + Q_D(QSerialPort); + + if (!isOpen()) { + setError(QSerialPort::NotOpenError); + qWarning("%s: device not open", Q_FUNC_INFO); + return QSerialPort::NoSignal; + } + + return d->pinoutSignals(); +} + +/*! + This function writes as much as possible from the internal write + buffer to the underlying serial port without blocking. If any data + was written, this function returns true; otherwise returns false. + + Call this function for sending the buffered data immediately to the serial + port. The number of bytes successfully written depends on the operating + system. In most cases, this function does not need to be called, because the + QSerialPort class will start sending data automatically once control is + returned to the event loop. In the absence of an event loop, call + waitForBytesWritten() instead. + + \note The serial port has to be open before trying to flush any buffered + data; otherwise returns false and sets the NotOpenError error code. + + \sa write(), waitForBytesWritten() +*/ +bool QSerialPort::flush() +{ + Q_D(QSerialPort); + + if (!isOpen()) { + setError(QSerialPort::NotOpenError); + qWarning("%s: device not open", Q_FUNC_INFO); + return false; + } + + return d->flush(); +} + +/*! + Discards all characters from the output or input buffer, depending on + given directions \a directions. This includes clearing the internal class buffers and + the UART (driver) buffers. Also terminate pending read or write operations. + If successful, returns true; otherwise returns false. + + \note The serial port has to be open before trying to clear any buffered + data; otherwise returns false and sets the NotOpenError error code. +*/ +bool QSerialPort::clear(Directions directions) +{ + Q_D(QSerialPort); + + if (!isOpen()) { + setError(QSerialPort::NotOpenError); + qWarning("%s: device not open", Q_FUNC_INFO); + return false; + } + + if (directions & Input) + d->readBuffer.clear(); + if (directions & Output) + d->writeBuffer.clear(); + return d->clear(directions); +} + +/*! + \reimp + + Returns true if no more data is currently available for reading; otherwise + returns false. + + This function is most commonly used when reading data from the + serial port in a loop. For example: + + \code + // This slot is connected to QSerialPort::readyRead() + void QSerialPortClass::readyReadSlot() + { + while (!port.atEnd()) { + QByteArray data = port.read(100); + .... + } + } + \endcode + + \sa bytesAvailable(), readyRead() + */ +bool QSerialPort::atEnd() const +{ + Q_D(const QSerialPort); + return QIODevice::atEnd() && (!isOpen() || (d->readBuffer.size() == 0)); +} + +/*! + \property QSerialPort::dataErrorPolicy + \brief the error policy for how the process receives characters in the case where + a parity error is detected. + \obsolete + + If the setting is successful, returns true; otherwise returns false. The + default policy set is IgnorePolicy. + + \note The serial port has to be open before trying to set this property; + otherwise returns false and sets the NotOpenError error code. This is a bit + unusual as opposed to the regular Qt property settings of a class. However, + this is a special use case since the property is set through the interaction + with the kernel and hardware. Hence, the two scenarios cannot be completely + compared to each other. +*/ +#if QT_DEPRECATED_SINCE(5, 2) +bool QSerialPort::setDataErrorPolicy(DataErrorPolicy policy) +{ + Q_D(QSerialPort); + + if (!isOpen()) { + setError(QSerialPort::NotOpenError); + qWarning("%s: device not open", Q_FUNC_INFO); + return false; + } + + const bool ret = d->policy == policy || d->setDataErrorPolicy(policy); + if (ret && (d->policy != policy)) { + d->policy = policy; + emit dataErrorPolicyChanged(d->policy); + } + + return ret; +} + +QSerialPort::DataErrorPolicy QSerialPort::dataErrorPolicy() const +{ + Q_D(const QSerialPort); + return d->policy; +} +#endif // QT_DEPRECATED_SINCE(5, 2) +/*! + \fn void QSerialPort::dataErrorPolicyChanged(DataErrorPolicy policy) + \obsolete + + This signal is emitted after the error policy for how the process receives + characters in case of parity error detection has been changed. The new error + policy for how the process receives the character in case of parity error + detection is passed as \a policy. + + \sa QSerialPort::dataErrorPolicy +*/ + +/*! + \property QSerialPort::error + \brief the error status of the serial port + + The I/O device status returns an error code. For example, if open() + returns false, or a read/write operation returns -1, this property can + be used to figure out the reason why the operation failed. + + The error code is set to the default QSerialPort::NoError after a call to + clearError() +*/ +QSerialPort::SerialPortError QSerialPort::error() const +{ + Q_D(const QSerialPort); + return d->error; +} + +void QSerialPort::clearError() +{ + setError(QSerialPort::NoError); +} + +/*! + \fn void QSerialPort::error(SerialPortError error) + + This signal is emitted after the error has been changed. The new error + is passed as \a error. + + \sa QSerialPort::error +*/ + +/*! + Returns the size of the internal read buffer. This limits the + amount of data that the client can receive before calling the read() + or readAll() methods. + + A read buffer size of 0 (the default) means that the buffer has + no size limit, ensuring that no data is lost. + + \sa setReadBufferSize(), read() +*/ +qint64 QSerialPort::readBufferSize() const +{ + Q_D(const QSerialPort); + return d->readBufferMaxSize; +} + +/*! + Sets the size of QSerialPort's internal read buffer to be \a + size bytes. + + If the buffer size is limited to a certain size, QSerialPort + will not buffer more than this size of data. The special case of a buffer + size of 0 means that the read buffer is unlimited and all + incoming data is buffered. This is the default. + + This option is useful if the data is only read at certain points + in time (for instance in a real-time streaming application) or if the serial + port should be protected against receiving too much data, which may + eventually cause the application to run out of memory. + + \sa readBufferSize(), read() +*/ +void QSerialPort::setReadBufferSize(qint64 size) +{ + Q_D(QSerialPort); + + if (d->readBufferMaxSize == size) + return; + d->readBufferMaxSize = size; +} + +/*! + \reimp + + Always returns true. The serial port is a sequential device. +*/ +bool QSerialPort::isSequential() const +{ + return true; +} + +/*! + \reimp + + Returns the number of incoming bytes that are waiting to be read. + + \sa bytesToWrite(), read() +*/ +qint64 QSerialPort::bytesAvailable() const +{ + Q_D(const QSerialPort); + return d->readBuffer.size() + QIODevice::bytesAvailable(); +} + +/*! + \reimp + + Returns the number of bytes that are waiting to be written. The + bytes are written when control goes back to the event loop or + when flush() is called. + + \sa bytesAvailable(), flush() +*/ +qint64 QSerialPort::bytesToWrite() const +{ + Q_D(const QSerialPort); + return d->bytesToWrite() + QIODevice::bytesToWrite(); +} + +/*! + \reimp + + Returns true if a line of data can be read from the serial port; + otherwise returns false. + + \sa readLine() +*/ +bool QSerialPort::canReadLine() const +{ + Q_D(const QSerialPort); + const bool hasLine = (d->readBuffer.size() > 0) && d->readBuffer.canReadLine(); + return hasLine || QIODevice::canReadLine(); +} + +/*! + \reimp + + This function blocks until new data is available for reading and the + \l{QIODevice::}{readyRead()} signal has been emitted. The function + will timeout after \a msecs milliseconds. + + The function returns true if the readyRead() signal is emitted and + there is new data available for reading; otherwise it returns false + (if an error occurred or the operation timed out). + + \sa waitForBytesWritten() +*/ +bool QSerialPort::waitForReadyRead(int msecs) +{ + Q_D(QSerialPort); + return d->waitForReadyRead(msecs); +} + +/*! + \fn Handle QSerialPort::handle() const + \since 5.2 + + If the platform is supported and the serial port is open, returns the native + serial port handle; otherwise returns -1. + + \warning This function is for expert use only; use it at your own risk. + Furthermore, this function carries no compatibility promise between minor + Qt releases. +*/ + +/*! + \reimp +*/ +bool QSerialPort::waitForBytesWritten(int msecs) +{ + Q_D(QSerialPort); + return d->waitForBytesWritten(msecs); +} + +/*! + Sends a continuous stream of zero bits during a specified period + of time \a duration in msec if the terminal is using asynchronous + serial data. If successful, returns true; otherwise returns false. + + If the duration is zero then zero bits are transmitted by at least + 0.25 seconds, but no more than 0.5 seconds. + + If the duration is non zero then zero bits are transmitted within a certain + period of time depending on the implementation. + + \note The serial port has to be open before trying to send a break + duration; otherwise returns false and sets the NotOpenError error code. + + \sa setBreakEnabled() +*/ +bool QSerialPort::sendBreak(int duration) +{ + Q_D(QSerialPort); + + if (!isOpen()) { + setError(QSerialPort::NotOpenError); + qWarning("%s: device not open", Q_FUNC_INFO); + return false; + } + + return d->sendBreak(duration); +} + +/*! + Controls the signal break, depending on the flag \a set. + If successful, returns true; otherwise returns false. + + If \a set is true then enables the break transmission; otherwise disables. + + \note The serial port has to be open before trying to set break enabled; + otherwise returns false and sets the NotOpenError error code. + + \sa sendBreak() +*/ +bool QSerialPort::setBreakEnabled(bool set) +{ + Q_D(QSerialPort); + + if (!isOpen()) { + setError(QSerialPort::NotOpenError); + qWarning("%s: device not open", Q_FUNC_INFO); + return false; + } + + return d->setBreakEnabled(set); +} + +/*! + \reimp +*/ +qint64 QSerialPort::readData(char *data, qint64 maxSize) +{ + Q_D(QSerialPort); +#ifdef Q_OS_ANDROID + qint64 retL = d->readBuffer.read(data, maxSize); + d->startReadThread(); + return retL; +#else + return d->readData(data, maxSize); +#endif +} + +/*! + \reimp +*/ +qint64 QSerialPort::readLineData(char *data, qint64 maxSize) +{ +#ifdef Q_OS_ANDROID + qint64 retL = QIODevice::readLineData(data, maxSize); + Q_D(QSerialPort); + d->startReadThread(); + return retL; +#else + return QIODevice::readLineData(data, maxSize); +#endif +} + +/*! + \reimp +*/ +qint64 QSerialPort::writeData(const char *data, qint64 maxSize) +{ + Q_D(QSerialPort); + return d->writeData(data, maxSize); +} + +void QSerialPort::setError(QSerialPort::SerialPortError serialPortError, const QString &errorString) +{ + Q_D(QSerialPort); + + d->error = serialPortError; + + if (errorString.isNull()) + setErrorString(qt_error_string(-1)); + else + setErrorString(errorString); + + emit error(serialPortError); +} + +#include "moc_qserialport.cpp" + +QT_END_NAMESPACE diff --git a/libs/qtandroidserialport/src/qserialport.h b/libs/qtandroidserialport/src/qserialport.h new file mode 100644 index 000000000..c637a3f79 --- /dev/null +++ b/libs/qtandroidserialport/src/qserialport.h @@ -0,0 +1,287 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Denis Shienkov +** Copyright (C) 2013 Laszlo Papp +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtSerialPort module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSERIALPORT_H +#define QSERIALPORT_H + +#include + +QT_BEGIN_NAMESPACE + +class QSerialPortInfo; +class QSerialPortPrivate; + +class QSerialPort : public QIODevice +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QSerialPort) + + Q_PROPERTY(qint32 baudRate READ baudRate WRITE setBaudRate NOTIFY baudRateChanged) + Q_PROPERTY(DataBits dataBits READ dataBits WRITE setDataBits NOTIFY dataBitsChanged) + Q_PROPERTY(Parity parity READ parity WRITE setParity NOTIFY parityChanged) + Q_PROPERTY(StopBits stopBits READ stopBits WRITE setStopBits NOTIFY stopBitsChanged) + Q_PROPERTY(FlowControl flowControl READ flowControl WRITE setFlowControl NOTIFY flowControlChanged) +#if QT_DEPRECATED_SINCE(5, 2) + Q_PROPERTY(DataErrorPolicy dataErrorPolicy READ dataErrorPolicy WRITE setDataErrorPolicy NOTIFY dataErrorPolicyChanged) +#endif + Q_PROPERTY(bool dataTerminalReady READ isDataTerminalReady WRITE setDataTerminalReady NOTIFY dataTerminalReadyChanged) + Q_PROPERTY(bool requestToSend READ isRequestToSend WRITE setRequestToSend NOTIFY requestToSendChanged) + Q_PROPERTY(SerialPortError error READ error RESET clearError NOTIFY error) +#if QT_DEPRECATED_SINCE(5, 3) + Q_PROPERTY(bool settingsRestoredOnClose READ settingsRestoredOnClose WRITE setSettingsRestoredOnClose NOTIFY settingsRestoredOnCloseChanged) +#endif + + Q_ENUMS(BaudRate DataBits Parity StopBits FlowControl DataErrorPolicy SerialPortError) + Q_FLAGS(Directions PinoutSignals) + +#if defined(Q_OS_WIN32) || defined(Q_OS_WINCE) + typedef void* Handle; +#else + typedef int Handle; +#endif + +public: + + enum Direction { + Input = 1, + Output = 2, + AllDirections = Input | Output + }; + Q_DECLARE_FLAGS(Directions, Direction) + + enum BaudRate { + Baud1200 = 1200, + Baud2400 = 2400, + Baud4800 = 4800, + Baud9600 = 9600, + Baud19200 = 19200, + Baud38400 = 38400, + Baud57600 = 57600, + Baud115200 = 115200, + UnknownBaud = -1 + }; + + enum DataBits { + Data5 = 5, + Data6 = 6, + Data7 = 7, + Data8 = 8, + UnknownDataBits = -1 + }; + + enum Parity { + NoParity = 0, + EvenParity = 2, + OddParity = 3, + SpaceParity = 4, + MarkParity = 5, + UnknownParity = -1 + }; + + enum StopBits { + OneStop = 1, + OneAndHalfStop = 3, + TwoStop = 2, + UnknownStopBits = -1 + }; + + enum FlowControl { + NoFlowControl, + HardwareControl, + SoftwareControl, + UnknownFlowControl = -1 + }; + + enum PinoutSignal { + NoSignal = 0x00, + TransmittedDataSignal = 0x01, + ReceivedDataSignal = 0x02, + DataTerminalReadySignal = 0x04, + DataCarrierDetectSignal = 0x08, + DataSetReadySignal = 0x10, + RingIndicatorSignal = 0x20, + RequestToSendSignal = 0x40, + ClearToSendSignal = 0x80, + SecondaryTransmittedDataSignal = 0x100, + SecondaryReceivedDataSignal = 0x200 + }; + Q_DECLARE_FLAGS(PinoutSignals, PinoutSignal) + +#if QT_DEPRECATED_SINCE(5, 2) +#if defined _MSC_VER +#pragma deprecated(UnknownBaud) +#pragma deprecated(UnknownDataBits) +#pragma deprecated(UnknownParity) +#pragma deprecated(UnknownStopBits) +#pragma deprecated(UnknownFlowControl) +#pragma deprecated(TransmittedDataSignal) +#pragma deprecated(ReceivedDataSignal) +#endif +#endif + +#if QT_DEPRECATED_SINCE(5, 2) + enum DataErrorPolicy { + SkipPolicy, + PassZeroPolicy, + IgnorePolicy, + StopReceivingPolicy, + UnknownPolicy = -1 + }; +#endif + + enum SerialPortError { + NoError, + DeviceNotFoundError, + PermissionError, + OpenError, + ParityError, + FramingError, + BreakConditionError, + WriteError, + ReadError, + ResourceError, + UnsupportedOperationError, + UnknownError, + TimeoutError, + NotOpenError + }; + + explicit QSerialPort(QObject *parent = Q_NULLPTR); + explicit QSerialPort(const QString &name, QObject *parent = Q_NULLPTR); + explicit QSerialPort(const QSerialPortInfo &info, QObject *parent = Q_NULLPTR); + virtual ~QSerialPort(); + + void setPortName(const QString &name); + QString portName() const; + + void setPort(const QSerialPortInfo &info); + + bool open(OpenMode mode) Q_DECL_OVERRIDE; + void close() Q_DECL_OVERRIDE; + +#if QT_DEPRECATED_SINCE(5, 3) + QT_DEPRECATED void setSettingsRestoredOnClose(bool restore); + QT_DEPRECATED bool settingsRestoredOnClose() const; +#endif + + bool setBaudRate(qint32 baudRate, Directions directions = AllDirections); + qint32 baudRate(Directions directions = AllDirections) const; + + bool setDataBits(DataBits dataBits); + DataBits dataBits() const; + + bool setParity(Parity parity); + Parity parity() const; + + bool setStopBits(StopBits stopBits); + StopBits stopBits() const; + + bool setFlowControl(FlowControl flowControl); + FlowControl flowControl() const; + + bool setDataTerminalReady(bool set); + bool isDataTerminalReady(); + + bool setRequestToSend(bool set); + bool isRequestToSend(); + + PinoutSignals pinoutSignals(); + + bool flush(); + bool clear(Directions directions = AllDirections); + bool atEnd() const Q_DECL_OVERRIDE; + +#if QT_DEPRECATED_SINCE(5, 2) + QT_DEPRECATED bool setDataErrorPolicy(DataErrorPolicy policy = IgnorePolicy); + QT_DEPRECATED DataErrorPolicy dataErrorPolicy() const; +#endif + + SerialPortError error() const; + void clearError(); + + qint64 readBufferSize() const; + void setReadBufferSize(qint64 size); + + bool isSequential() const Q_DECL_OVERRIDE; + + qint64 bytesAvailable() const Q_DECL_OVERRIDE; + qint64 bytesToWrite() const Q_DECL_OVERRIDE; + bool canReadLine() const Q_DECL_OVERRIDE; + + bool waitForReadyRead(int msecs) Q_DECL_OVERRIDE; + bool waitForBytesWritten(int msecs) Q_DECL_OVERRIDE; + + bool sendBreak(int duration = 0); + bool setBreakEnabled(bool set = true); + + Handle handle() const; + +Q_SIGNALS: + void baudRateChanged(qint32 baudRate, QSerialPort::Directions directions); + void dataBitsChanged(QSerialPort::DataBits dataBits); + void parityChanged(QSerialPort::Parity parity); + void stopBitsChanged(QSerialPort::StopBits stopBits); + void flowControlChanged(QSerialPort::FlowControl flowControl); + void dataErrorPolicyChanged(QSerialPort::DataErrorPolicy policy); + void dataTerminalReadyChanged(bool set); + void requestToSendChanged(bool set); + void error(QSerialPort::SerialPortError serialPortError); + void settingsRestoredOnCloseChanged(bool restore); + +protected: + qint64 readData(char *data, qint64 maxSize) Q_DECL_OVERRIDE; + qint64 readLineData(char *data, qint64 maxSize) Q_DECL_OVERRIDE; + qint64 writeData(const char *data, qint64 maxSize) Q_DECL_OVERRIDE; + +private: + void setError(QSerialPort::SerialPortError error, const QString &errorString = QString()); + + QSerialPortPrivate * const d_ptr; + + Q_DISABLE_COPY(QSerialPort) + +#if defined (Q_OS_WIN32) + Q_PRIVATE_SLOT(d_func(), bool _q_completeAsyncCommunication()) + Q_PRIVATE_SLOT(d_func(), bool _q_completeAsyncRead()) + Q_PRIVATE_SLOT(d_func(), bool _q_completeAsyncWrite()) + Q_PRIVATE_SLOT(d_func(), bool _q_startAsyncWrite()) +#endif +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QSerialPort::Directions) +Q_DECLARE_OPERATORS_FOR_FLAGS(QSerialPort::PinoutSignals) + +QT_END_NAMESPACE + +#endif // QSERIALPORT_H diff --git a/libs/qtandroidserialport/src/qserialport_android.cpp b/libs/qtandroidserialport/src/qserialport_android.cpp new file mode 100644 index 000000000..53b64440d --- /dev/null +++ b/libs/qtandroidserialport/src/qserialport_android.cpp @@ -0,0 +1,867 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Denis Shienkov +** Copyright (C) 2012 Laszlo Papp +** Copyright (C) 2012 Andre Hartmann +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtSerialPort module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// Written by: S. Michael Goza 2014 +// Adapted for QGC by: Gus Grubba 2015 + + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "qserialport_android_p.h" + +QT_BEGIN_NAMESPACE + +#define BAD_PORT 0 + +static const char V_jniClassName[] {"org/qgroundcontrol/qgchelper/UsbDeviceJNI"}; +static const char V_TAG[] {"QGC_QSerialPort"}; + +static void jniDeviceHasDisconnected(JNIEnv *envA, jobject thizA, jint userDataA) +{ + Q_UNUSED(envA); + Q_UNUSED(thizA); + if (userDataA != 0) + ((QSerialPortPrivate *)userDataA)->q_ptr->close(); +} + +static void jniDeviceNewData(JNIEnv *envA, jobject thizA, jint userDataA, jbyteArray dataA) +{ + Q_UNUSED(thizA); + if (userDataA != 0) + { + jbyte *bytesL = envA->GetByteArrayElements(dataA, NULL); + jsize lenL = envA->GetArrayLength(dataA); + ((QSerialPortPrivate *)userDataA)->newDataArrived((char *)bytesL, lenL); + envA->ReleaseByteArrayElements(dataA, bytesL, JNI_ABORT); + } +} + +static void jniDeviceException(JNIEnv *envA, jobject thizA, jint userDataA, jstring messageA) +{ + Q_UNUSED(thizA); + if(userDataA != 0) + { + const char *stringL = envA->GetStringUTFChars(messageA, NULL); + QString strL = QString::fromUtf8(stringL); + envA->ReleaseStringUTFChars(messageA, stringL); + if(envA->ExceptionCheck()) + envA->ExceptionClear(); + ((QSerialPortPrivate *)userDataA)->exceptionArrived(strL); + } +} + +QSerialPortPrivate::QSerialPortPrivate(QSerialPort *q) + : QSerialPortPrivateData(q) + , descriptor(-1) + , isCustomBaudRateSupported(false) + , emittedBytesWritten(false) + , pendingBytesWritten(0) + , hasRegisteredFunctions(false) + , jniDataBits(8) + , jniStopBits(1) + , jniParity(0) + , internalWriteTimeoutMsec(0) + , isReadStopped(true) +{ +} + +bool QSerialPortPrivate::open(QIODevice::OpenMode mode) +{ + rwMode = mode; + __android_log_print(ANDROID_LOG_INFO, V_TAG, "Opening %s", systemLocation.toLatin1().data()); + + QAndroidJniObject jnameL = QAndroidJniObject::fromString(systemLocation); + deviceId = QAndroidJniObject::callStaticMethod( + V_jniClassName, + "open", + "(Ljava/lang/String;I)I", + jnameL.object(), + (jint)this); + + isReadStopped = false; + + if (deviceId == BAD_PORT) + { + __android_log_print(ANDROID_LOG_ERROR, V_TAG, "Error opening %s", systemLocation.toLatin1().data()); + q_ptr->setError(QSerialPort::DeviceNotFoundError); + return false; + } + + descriptor = QAndroidJniObject::callStaticMethod( + V_jniClassName, + "getDeviceHandle", + "(I)I", + deviceId); + + if (!hasRegisteredFunctions) + { + // REGISTER THE C++ FUNCTION WITH JNI + QAndroidJniEnvironment envL; + + JNINativeMethod methodsL[] { + {"nativeDeviceHasDisconnected", "(I)V", reinterpret_cast(jniDeviceHasDisconnected)}, + {"nativeDeviceNewData", "(I[B)V", reinterpret_cast(jniDeviceNewData)}, + {"nativeDeviceException", "(ILjava/lang/String;)V", reinterpret_cast(jniDeviceException)} + }; + + QAndroidJniObject javaClassL(V_jniClassName); + jclass objectClassL = envL->GetObjectClass(javaClassL.object()); + jint valL = envL->RegisterNatives(objectClassL, methodsL, sizeof(methodsL) / sizeof(methodsL[0])); + envL->DeleteLocalRef(objectClassL); + hasRegisteredFunctions = true; + + if (envL->ExceptionCheck()) + envL->ExceptionClear(); + + if(valL < 0) { + __android_log_print(ANDROID_LOG_ERROR, V_TAG, "Error registering methods"); + q_ptr->setError(QSerialPort::OpenError); + return false; + } + } + + if (rwMode == QIODevice::WriteOnly) + stopReadThread(); + + return true; +} + +void QSerialPortPrivate::close() +{ + if (deviceId == BAD_PORT) + return; + + __android_log_print(ANDROID_LOG_INFO, V_TAG, "Closing %s", systemLocation.toLatin1().data()); + jboolean resultL = QAndroidJniObject::callStaticMethod( + V_jniClassName, + "close", + "(I)Z", + deviceId); + + descriptor = -1; + isCustomBaudRateSupported = false; + pendingBytesWritten = 0; + deviceId = BAD_PORT; + + if (!resultL) + q_ptr->setErrorString(QStringLiteral("Closing device failed")); +} + +bool QSerialPortPrivate::setParameters(int baudRateA, int dataBitsA, int stopBitsA, int parityA) +{ + if (deviceId == BAD_PORT) + { + q_ptr->setError(QSerialPort::NotOpenError); + return false; + } + + jboolean resultL = QAndroidJniObject::callStaticMethod(V_jniClassName, + "setParameters", + "(IIIII)Z", + deviceId, + baudRateA, + dataBitsA, + stopBitsA, + parityA); + + if(resultL) + { + // SET THE JNI VALUES TO WHAT WAS SENT + inputBaudRate = outputBaudRate = baudRateA; + jniDataBits = dataBitsA; + jniStopBits = stopBitsA; + jniParity = parityA; + } + + return resultL; +} + + + +void QSerialPortPrivate::stopReadThread() +{ + if (isReadStopped) + return; + + QAndroidJniObject::callStaticMethod(V_jniClassName, + "stopIoManager", + "(I)V", + deviceId); + isReadStopped = true; +} + + + +void QSerialPortPrivate::startReadThread() +{ + if (!isReadStopped) + return; + + QAndroidJniObject::callStaticMethod(V_jniClassName, + "startIoManager", + "(I)V", + deviceId); + isReadStopped = false; +} + + + + + +QSerialPort::PinoutSignals QSerialPortPrivate::pinoutSignals() +{ + return QSerialPort::NoSignal; +} + + + + +bool QSerialPortPrivate::setDataTerminalReady(bool set) +{ + if (deviceId == BAD_PORT) + { + q_ptr->setError(QSerialPort::NotOpenError); + return false; + } + + return QAndroidJniObject::callStaticMethod(V_jniClassName, + "setDataTerminalReady", + "(IZ)Z", + deviceId, + set); +} + + + + +bool QSerialPortPrivate::setRequestToSend(bool set) +{ + if (deviceId == BAD_PORT) + { + q_ptr->setError(QSerialPort::NotOpenError); + return false; + } + + return QAndroidJniObject::callStaticMethod(V_jniClassName, + "setRequestToSend", + "(IZ)Z", + deviceId, + set); +} + + + + +bool QSerialPortPrivate::flush() +{ + return writeDataOneShot(); +} + + + + +bool QSerialPortPrivate::clear(QSerialPort::Directions directions) +{ + if (deviceId == BAD_PORT) + { + q_ptr->setError(QSerialPort::NotOpenError); + return false; + } + + bool inputL = false; + bool outputL = false; + + if (directions == QSerialPort::AllDirections) + inputL = outputL = true; + else + { + if (directions & QSerialPort::Input) + inputL = true; + + if (directions & QSerialPort::Output) + outputL = true; + } + + return QAndroidJniObject::callStaticMethod(V_jniClassName, + "purgeBuffers", + "(IZZ)Z", + deviceId, + inputL, + outputL); +} + + + + +bool QSerialPortPrivate::sendBreak(int duration) +{ + Q_UNUSED(duration); + return true; +} + + + + +bool QSerialPortPrivate::setBreakEnabled(bool set) +{ + Q_UNUSED(set); + return true; +} + + + + +void QSerialPortPrivate::startWriting() +{ + writeDataOneShot(); +} + + + + +bool QSerialPortPrivate::waitForReadyRead(int msecs) +{ + int origL = readBuffer.size(); + + if (origL > 0) + return true; + + for (int iL=0; iLpolicy = policy; + return true; +} + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void QSerialPortPrivate::newDataArrived(char *bytesA, int lengthA) +{ + Q_Q(QSerialPort); + + int bytesToReadL = lengthA; + + // Always buffered, read data from the port into the read buffer + if (readBufferMaxSize && (bytesToReadL > (readBufferMaxSize - readBuffer.size()))) { + bytesToReadL = readBufferMaxSize - readBuffer.size(); + if (bytesToReadL <= 0) { + // Buffer is full. User must read data from the buffer + // before we can read more from the port. + stopReadThread(); + return; + } + } + + char *ptr = readBuffer.reserve(bytesToReadL); + memcpy(ptr, bytesA, bytesToReadL); + + emit q->readyRead(); +} + + + +void QSerialPortPrivate::exceptionArrived(QString strA) +{ + q_ptr->setErrorString(strA); +} + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +bool QSerialPortPrivate::writeDataOneShot() +{ + Q_Q(QSerialPort); + + pendingBytesWritten = -1; + + while (!writeBuffer.isEmpty()) + { + pendingBytesWritten = writeToPort(writeBuffer.readPointer(), writeBuffer.nextDataBlockSize()); + + if (pendingBytesWritten <= 0) + { + QSerialPort::SerialPortError errorL = decodeSystemError(); + if (errorL != QSerialPort::ResourceError) + errorL = QSerialPort::WriteError; + q->setError(errorL); + return false; + } + + writeBuffer.free(pendingBytesWritten); + + emit q->bytesWritten(pendingBytesWritten); + } + + return (pendingBytesWritten < 0)? false: true; +} + + + +QSerialPort::SerialPortError QSerialPortPrivate::decodeSystemError() const +{ + QSerialPort::SerialPortError error; + switch (errno) { + case ENODEV: + error = QSerialPort::DeviceNotFoundError; + break; + case EACCES: + error = QSerialPort::PermissionError; + break; + case EBUSY: + error = QSerialPort::PermissionError; + break; + case EAGAIN: + error = QSerialPort::ResourceError; + break; + case EIO: + error = QSerialPort::ResourceError; + break; + case EBADF: + error = QSerialPort::ResourceError; + break; + default: + error = QSerialPort::UnknownError; + break; + } + return error; +} + + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +qint64 QSerialPortPrivate::writeToPort(const char *data, qint64 maxSize) +{ + if (deviceId == BAD_PORT) + { + q_ptr->setError(QSerialPort::NotOpenError); + return 0; + } + + QAndroidJniEnvironment envL; + jbyteArray jarrayL = envL->NewByteArray(maxSize); + envL->SetByteArrayRegion(jarrayL, 0, maxSize, (jbyte *)data); + int resultL = QAndroidJniObject::callStaticMethod(V_jniClassName, + "write", + "(I[BI)I", + deviceId, + jarrayL, + internalWriteTimeoutMsec); + + if (envL->ExceptionCheck()) + { + envL->ExceptionClear(); + q_ptr->setErrorString(QStringLiteral("Writing to the device threw an exception")); + envL->DeleteLocalRef(jarrayL); + return 0; + } + + envL->DeleteLocalRef(jarrayL); + + return resultL; +} + + + + +static inline bool evenParity(quint8 c) +{ + c ^= c >> 4; //(c7 ^ c3)(c6 ^ c2)(c5 ^ c1)(c4 ^ c0) + c ^= c >> 2; //[(c7 ^ c3)(c5 ^ c1)][(c6 ^ c2)(c4 ^ c0)] + c ^= c >> 1; + return c & 1; //(c7 ^ c3)(c5 ^ c1)(c6 ^ c2)(c4 ^ c0) +} + +typedef QMap BaudRateMap; + +// The OS specific defines can be found in termios.h + +static const BaudRateMap createStandardBaudRateMap() +{ + BaudRateMap baudRateMap; + +#ifdef B50 + baudRateMap.insert(50, B50); +#endif + +#ifdef B75 + baudRateMap.insert(75, B75); +#endif + +#ifdef B110 + baudRateMap.insert(110, B110); +#endif + +#ifdef B134 + baudRateMap.insert(134, B134); +#endif + +#ifdef B150 + baudRateMap.insert(150, B150); +#endif + +#ifdef B200 + baudRateMap.insert(200, B200); +#endif + +#ifdef B300 + baudRateMap.insert(300, B300); +#endif + +#ifdef B600 + baudRateMap.insert(600, B600); +#endif + +#ifdef B1200 + baudRateMap.insert(1200, B1200); +#endif + +#ifdef B1800 + baudRateMap.insert(1800, B1800); +#endif + +#ifdef B2400 + baudRateMap.insert(2400, B2400); +#endif + +#ifdef B4800 + baudRateMap.insert(4800, B4800); +#endif + +#ifdef B7200 + baudRateMap.insert(7200, B7200); +#endif + +#ifdef B9600 + baudRateMap.insert(9600, B9600); +#endif + +#ifdef B14400 + baudRateMap.insert(14400, B14400); +#endif + +#ifdef B19200 + baudRateMap.insert(19200, B19200); +#endif + +#ifdef B28800 + baudRateMap.insert(28800, B28800); +#endif + +#ifdef B38400 + baudRateMap.insert(38400, B38400); +#endif + +#ifdef B57600 + baudRateMap.insert(57600, B57600); +#endif + +#ifdef B76800 + baudRateMap.insert(76800, B76800); +#endif + +#ifdef B115200 + baudRateMap.insert(115200, B115200); +#endif + +#ifdef B230400 + baudRateMap.insert(230400, B230400); +#endif + +#ifdef B460800 + baudRateMap.insert(460800, B460800); +#endif + +#ifdef B500000 + baudRateMap.insert(500000, B500000); +#endif + +#ifdef B576000 + baudRateMap.insert(576000, B576000); +#endif + +#ifdef B921600 + baudRateMap.insert(921600, B921600); +#endif + +#ifdef B1000000 + baudRateMap.insert(1000000, B1000000); +#endif + +#ifdef B1152000 + baudRateMap.insert(1152000, B1152000); +#endif + +#ifdef B1500000 + baudRateMap.insert(1500000, B1500000); +#endif + +#ifdef B2000000 + baudRateMap.insert(2000000, B2000000); +#endif + +#ifdef B2500000 + baudRateMap.insert(2500000, B2500000); +#endif + +#ifdef B3000000 + baudRateMap.insert(3000000, B3000000); +#endif + +#ifdef B3500000 + baudRateMap.insert(3500000, B3500000); +#endif + +#ifdef B4000000 + baudRateMap.insert(4000000, B4000000); +#endif + + return baudRateMap; +} + + + + +static const BaudRateMap& standardBaudRateMap() +{ + static const BaudRateMap baudRateMap = createStandardBaudRateMap(); + return baudRateMap; +} + + + + +qint32 QSerialPortPrivate::baudRateFromSetting(qint32 setting) +{ + return standardBaudRateMap().key(setting); +} + + + + +qint32 QSerialPortPrivate::settingFromBaudRate(qint32 baudRate) +{ + return standardBaudRateMap().value(baudRate); +} + + + + +QList QSerialPortPrivate::standardBaudRates() +{ + return standardBaudRateMap().keys(); +} + + + + +QSerialPort::Handle QSerialPort::handle() const +{ + Q_D(const QSerialPort); + return d->descriptor; +} + +qint64 QSerialPortPrivate::bytesToWrite() const +{ + return writeBuffer.size(); +} + +qint64 QSerialPortPrivate::writeData(const char *data, qint64 maxSize) +{ + return writeToPort(data, maxSize); +} + +QT_END_NAMESPACE + diff --git a/libs/qtandroidserialport/src/qserialport_android_p.h b/libs/qtandroidserialport/src/qserialport_android_p.h new file mode 100644 index 000000000..f105c1fd7 --- /dev/null +++ b/libs/qtandroidserialport/src/qserialport_android_p.h @@ -0,0 +1,133 @@ +#ifndef QSERIALPORT_ANDROID_P_H +#define QSERIALPORT_ANDROID_P_H +/**************************************************************************** +** +** Copyright (C) 2012 Denis Shienkov +** Copyright (C) 2012 Laszlo Papp +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtSerialPort module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qserialport_p.h" + +#include +#include +#include +#include + + +QT_BEGIN_NAMESPACE + +class QSerialPortPrivate : public QSerialPortPrivateData +{ + Q_DECLARE_PUBLIC(QSerialPort) + +public: + QSerialPortPrivate(QSerialPort *q); + + bool open(QIODevice::OpenMode mode); + void close(); + + QSerialPort::PinoutSignals pinoutSignals(); + + bool setDataTerminalReady(bool set); + bool setRequestToSend(bool set); + + bool flush(); + bool clear(QSerialPort::Directions directions); + + bool sendBreak(int duration); + bool setBreakEnabled(bool set); + + void startWriting(); + + bool waitForReadyRead(int msecs); + bool waitForBytesWritten(int msecs); + + bool setBaudRate(); + bool setBaudRate(qint32 baudRate, QSerialPort::Directions directions); + bool setDataBits(QSerialPort::DataBits dataBits); + bool setParity(QSerialPort::Parity parity); + bool setStopBits(QSerialPort::StopBits stopBits); + bool setFlowControl(QSerialPort::FlowControl flowControl); + bool setDataErrorPolicy(QSerialPort::DataErrorPolicy policy); + + bool startAsyncWrite(); + bool completeAsyncWrite(); + void newDataArrived(char *bytesA, int lengthA); + void exceptionArrived(QString strA); + + void stopReadThread(); + void startReadThread(); + qint64 bytesToWrite() const; + qint64 writeData(const char *data, qint64 maxSize); + + static qint32 baudRateFromSetting(qint32 setting); + static qint32 settingFromBaudRate(qint32 baudRate); + + static QList standardBaudRates(); + + int descriptor; + bool isCustomBaudRateSupported; + + bool emittedBytesWritten; + + qint64 pendingBytesWritten; + +private: + QIODevice::OpenMode rwMode; + int deviceId; + bool hasRegisteredFunctions; + int jniDataBits; + int jniStopBits; + int jniParity; + qint64 internalWriteTimeoutMsec; + bool isReadStopped; + + bool setParameters(int baudRateA, int dataBitsA, int stopBitsA, int parityA); + + QSerialPort::SerialPortError decodeSystemError() const; + + qint64 writeToPort(const char *data, qint64 maxSize); + + bool writeDataOneShot(); +}; + +QT_END_NAMESPACE + +#endif // QSERIALPORT_ANDROID_P_H + diff --git a/libs/qtandroidserialport/src/qserialport_p.h b/libs/qtandroidserialport/src/qserialport_p.h new file mode 100644 index 000000000..3ba91b9b2 --- /dev/null +++ b/libs/qtandroidserialport/src/qserialport_p.h @@ -0,0 +1,338 @@ +/**************************************************************************** +** +** Copyright (C) 2011-2012 Denis Shienkov +** Copyright (C) 2011 Sergey Belyashov +** Copyright (C) 2012 Laszlo Papp +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtSerialPort module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSERIALPORT_P_H +#define QSERIALPORT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qserialport.h" + +QT_BEGIN_NAMESPACE + +class QGCRingBuffer +{ +public: + explicit inline QGCRingBuffer(int growth = 4096) : + head(0), tail(0), tailBuffer(0), basicBlockSize(growth), bufferSize(0) { + buffers.append(QByteArray()); + } + + inline int nextDataBlockSize() const { + return (tailBuffer == 0 ? tail : buffers.first().size()) - head; + } + + inline const char *readPointer() const { + return buffers.isEmpty() ? 0 : (buffers.first().constData() + head); + } + + // access the bytes at a specified position + // the out-variable length will contain the amount of bytes readable + // from there, e.g. the amount still the same QByteArray + inline const char *readPointerAtPosition(qint64 pos, qint64 &length) const { + if (pos >= 0) { + pos += head; + for (int i = 0; i < buffers.size(); ++i) { + length = (i == tailBuffer ? tail : buffers[i].size()); + if (length > pos) { + length -= pos; + return buffers[i].constData() + pos; + } + pos -= length; + } + } + + length = 0; + return 0; + } + + inline void free(int bytes) { + while (bytes > 0) { + int blockSize = buffers.first().size() - head; + + if (tailBuffer == 0 || blockSize > bytes) { + bufferSize -= bytes; + if (bufferSize <= 0) + clear(); // try to minify/squeeze us + else + head += bytes; + return; + } + + bufferSize -= blockSize; + bytes -= blockSize; + buffers.removeFirst(); + --tailBuffer; + head = 0; + } + } + + inline char *reserve(int bytes) { + if (bytes <= 0) + return 0; + + // if need buffer reallocation + if (tail + bytes > buffers.last().size()) { + if (tail >= basicBlockSize) { + // shrink this buffer to its current size + buffers.last().resize(tail); + + // create a new QByteArray + buffers.append(QByteArray()); + ++tailBuffer; + tail = 0; + } + buffers.last().resize(qMax(basicBlockSize, tail + bytes)); + } + + char *writePtr = buffers.last().data() + tail; + bufferSize += bytes; + tail += bytes; + return writePtr; + } + + inline void truncate(int pos) { + if (pos < size()) + chop(size() - pos); + } + + inline void chop(int bytes) { + while (bytes > 0) { + if (tailBuffer == 0 || tail > bytes) { + bufferSize -= bytes; + if (bufferSize <= 0) + clear(); // try to minify/squeeze us + else + tail -= bytes; + return; + } + + bufferSize -= tail; + bytes -= tail; + buffers.removeLast(); + --tailBuffer; + tail = buffers.last().size(); + } + } + + inline bool isEmpty() const { + return tailBuffer == 0 && tail == 0; + } + + inline int getChar() { + if (isEmpty()) + return -1; + char c = *readPointer(); + free(1); + return int(uchar(c)); + } + + inline void putChar(char c) { + char *ptr = reserve(1); + *ptr = c; + } + + inline void ungetChar(char c) { + --head; + if (head < 0) { + buffers.prepend(QByteArray()); + buffers.first().resize(basicBlockSize); + head = basicBlockSize - 1; + ++tailBuffer; + } + buffers.first()[head] = c; + ++bufferSize; + } + + inline int size() const { + return bufferSize; + } + + inline void clear() { + buffers.erase(buffers.begin() + 1, buffers.end()); + buffers.first().clear(); + + head = tail = 0; + tailBuffer = 0; + bufferSize = 0; + } + + inline int indexOf(char c) const { + int index = 0; + int j = head; + for (int i = 0; i < buffers.size(); ++i) { + const char *ptr = buffers[i].constData() + j; + j = index + (i == tailBuffer ? tail : buffers[i].size()) - j; + + while (index < j) { + if (*ptr++ == c) + return index; + ++index; + } + j = 0; + } + return -1; + } + + inline int indexOf(char c, int maxLength) const { + int index = 0; + int j = head; + for (int i = 0; index < maxLength && i < buffers.size(); ++i) { + const char *ptr = buffers[i].constData() + j; + j = qMin(index + (i == tailBuffer ? tail : buffers[i].size()) - j, maxLength); + + while (index < j) { + if (*ptr++ == c) + return index; + ++index; + } + j = 0; + } + return -1; + } + + inline int read(char *data, int maxLength) { + int bytesToRead = qMin(size(), maxLength); + int readSoFar = 0; + while (readSoFar < bytesToRead) { + int bytesToReadFromThisBlock = qMin(bytesToRead - readSoFar, nextDataBlockSize()); + if (data) + memcpy(data + readSoFar, readPointer(), bytesToReadFromThisBlock); + readSoFar += bytesToReadFromThisBlock; + free(bytesToReadFromThisBlock); + } + return readSoFar; + } + + // read an unspecified amount (will read the first buffer) + inline QByteArray read() { + if (bufferSize == 0) + return QByteArray(); + + QByteArray qba(buffers.takeFirst()); + + qba.reserve(0); // avoid that resizing needlessly reallocates + if (tailBuffer == 0) { + qba.resize(tail); + tail = 0; + buffers.append(QByteArray()); + } else { + --tailBuffer; + } + qba.remove(0, head); // does nothing if head is 0 + head = 0; + bufferSize -= qba.size(); + return qba; + } + + // append a new buffer to the end + inline void append(const QByteArray &qba) { + if (tail == 0) { + buffers.last() = qba; + } else { + buffers.last().resize(tail); + buffers.append(qba); + ++tailBuffer; + } + tail = qba.size(); + bufferSize += tail; + } + + inline int skip(int length) { + return read(0, length); + } + + inline int readLine(char *data, int maxLength) { + if (!data || --maxLength <= 0) + return -1; + + int i = indexOf('\n', maxLength); + i = read(data, i >= 0 ? (i + 1) : maxLength); + + // Terminate it. + data[i] = '\0'; + return i; + } + + inline bool canReadLine() const { + return indexOf('\n') >= 0; + } + +private: + QList buffers; + int head, tail; + int tailBuffer; // always buffers.size() - 1 + const int basicBlockSize; + int bufferSize; +}; + +class QSerialPortPrivateData +{ +public: + enum IoConstants { + ReadChunkSize = 512 + }; + + QSerialPortPrivateData(QSerialPort *q); + int timeoutValue(int msecs, int elapsed); + + qint64 readBufferMaxSize; + QGCRingBuffer readBuffer; + QGCRingBuffer writeBuffer; + QSerialPort::SerialPortError error; + QString systemLocation; + qint32 inputBaudRate; + qint32 outputBaudRate; + QSerialPort::DataBits dataBits; + QSerialPort::Parity parity; + QSerialPort::StopBits stopBits; + QSerialPort::FlowControl flowControl; + QSerialPort::DataErrorPolicy policy; + bool settingsRestoredOnClose; + QSerialPort * const q_ptr; +}; + +QT_END_NAMESPACE + +#endif // QSERIALPORT_P_H diff --git a/libs/qtandroidserialport/src/qserialportinfo.cpp b/libs/qtandroidserialport/src/qserialportinfo.cpp new file mode 100644 index 000000000..26b275d6d --- /dev/null +++ b/libs/qtandroidserialport/src/qserialportinfo.cpp @@ -0,0 +1,292 @@ +/**************************************************************************** +** +** Copyright (C) 2011-2012 Denis Shienkov +** Copyright (C) 2011 Sergey Belyashov +** Copyright (C) 2012 Laszlo Papp +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtSerialPort module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qserialportinfo.h" +#include "qserialportinfo_p.h" +#include "qserialport.h" + +QT_BEGIN_NAMESPACE + + +/*! + \class QSerialPortInfo + + \brief Provides information about existing serial ports. + + \ingroup serialport-main + \inmodule QtSerialPort + \since 5.1 + + Use the static functions to generate a list of QSerialPortInfo + objects. Each QSerialPortInfo object in the list represents a single + serial port and can be queried for the port name, system location, + description, and manufacturer. The QSerialPortInfo class can also be + used as an input parameter for the setPort() method of the QSerialPort + class. + + \sa QSerialPort +*/ + +/*! + Constructs an empty QSerialPortInfo object. + + \sa isNull() +*/ +QSerialPortInfo::QSerialPortInfo() +{ +} + +/*! + Constructs a copy of \a other. +*/ +QSerialPortInfo::QSerialPortInfo(const QSerialPortInfo &other) + : d_ptr(other.d_ptr ? new QSerialPortInfoPrivate(*other.d_ptr) : Q_NULLPTR) +{ +} + +/*! + Constructs a QSerialPortInfo object from serial \a port. +*/ +QSerialPortInfo::QSerialPortInfo(const QSerialPort &port) +{ + foreach (const QSerialPortInfo &serialPortInfo, availablePorts()) { + if (port.portName() == serialPortInfo.portName()) { + *this = serialPortInfo; + break; + } + } +} + +/*! + Constructs a QSerialPortInfo object from serial port \a name. + + This constructor finds the relevant serial port among the available ones + according to the port name \a name, and constructs the serial port info + instance for that port. +*/ +QSerialPortInfo::QSerialPortInfo(const QString &name) +{ + foreach (const QSerialPortInfo &serialPortInfo, availablePorts()) { + if (name == serialPortInfo.portName()) { + *this = serialPortInfo; + break; + } + } +} + +QSerialPortInfo::QSerialPortInfo(const QSerialPortInfoPrivate &dd) + : d_ptr(new QSerialPortInfoPrivate(dd)) +{ +} + +/*! + Destroys the QSerialPortInfo object. References to the values in the + object become invalid. +*/ +QSerialPortInfo::~QSerialPortInfo() +{ +} + +/*! \fn void QSerialPortInfo::swap(QSerialPortInfo &other) + + Swaps QSerialPortInfo \a other with this QSerialPortInfo. This operation is + very fast and never fails. +*/ +void QSerialPortInfo::swap(QSerialPortInfo &other) +{ + d_ptr.swap(other.d_ptr); +} + +/*! + Sets the QSerialPortInfo object to be equal to \a other. +*/ +QSerialPortInfo& QSerialPortInfo::operator=(const QSerialPortInfo &other) +{ + QSerialPortInfo(other).swap(*this); + return *this; +} + +/*! + Returns the name of the serial port. + + \sa systemLocation() +*/ +QString QSerialPortInfo::portName() const +{ + Q_D(const QSerialPortInfo); + return !d ? QString() : d->portName; +} + +/*! + Returns the system location of the serial port. + + \sa portName() +*/ +QString QSerialPortInfo::systemLocation() const +{ + Q_D(const QSerialPortInfo); + return !d ? QString() : d->device; +} + +/*! + Returns the description string of the serial port, + if available; otherwise returns an empty string. + + \sa manufacturer(), serialNumber() +*/ +QString QSerialPortInfo::description() const +{ + Q_D(const QSerialPortInfo); + return !d ? QString() : d->description; +} + +/*! + Returns the manufacturer string of the serial port, + if available; otherwise returns an empty string. + + \sa description(), serialNumber() +*/ +QString QSerialPortInfo::manufacturer() const +{ + Q_D(const QSerialPortInfo); + return !d ? QString() : d->manufacturer; +} + +/*! + \since 5.3 + + Returns the serial number string of the serial port, + if available; otherwise returns an empty string. + + \note The serial number may include letters. + + \sa description(), manufacturer() +*/ +QString QSerialPortInfo::serialNumber() const +{ + Q_D(const QSerialPortInfo); + return !d ? QString() : d->serialNumber; +} + +/*! + Returns the 16-bit vendor number for the serial port, if available; + otherwise returns zero. + + \sa hasVendorIdentifier(), productIdentifier(), hasProductIdentifier() +*/ +quint16 QSerialPortInfo::vendorIdentifier() const +{ + Q_D(const QSerialPortInfo); + return !d ? 0 : d->vendorIdentifier; +} + +/*! + Returns the 16-bit product number for the serial port, if available; + otherwise returns zero. + + \sa hasProductIdentifier(), vendorIdentifier(), hasVendorIdentifier() +*/ +quint16 QSerialPortInfo::productIdentifier() const +{ + Q_D(const QSerialPortInfo); + return !d ? 0 : d->productIdentifier; +} + +/*! + Returns true if there is a valid 16-bit vendor number present; otherwise + returns false. + + \sa vendorIdentifier(), productIdentifier(), hasProductIdentifier() +*/ +bool QSerialPortInfo::hasVendorIdentifier() const +{ + Q_D(const QSerialPortInfo); + return !d ? false : d->hasVendorIdentifier; +} + +/*! + Returns true if there is a valid 16-bit product number present; otherwise + returns false. + + \sa productIdentifier(), vendorIdentifier(), hasVendorIdentifier() +*/ +bool QSerialPortInfo::hasProductIdentifier() const +{ + Q_D(const QSerialPortInfo); + return !d ? false : d->hasProductIdentifier; +} + +/*! + \fn bool QSerialPortInfo::isNull() const + + Returns whether this QSerialPortInfo object holds a + serial port definition. + + \sa isBusy() +*/ + +/*! + \fn bool QSerialPortInfo::isBusy() const + + Returns true if serial port is busy; + otherwise returns false. + + \sa isNull() +*/ + +/*! + \fn bool QSerialPortInfo::isValid() const + \obsolete + + Returns true if serial port is present on system; + otherwise returns false. + + \sa isNull(), isBusy() +*/ + +/*! + \fn QList QSerialPortInfo::standardBaudRates() + + Returns a list of available standard baud rates supported by + the current serial port. +*/ + +/*! + \fn QList QSerialPortInfo::availablePorts() + + Returns a list of available serial ports on the system. +*/ + +QT_END_NAMESPACE diff --git a/libs/qtandroidserialport/src/qserialportinfo.h b/libs/qtandroidserialport/src/qserialportinfo.h new file mode 100644 index 000000000..6fe1662e8 --- /dev/null +++ b/libs/qtandroidserialport/src/qserialportinfo.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Denis Shienkov +** Copyright (C) 2012 Laszlo Papp +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtSerialPort module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSERIALPORTINFO_H +#define QSERIALPORTINFO_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QSerialPort; +class QSerialPortInfoPrivate; +class QSerialPortInfoPrivateDeleter; + +class QSerialPortInfo +{ + Q_DECLARE_PRIVATE(QSerialPortInfo) +public: + QSerialPortInfo(); + explicit QSerialPortInfo(const QSerialPort &port); + explicit QSerialPortInfo(const QString &name); + QSerialPortInfo(const QSerialPortInfo &other); + ~QSerialPortInfo(); + + QSerialPortInfo& operator=(const QSerialPortInfo &other); + void swap(QSerialPortInfo &other); + + QString portName() const; + QString systemLocation() const; + QString description() const; + QString manufacturer() const; + QString serialNumber() const; + + quint16 vendorIdentifier() const; + quint16 productIdentifier() const; + + bool hasVendorIdentifier() const; + bool hasProductIdentifier() const; + + bool isNull() const; + bool isBusy() const; +#if QT_DEPRECATED_SINCE(5, 2) + QT_DEPRECATED bool isValid() const; +#endif + + static QList standardBaudRates(); + static QList availablePorts(); + + QScopedPointer d_ptr; + +private: + QSerialPortInfo(const QSerialPortInfoPrivate &dd); + friend QList availablePortsByUdev(bool &ok); + friend QList availablePortsBySysfs(bool &ok); + friend QList availablePortsByFiltersOfDevices(bool &ok); +}; + +inline bool QSerialPortInfo::isNull() const +{ return !d_ptr; } + +QT_END_NAMESPACE + +#endif // QSERIALPORTINFO_H diff --git a/libs/qtandroidserialport/src/qserialportinfo_android.cpp b/libs/qtandroidserialport/src/qserialportinfo_android.cpp new file mode 100644 index 000000000..50db905fc --- /dev/null +++ b/libs/qtandroidserialport/src/qserialportinfo_android.cpp @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** Copyright (C) 2011-2012 Denis Shienkov +** Copyright (C) 2011 Sergey Belyashov +** Copyright (C) 2012 Laszlo Papp +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtSerialPort module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qserialportinfo.h" +#include "qserialportinfo_p.h" +#include "qserialport_android_p.h" + +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE +static const char V_jniClassName[] {"org/qgroundcontrol/qgchelper/UsbDeviceJNI"}; +static const char V_TAG[] {"QGC_QSerialPortInfo"}; + +QList availablePortsByFiltersOfDevices() +{ + QList serialPortInfoList; + + __android_log_print(ANDROID_LOG_INFO, V_TAG, "Collecting device list"); + QAndroidJniObject resultL = QAndroidJniObject::callStaticObjectMethod( + V_jniClassName, + "availableDevicesInfo", + "()[Ljava/lang/String;"); + + if (!resultL.isValid()) { + __android_log_print(ANDROID_LOG_ERROR, V_TAG, "Error from availableDevicesInfo"); + return serialPortInfoList; + } + + QAndroidJniEnvironment envL; + jobjectArray objArrayL = resultL.object(); + int countL = envL->GetArrayLength(objArrayL); + + for (int iL=0; iLGetObjectArrayElement(objArrayL, iL)); + const char *rawStringL = envL->GetStringUTFChars(stringL, 0); + __android_log_print(ANDROID_LOG_INFO, V_TAG, "Adding device: %s", rawStringL); + QStringList strListL = QString::fromUtf8(rawStringL).split(QStringLiteral(":")); + envL->ReleaseStringUTFChars(stringL, rawStringL); + + infoL.d_ptr->portName = strListL[0]; + infoL.d_ptr->device = strListL[0]; + infoL.d_ptr->manufacturer = strListL[1]; + infoL.d_ptr->productIdentifier = strListL[2].toInt(); + infoL.d_ptr->hasProductIdentifier = (infoL.d_ptr->productIdentifier != 0) ? true: false; + infoL.d_ptr->vendorIdentifier = strListL[3].toInt(); + infoL.d_ptr->hasVendorIdentifier = (infoL.d_ptr->vendorIdentifier != 0) ? true: false; + + serialPortInfoList.append(infoL); + } + + return serialPortInfoList; +} + +QList availablePortsBySysfs() +{ + return availablePortsByFiltersOfDevices(); +} + +QList availablePortsByUdev() +{ + return availablePortsByFiltersOfDevices(); +} + +QList QSerialPortInfo::availablePorts() +{ + return availablePortsByFiltersOfDevices(); +} + +QList QSerialPortInfo::standardBaudRates() +{ + return QSerialPortPrivate::standardBaudRates(); +} + +bool QSerialPortInfo::isBusy() const +{ + QAndroidJniObject jstrL = QAndroidJniObject::fromString(d_ptr->portName); + jboolean resultL = QAndroidJniObject::callStaticMethod( + V_jniClassName, + "isDeviceNameOpen", + "(Ljava/lang/String;)Z", + jstrL.object()); + return resultL; +} + +bool QSerialPortInfo::isValid() const +{ + QAndroidJniObject jstrL = QAndroidJniObject::fromString(d_ptr->portName); + jboolean resultL = QAndroidJniObject::callStaticMethod( + V_jniClassName, + "isDeviceNameValid", + "(Ljava/lang/String;)Z", + jstrL.object()); + return resultL; +} + +QString QSerialPortInfoPrivate::portNameToSystemLocation(const QString &source) +{ + return (source.startsWith(QLatin1Char('/')) + || source.startsWith(QStringLiteral("./")) + || source.startsWith(QStringLiteral("../"))) + ? source : (QStringLiteral("/dev/") + source); +} + +QString QSerialPortInfoPrivate::portNameFromSystemLocation(const QString &source) +{ + return source.startsWith(QStringLiteral("/dev/")) + ? source.mid(5) : source; +} + +QT_END_NAMESPACE + diff --git a/libs/qtandroidserialport/src/qserialportinfo_p.h b/libs/qtandroidserialport/src/qserialportinfo_p.h new file mode 100644 index 000000000..c9f108638 --- /dev/null +++ b/libs/qtandroidserialport/src/qserialportinfo_p.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2011-2012 Denis Shienkov +** Copyright (C) 2011 Sergey Belyashov +** Copyright (C) 2012 Laszlo Papp +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtSerialPort module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSERIALPORTINFO_P_H +#define QSERIALPORTINFO_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +QT_BEGIN_NAMESPACE + +class Q_AUTOTEST_EXPORT QSerialPortInfoPrivate +{ +public: + QSerialPortInfoPrivate() + : vendorIdentifier(0) + , productIdentifier(0) + , hasVendorIdentifier(false) + , hasProductIdentifier(false) + { + } + + ~QSerialPortInfoPrivate() + { + } + + static QString portNameToSystemLocation(const QString &source); + static QString portNameFromSystemLocation(const QString &source); + + QString portName; + QString device; + QString description; + QString manufacturer; + QString serialNumber; + + quint16 vendorIdentifier; + quint16 productIdentifier; + + bool hasVendorIdentifier; + bool hasProductIdentifier; +}; + +class QSerialPortInfoPrivateDeleter +{ +public: + static void cleanup(QSerialPortInfoPrivate *p) { + delete p; + } +}; + +QT_END_NAMESPACE + +#endif // QSERIALPORTINFO_P_H diff --git a/libs/qtandroidserialport/src/qtandroidserialport.pri b/libs/qtandroidserialport/src/qtandroidserialport.pri new file mode 100644 index 000000000..9bf07c0ad --- /dev/null +++ b/libs/qtandroidserialport/src/qtandroidserialport.pri @@ -0,0 +1,28 @@ +android { + + QT += androidextras + + INCLUDEPATH += $$PWD + + PUBLIC_HEADERS += \ + $$PWD/qserialport.h \ + $$PWD/qserialportinfo.h + + PRIVATE_HEADERS += \ + $$PWD/qserialport_p.h \ + $$PWD/qserialportinfo_p.h \ + $$PWD/qserialport_android_p.h + + SOURCES += \ + $$PWD/qserialport.cpp \ + $$PWD/qserialportinfo.cpp \ + $$PWD/qserialport_android.cpp \ + $$PWD/qserialportinfo_android.cpp + + CONFIG += mobility + + INCLUDEPATH += $$PWD/qt4support/include + + HEADERS += $$PUBLIC_HEADERS $$PRIVATE_HEADERS + +} diff --git a/src/QGC.h b/src/QGC.h index 2ffca0600..35f58e14e 100644 --- a/src/QGC.h +++ b/src/QGC.h @@ -60,6 +60,9 @@ inline bool isinf(T value) #define isinf(x) std::isinf(x) #endif #endif +#ifdef __android__ +#define isinf(x) std::isinf(x) +#endif namespace QGC { diff --git a/src/QGCFileDialog.cc b/src/QGCFileDialog.cc index 25c667ea9..b3160b50c 100644 --- a/src/QGCFileDialog.cc +++ b/src/QGCFileDialog.cc @@ -26,8 +26,10 @@ #include #include "MainWindow.h" #ifdef QT_DEBUG +#ifndef __android__ #include "UnitTest.h" #endif +#endif QString QGCFileDialog::getExistingDirectory( QWidget* parent, @@ -38,9 +40,11 @@ QString QGCFileDialog::getExistingDirectory( _validate(options); #ifdef QT_DEBUG +#ifndef __android__ if (qgcApp()->runningUnitTests()) { return UnitTest::_getExistingDirectory(parent, caption, dir, options); } else +#endif #endif { return QFileDialog::getExistingDirectory(parent, caption, dir, options); @@ -57,9 +61,11 @@ QString QGCFileDialog::getOpenFileName( _validate(options); #ifdef QT_DEBUG +#ifndef __android__ if (qgcApp()->runningUnitTests()) { return UnitTest::_getOpenFileName(parent, caption, dir, filter, options); } else +#endif #endif { return QFileDialog::getOpenFileName(parent, caption, dir, filter, NULL, options); @@ -76,9 +82,11 @@ QStringList QGCFileDialog::getOpenFileNames( _validate(options); #ifdef QT_DEBUG +#ifndef __android__ if (qgcApp()->runningUnitTests()) { return UnitTest::_getOpenFileNames(parent, caption, dir, filter, options); } else +#endif #endif { return QFileDialog::getOpenFileNames(parent, caption, dir, filter, NULL, options); @@ -97,9 +105,11 @@ QString QGCFileDialog::getSaveFileName( _validate(options); #ifdef QT_DEBUG +#ifndef __android__ if (qgcApp()->runningUnitTests()) { return UnitTest::_getSaveFileName(parent, caption, dir, filter, defaultSuffix, options); } else +#endif #endif { QString defaultSuffixCopy(defaultSuffix); diff --git a/src/QGCMessageBox.h b/src/QGCMessageBox.h index dfda32d18..91402a9d0 100644 --- a/src/QGCMessageBox.h +++ b/src/QGCMessageBox.h @@ -29,8 +29,10 @@ #include "MainWindow.h" #include "QGCApplication.h" #ifdef QT_DEBUG +#ifndef __android__ #include "UnitTest.h" #endif +#endif /// @file /// @brief Subclass of QMessageBox which re-implements the static public functions. There are two reasons for this: @@ -104,10 +106,12 @@ private: } #ifdef QT_DEBUG +#ifndef __android__ if (qgcApp()->runningUnitTests()) { qDebug() << "QGCMessageBox (unit testing)" << title << text; return UnitTest::_messageBox(icon, title, text, buttons, defaultButton); } else +#endif #endif { #ifdef Q_OS_MAC diff --git a/src/VehicleSetup/PX4Bootloader.cc b/src/VehicleSetup/PX4Bootloader.cc index deab12ea4..d5cd1346b 100644 --- a/src/VehicleSetup/PX4Bootloader.cc +++ b/src/VehicleSetup/PX4Bootloader.cc @@ -28,7 +28,11 @@ #include "PX4Bootloader.h" #include +#ifdef __android__ +#include "qserialportinfo.h" +#else #include +#endif #include #include diff --git a/src/VehicleSetup/PX4FirmwareUpgradeThread.cc b/src/VehicleSetup/PX4FirmwareUpgradeThread.cc index 784948eb2..0f581facf 100644 --- a/src/VehicleSetup/PX4FirmwareUpgradeThread.cc +++ b/src/VehicleSetup/PX4FirmwareUpgradeThread.cc @@ -29,7 +29,11 @@ #include "PX4Bootloader.h" #include +#ifdef __android__ +#include "qserialportinfo.h" +#else #include +#endif #include PX4FirmwareUpgradeThreadWorker::PX4FirmwareUpgradeThreadWorker(QObject* parent) : diff --git a/src/comm/LinkConfiguration.cc b/src/comm/LinkConfiguration.cc index b3a351d45..5e974f30a 100644 --- a/src/comm/LinkConfiguration.cc +++ b/src/comm/LinkConfiguration.cc @@ -33,8 +33,10 @@ This file is part of the QGROUNDCONTROL project #include "TCPLink.h" #ifdef UNITTEST_BUILD +#ifndef __android__ #include "MockLink.h" #endif +#endif #define LINK_SETTING_ROOT "LinkConfigurations" @@ -92,9 +94,11 @@ LinkConfiguration* LinkConfiguration::createSettings(int type, const QString& na config = new TCPConfiguration(name); break; #ifdef UNITTEST_BUILD +#ifndef __android__ case LinkConfiguration::TypeMock: config = new MockConfiguration(name); break; +#endif #endif } return config; @@ -118,9 +122,11 @@ LinkConfiguration* LinkConfiguration::duplicateSettings(LinkConfiguration* sourc dupe = new TCPConfiguration(dynamic_cast(source)); break; #ifdef UNITTEST_BUILD +#ifndef __android__ case TypeMock: dupe = new MockConfiguration(dynamic_cast(source)); break; +#endif #endif } return dupe; diff --git a/src/comm/LinkManager.cc b/src/comm/LinkManager.cc index 91e63ed5c..10b5af4e4 100644 --- a/src/comm/LinkManager.cc +++ b/src/comm/LinkManager.cc @@ -32,7 +32,11 @@ This file is part of the QGROUNDCONTROL project #include #include #include +#ifdef __android__ +#include "qserialportinfo.h" +#else #include +#endif #include "LinkManager.h" #include "MainWindow.h" @@ -85,9 +89,11 @@ LinkInterface* LinkManager::createConnectedLink(LinkConfiguration* config) pLink = new TCPLink(dynamic_cast(config)); break; #ifdef UNITTEST_BUILD +#ifndef __android__ case LinkConfiguration::TypeMock: pLink = new MockLink(dynamic_cast(config)); break; +#endif #endif } if(pLink) { @@ -379,10 +385,12 @@ void LinkManager::loadLinkConfigurationList() pLink->setPreferred(preferred); break; #ifdef UNITTEST_BUILD +#ifndef __android__ case LinkConfiguration::TypeMock: pLink = (LinkConfiguration*)new MockConfiguration(name); pLink->setPreferred(false); break; +#endif #endif } if(pLink) { diff --git a/src/comm/LinkManager.h b/src/comm/LinkManager.h index 0fba35a4a..3fdca2ab4 100644 --- a/src/comm/LinkManager.h +++ b/src/comm/LinkManager.h @@ -40,8 +40,10 @@ This file is part of the PIXHAWK project #include "TCPLink.h" #ifdef UNITTEST_BUILD +#ifndef __android__ #include "MockLink.h" #endif +#endif #include "ProtocolInterface.h" #include "QGCSingleton.h" diff --git a/src/comm/SerialLink.cc b/src/comm/SerialLink.cc index 415b77c6f..9cd1f2b3f 100644 --- a/src/comm/SerialLink.cc +++ b/src/comm/SerialLink.cc @@ -12,8 +12,14 @@ #include #include #include + +#ifdef __android__ +#include "qserialport.h" +#include "qserialportinfo.h" +#else #include #include +#endif #include "SerialLink.h" #include "QGC.h" diff --git a/src/comm/SerialLink.h b/src/comm/SerialLink.h index 29bdb6a69..8f2011e83 100644 --- a/src/comm/SerialLink.h +++ b/src/comm/SerialLink.h @@ -40,7 +40,11 @@ class SerialLink; #include #include #include +#ifdef __android__ +#include "qserialport.h" +#else #include +#endif #include #include diff --git a/src/main.cc b/src/main.cc index 0f1615dec..5f6dc5f28 100644 --- a/src/main.cc +++ b/src/main.cc @@ -35,7 +35,9 @@ This file is part of the QGROUNDCONTROL project #include "MainWindow.h" #include "configuration.h" #ifdef QT_DEBUG +#ifndef __android__ #include "UnitTest.h" +#endif #include "CmdLineOptParser.h" #ifdef Q_OS_WIN #include @@ -156,6 +158,7 @@ int main(int argc, char *argv[]) int exitCode; +#ifndef __android__ #ifdef QT_DEBUG if (runUnitTests) { if (!app->_initForUnitTests()) { @@ -171,6 +174,7 @@ int main(int argc, char *argv[]) } exitCode = -failures; } else +#endif #endif { if (!app->_initForNormalAppBoot()) { diff --git a/src/qgcunittest/UnitTest.h b/src/qgcunittest/UnitTest.h index bbc78acb1..5e46eff7f 100644 --- a/src/qgcunittest/UnitTest.h +++ b/src/qgcunittest/UnitTest.h @@ -28,6 +28,7 @@ #ifndef UNITTEST_H #define UNITTEST_H +#ifndef __android__ #include #include @@ -186,4 +187,5 @@ private: QSharedPointer _unitTest; }; +#endif // Android #endif diff --git a/src/ui/MainWindow.cc b/src/ui/MainWindow.cc index 144a4f0eb..8d20f4ea7 100644 --- a/src/ui/MainWindow.cc +++ b/src/ui/MainWindow.cc @@ -45,7 +45,9 @@ This file is part of the QGROUNDCONTROL project #include "MAVLinkProtocol.h" #include "QGCWaypointListMulti.h" #include "MainWindow.h" +#ifndef __android__ #include "JoystickWidget.h" +#endif #include "GAudioOutput.h" #include "QGCToolWidget.h" #include "QGCMAVLinkLogPlayer.h" @@ -193,8 +195,9 @@ MainWindow::MainWindow(QSplashScreen* splashScreen) connectCommonActions(); // Connect user interface devices emit initStatusChanged(tr("Initializing joystick interface"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141)); +#ifndef __android__ joystick = new JoystickInput(); - +#endif #ifdef QGC_MOUSE_ENABLED_WIN emit initStatusChanged(tr("Initializing 3D mouse interface"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141)); mouseInput = new Mouse3DInput(this); @@ -315,6 +318,7 @@ MainWindow::~MainWindow() delete _simulationLink; _simulationLink = NULL; } +#ifndef __android__ if (joystick) { joystick->shutdown(); @@ -322,6 +326,7 @@ MainWindow::~MainWindow() delete joystick; joystick = NULL; } +#endif // Delete all UAS objects for (int i=0;i<_commsWidgetList.size();i++) { @@ -945,7 +950,11 @@ void MainWindow::showRoadMap() void MainWindow::showSettings() { +#ifndef __android__ SettingsDialog settings(joystick, this); +#else + SettingsDialog settings(this); +#endif settings.exec(); } @@ -1268,7 +1277,11 @@ void MainWindow::hideSplashScreen(void) void MainWindow::manageLinks() { +#ifndef __android__ SettingsDialog settings(joystick, this, SettingsDialog::ShowCommLinks); +#else + SettingsDialog settings(this, SettingsDialog::ShowCommLinks); +#endif settings.exec(); } diff --git a/src/ui/MainWindow.h b/src/ui/MainWindow.h index 95d78384c..03963e500 100644 --- a/src/ui/MainWindow.h +++ b/src/ui/MainWindow.h @@ -48,7 +48,9 @@ This file is part of the QGROUNDCONTROL project #include "CameraView.h" #include "UASListWidget.h" #include "MAVLinkSimulationLink.h" +#ifndef __android__ #include "input/JoystickInput.h" +#endif #if (defined QGC_MOUSE_ENABLED_WIN) | (defined QGC_MOUSE_ENABLED_LINUX) #include "Mouse6dofInput.h" #endif // QGC_MOUSE_ENABLED_WIN @@ -252,7 +254,9 @@ protected: QPointer fileWidget; +#ifndef __android__ JoystickInput* joystick; ///< The joystick manager for QGC +#endif #ifdef QGC_MOUSE_ENABLED_WIN /** @brief 3d Mouse support (WIN only) */ diff --git a/src/ui/SerialConfigurationWindow.cc b/src/ui/SerialConfigurationWindow.cc index 4b04dc9a2..7f9e21cd7 100644 --- a/src/ui/SerialConfigurationWindow.cc +++ b/src/ui/SerialConfigurationWindow.cc @@ -32,7 +32,12 @@ This file is part of the QGROUNDCONTROL project #include #include #include + +#ifdef __android__ +#include "qserialportinfo.h" +#else #include +#endif #include #include diff --git a/src/ui/SettingsDialog.cc b/src/ui/SettingsDialog.cc index d85618865..56e3fcfb5 100644 --- a/src/ui/SettingsDialog.cc +++ b/src/ui/SettingsDialog.cc @@ -28,7 +28,9 @@ #include "MainWindow.h" #include "ui_SettingsDialog.h" +#ifndef __android__ #include "JoystickWidget.h" +#endif #include "LinkManager.h" #include "MAVLinkProtocol.h" #include "MAVLinkSettingsWidget.h" @@ -39,7 +41,11 @@ #include "QGCMessageBox.h" #include "MainToolBar.h" +#ifndef __android__ SettingsDialog::SettingsDialog(JoystickInput *joystick, QWidget *parent, int showTab, Qt::WindowFlags flags) : +#else +SettingsDialog::SettingsDialog(QWidget *parent, int showTab, Qt::WindowFlags flags) : +#endif QDialog(parent, flags), _mainWindow(MainWindow::instance()), _ui(new Ui::SettingsDialog) @@ -52,13 +58,17 @@ _ui(new Ui::SettingsDialog) move(position.topLeft()); QGCLinkConfiguration* pLinkConf = new QGCLinkConfiguration(this); +#ifndef __android__ JoystickWidget* pJoystickConf = new JoystickWidget(joystick, this); +#endif MAVLinkSettingsWidget* pMavsettings = new MAVLinkSettingsWidget(MAVLinkProtocol::instance(), this); // Add the link settings pane _ui->tabWidget->addTab(pLinkConf, "Comm Links"); +#ifndef __android__ // Add the joystick settings pane _ui->tabWidget->addTab(pJoystickConf, "Controllers"); +#endif // Add the MAVLink settings pane _ui->tabWidget->addTab(pMavsettings, "MAVLink"); @@ -102,9 +112,11 @@ _ui(new Ui::SettingsDialog) case ShowCommLinks: _ui->tabWidget->setCurrentWidget(pLinkConf); break; +#ifndef __android__ case ShowControllers: _ui->tabWidget->setCurrentWidget(pJoystickConf); break; +#endif case ShowMavlink: _ui->tabWidget->setCurrentWidget(pMavsettings); break; diff --git a/src/ui/SettingsDialog.h b/src/ui/SettingsDialog.h index b8a3f8bed..e06128847 100644 --- a/src/ui/SettingsDialog.h +++ b/src/ui/SettingsDialog.h @@ -45,7 +45,11 @@ public: ShowMavlink }; +#ifdef __android__ + SettingsDialog(QWidget *parent = 0, int showTab = ShowDefault, Qt::WindowFlags flags = Qt::Sheet); +#else SettingsDialog(JoystickInput *joystick, QWidget *parent = 0, int showTab = ShowDefault, Qt::WindowFlags flags = Qt::Sheet); +#endif ~SettingsDialog(); public slots: diff --git a/src/ui/configuration/SerialSettingsDialog.cc b/src/ui/configuration/SerialSettingsDialog.cc index 83418c9d9..2b5c6b479 100644 --- a/src/ui/configuration/SerialSettingsDialog.cc +++ b/src/ui/configuration/SerialSettingsDialog.cc @@ -37,8 +37,13 @@ This file is part of the APM_PLANNER project #include "terminalconsole.h" #include "ui_SerialSettingsDialog.h" +#ifdef __android__ +#include "qserialport.h" +#include "qserialportinfo.h" +#else #include #include +#endif #include #include diff --git a/src/ui/configuration/SerialSettingsDialog.h b/src/ui/configuration/SerialSettingsDialog.h index 79eff0e72..3aec3deca 100644 --- a/src/ui/configuration/SerialSettingsDialog.h +++ b/src/ui/configuration/SerialSettingsDialog.h @@ -37,7 +37,11 @@ This file is part of the APM_PLANNER project #define SERIALSETTINGSDIALOG_H #include +#ifdef __android__ +#include "qserialport.h" +#else #include +#endif namespace Ui { class SerialSettingsDialog; diff --git a/src/ui/configuration/terminalconsole.cpp b/src/ui/configuration/terminalconsole.cpp index 54a090f51..fe1a0d7fc 100644 --- a/src/ui/configuration/terminalconsole.cpp +++ b/src/ui/configuration/terminalconsole.cpp @@ -45,8 +45,13 @@ This file is part of the APM_PLANNER project #include #include #include +#ifdef __android__ +#include "qserialport.h" +#include "qserialportinfo.h" +#else #include #include +#endif TerminalConsole::TerminalConsole(QWidget *parent) : QWidget(parent), diff --git a/src/ui/configuration/terminalconsole.h b/src/ui/configuration/terminalconsole.h index f100a6ef2..909416edd 100644 --- a/src/ui/configuration/terminalconsole.h +++ b/src/ui/configuration/terminalconsole.h @@ -39,7 +39,11 @@ This file is part of the APM_PLANNER project #include "SerialSettingsDialog.h" #include +#ifdef __android__ +#include "qserialport.h" +#else #include +#endif namespace Ui { class TerminalConsole; diff --git a/src/ui/designer/QGCParamSlider.h b/src/ui/designer/QGCParamSlider.h index 9ca92423b..ab3048dbb 100644 --- a/src/ui/designer/QGCParamSlider.h +++ b/src/ui/designer/QGCParamSlider.h @@ -3,7 +3,9 @@ #include #include +#ifndef __android__ #include +#endif #include "QGCToolWidgetItem.h" diff --git a/src/ui/designer/QGCToolWidgetComboBox.h b/src/ui/designer/QGCToolWidgetComboBox.h index 785d606a4..9b4d162f4 100644 --- a/src/ui/designer/QGCToolWidgetComboBox.h +++ b/src/ui/designer/QGCToolWidgetComboBox.h @@ -3,8 +3,9 @@ #include #include +#ifndef __android__ #include - +#endif #include "QGCToolWidgetItem.h" class QGCUASParamManagerInterface; diff --git a/src/ui/toolbar/MainToolBar.cc b/src/ui/toolbar/MainToolBar.cc index 560c664e9..876214ac7 100644 --- a/src/ui/toolbar/MainToolBar.cc +++ b/src/ui/toolbar/MainToolBar.cc @@ -64,8 +64,13 @@ MainToolBar::MainToolBar(QWidget* parent) { setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); setObjectName("MainToolBar"); +#ifdef __android__ + setMinimumHeight(120); + setMaximumHeight(120); +#else setMinimumHeight(40); setMaximumHeight(40); +#endif setMinimumWidth(MainWindow::instance()->minimumWidth()); // Get rid of layout default margins QLayout* pl = layout(); diff --git a/src/ui/toolbar/MainToolBar.qml b/src/ui/toolbar/MainToolBar.qml index efed1d7f7..f58909947 100644 --- a/src/ui/toolbar/MainToolBar.qml +++ b/src/ui/toolbar/MainToolBar.qml @@ -38,13 +38,14 @@ import QGroundControl.MainToolBar 1.0 import QGroundControl.ScreenTools 1.0 Rectangle { + id: toolBarHolder property var qgcPal: QGCPalette { id: palette; colorGroupEnabled: true } property ScreenTools screenTools: ScreenTools { } - property int cellSpacerSize: 4 - property int cellHeight: 30 - property int cellRadius: 3 + property int cellSpacerSize: getProportionalDimmension(4) + property int cellHeight: getProportionalDimmension(30) + property int cellRadius: getProportionalDimmension(3) property var colorBlue: "#1a6eaa" property var colorGreen: "#079527" @@ -57,9 +58,16 @@ Rectangle { property var colorGreenText: (qgcPal.globalTheme === QGCPalette.Light) ? "#046b1b" : "#00d930" property var colorWhiteText: (qgcPal.globalTheme === QGCPalette.Light) ? "#343333" : "#f0f0f0" - id: toolBarHolder color: qgcPal.windowShade + Component.onCompleted: { + console.log(cellSpacerSize, cellHeight, cellRadius); + } + + function getProportionalDimmension(val) { + return toolBarHolder.height * val / 40 + } + function getMessageColor() { if(mainToolBar.messageType === MainToolBar.MessageNone) return qgcPal.button; @@ -126,14 +134,14 @@ Rectangle { id: row1 height: cellHeight anchors.left: parent.left - spacing: 4 + spacing: getProportionalDimmension(4) anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 10 + anchors.leftMargin: getProportionalDimmension(10) Row { id: row11 height: cellHeight - spacing: -12 + spacing: -getProportionalDimmension(12) anchors.verticalCenter: parent.verticalCenter Connections { target: screenTools @@ -149,7 +157,7 @@ Rectangle { QGCToolBarButton { id: setupButton - width: 90 + width: getProportionalDimmension(90) height: cellHeight exclusiveGroup: mainActionGroup text: qsTr("Setup") @@ -163,7 +171,7 @@ Rectangle { QGCToolBarButton { id: planButton - width: 90 + width: getProportionalDimmension(90) height: cellHeight exclusiveGroup: mainActionGroup text: qsTr("Plan") @@ -177,7 +185,7 @@ Rectangle { QGCToolBarButton { id: flyButton - width: 90 + width: getProportionalDimmension(90) height: cellHeight exclusiveGroup: mainActionGroup text: qsTr("Fly") @@ -191,7 +199,7 @@ Rectangle { QGCToolBarButton { id: analyzeButton - width: 90 + width: getProportionalDimmension(90) height: cellHeight exclusiveGroup: mainActionGroup text: qsTr("Analyze") @@ -213,7 +221,7 @@ Rectangle { Rectangle { id: messages - width: (mainToolBar.messageCount > 99) ? 70 : 60 + width: (mainToolBar.messageCount > 99) ? getProportionalDimmension(70) : getProportionalDimmension(60) height: cellHeight visible: (mainToolBar.connectionCount > 0) && (mainToolBar.showMessages) anchors.verticalCenter: parent.verticalCenter @@ -226,11 +234,11 @@ Rectangle { Image { id: messageIcon source: getMessageIcon(); - height: 16 + height: getProportionalDimmension(16) fillMode: Image.PreserveAspectFit anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left - anchors.leftMargin: 10 + anchors.leftMargin: getProportionalDimmension(10) } Rectangle { @@ -256,8 +264,8 @@ Rectangle { visible: (messages.showTriangle) && (mainToolBar.messageCount > 0) anchors.bottom: parent.bottom anchors.right: parent.right - anchors.bottomMargin: 3 - anchors.rightMargin: 3 + anchors.bottomMargin: getProportionalDimmension(3) + anchors.rightMargin: getProportionalDimmension(3) } Timer { @@ -306,7 +314,7 @@ Rectangle { Rectangle { id: satelitte - width: 60 + width: getProportionalDimmension(60) height: cellHeight visible: showMavStatus() && (mainToolBar.showGPS) anchors.verticalCenter: parent.verticalCenter @@ -317,11 +325,11 @@ Rectangle { Image { source: "qrc:/res/Gps"; - height: 24 + height: getProportionalDimmension(24) fillMode: Image.PreserveAspectFit anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left - anchors.leftMargin: 10 + anchors.leftMargin: getProportionalDimmension(10) mipmap: true smooth: true } @@ -333,7 +341,7 @@ Rectangle { font.weight: Font.DemiBold anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right - anchors.rightMargin: 10 + anchors.rightMargin: getProportionalDimmension(10) horizontalAlignment: Text.AlignRight color: colorWhite } @@ -341,7 +349,7 @@ Rectangle { Rectangle { id: battery - width: 80 + width: getProportionalDimmension(80) height: cellHeight visible: showMavStatus() && (mainToolBar.showBattery) anchors.verticalCenter: parent.verticalCenter @@ -352,11 +360,11 @@ Rectangle { Image { source: getBatteryIcon(); - height: 20 + height: getProportionalDimmension(20) fillMode: Image.PreserveAspectFit anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left - anchors.leftMargin: 6 + anchors.leftMargin: getProportionalDimmension(6) mipmap: true smooth: true } @@ -368,7 +376,7 @@ Rectangle { font.weight: Font.DemiBold anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right - anchors.rightMargin: 8 + anchors.rightMargin: getProportionalDimmension(8) horizontalAlignment: Text.AlignRight color: colorWhite } @@ -377,7 +385,7 @@ Rectangle { Column { visible: showMavStatus() height: cellHeight * 0.85 - width: 80 + width: getProportionalDimmension(80) anchors.verticalCenter: parent.verticalCenter Rectangle { @@ -422,7 +430,7 @@ Rectangle { Rectangle { id: modeStatus - width: 90 + width: getProportionalDimmension(90) height: cellHeight visible: showMavStatus() color: "#00000000" @@ -442,7 +450,7 @@ Rectangle { Rectangle { id: connectionStatus - width: 160 + width: getProportionalDimmension(160) height: cellHeight visible: (mainToolBar.connectionCount > 0 && mainToolBar.mavPresent && mainToolBar.heartbeatTimeout != 0) anchors.verticalCenter: parent.verticalCenter @@ -469,8 +477,8 @@ Rectangle { spacing: cellSpacerSize anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 10 - anchors.rightMargin: 10 + anchors.leftMargin: getProportionalDimmension(10) + anchors.rightMargin: getProportionalDimmension(10) Menu { id: connectMenu @@ -501,7 +509,7 @@ Rectangle { QGCButton { id: connectButton - width: 100 + width: getProportionalDimmension(100) visible: mainToolBar.connectionCount === 0 text: qsTr("Connect") menu: connectMenu @@ -510,7 +518,7 @@ Rectangle { QGCButton { id: disconnectButton - width: 100 + width: getProportionalDimmension(100) visible: mainToolBar.connectionCount === 1 text: qsTr("Disconnect") anchors.verticalCenter: parent.verticalCenter @@ -539,7 +547,7 @@ Rectangle { QGCButton { id: multidisconnectButton - width: 100 + width: getProportionalDimmension(100) text: "Disconnect" visible: mainToolBar.connectionCount > 1 menu: disconnectMenu @@ -550,8 +558,7 @@ Rectangle { // Progress bar Rectangle { - readonly property int progressBarHeight: 3 - + readonly property int progressBarHeight: getProportionalDimmension(3) y: parent.height - progressBarHeight height: progressBarHeight width: parent.width * mainToolBar.progressBarValue -- 2.22.0