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 6dbe2fa68b..9d57a82195 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 b1e891af7e..ce362f1fa9 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 ae7e716afc..cce994557c 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 0000000000..21fcc18846 --- /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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 ST&4^R3=E#GelF{r5}E)NlVXqn literal 0 HcmV?d00001 diff --git a/android/res/xml/device_filter.xml b/android/res/xml/device_filter.xml new file mode 100644 index 0000000000..a149a80b50 --- /dev/null +++ b/android/res/xml/device_filter.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + 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 0000000000..3dec53a9a7 --- /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 0000000000..734933a222 --- /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 0000000000..aa151ba1a0 --- /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 0000000000..ad3627ff38 --- /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 0000000000..d6e2d1338d --- /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 0000000000..618fbc844b --- /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 0000000000..ed4426fc4d --- /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 0000000000..0cfb366e45 --- /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 0000000000..b48607c59d --- /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 0000000000..79d2597a11 --- /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 97d7ac1a34..78c4865681 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 0000000000..992f06bfcf --- /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 0000000000..c637a3f79d --- /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 0000000000..53b64440d1 --- /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 0000000000..f105c1fd74 --- /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 0000000000..3ba91b9b28 --- /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 0000000000..26b275d6d3 --- /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 0000000000..6fe1662e82 --- /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 0000000000..50db905fce --- /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 0000000000..c9f108638f --- /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 0000000000..9bf07c0ad8 --- /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 2ffca0600b..35f58e14ed 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 25c667ea92..b3160b50c5 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 dfda32d18a..91402a9d03 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 deab12ea49..d5cd1346b8 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 784948eb23..0f581facf9 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 b3a351d456..5e974f30af 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 91e63ed5c2..10b5af4e48 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 0fba35a4ab..3fdca2ab4f 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 415b77c6fe..9cd1f2b3fb 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 29bdb6a69a..8f2011e83b 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 0f1615dec9..5f6dc5f287 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 bbc78acb19..5e46eff7fd 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 144a4f0ebf..8d20f4ea78 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 95d78384cc..03963e500d 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 4b04dc9a20..7f9e21cd7f 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 d856188655..56e3fcfb51 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 b8a3f8bedd..e061288471 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 83418c9d99..2b5c6b4792 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 79eff0e72c..3aec3deca9 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 54a090f516..fe1a0d7fc8 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 f100a6ef24..909416edd9 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 9ca92423bf..ab3048dbb0 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 785d606a4e..9b4d162f4c 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 560c664e9a..876214ac71 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 efed1d7f74..f589099473 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 -- GitLab