- Add the serial port support (via USB OTG).
This commit is contained in:
parent
9dc51f38bf
commit
0be3e7dea6
33 changed files with 4294 additions and 112 deletions
22
LICENSE-MIT.TXT
Normal file
22
LICENSE-MIT.TXT
Normal file
|
@ -0,0 +1,22 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2011-2013 Google Inc.
|
||||
Copyright (c) 2013 Mike Wakerly
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
34
ReadMe.txt
34
ReadMe.txt
|
@ -30,26 +30,18 @@ NOTES
|
|||
And then, to use with the HP48SX or the HP48GX, you must select this generated file in the "Settings/Port2 File".
|
||||
- By default when you create a new HP49/50 with the embedded readonly file "rom.49g",
|
||||
everything that you store in port 2 is lost just because the file "rom.49g" is READONLY.
|
||||
But it works exactly like with Windows. If you can write in the ROM file,
|
||||
it should save the content of port 2 in the ROM file with Android too.
|
||||
To save the port 2 in the HP49/50 with Emu48 for Android:
|
||||
* copy "real50g-lc.kml", "real50g-lc.png", "keyb4950.kmi", "rom.49g" in a FOLDER of your Android device,
|
||||
* in the menu:
|
||||
- touch "New..." to create a new device
|
||||
- or touch "Change KML Script..." to change the current KML script and ROM location
|
||||
* select "[Custom KML script...]"
|
||||
* select the FOLDER
|
||||
* pick the calculator (which should be "Eric's Real 50g (Large Cropped)")!
|
||||
And because, the file "FOLDER/rom.49g" is not readonly anymore, you can save your port 2.
|
||||
BUT for the moment, it is saved ONLY when you CLOSE (or change) the state file. Not when you end the application.
|
||||
Since version 2.0, it is now possible from the menu to manage the Flash ROM file (it will fully replaces the ROM).
|
||||
- To speed up printing, set the 'delay' to 0 in the calculator's print options.
|
||||
- The serial ports, wire or infrared (infrared limited to 2400 baud) are now supported through the USB port in mode OTG.
|
||||
It uses the library https://github.com/mik3y/usb-serial-for-android, which allows to plug most of the serial USB adapters (only tested with Prolific),
|
||||
without the need to be root. If it is not automatic, please, activate the OTG mode in your Android device, and then,
|
||||
you should be able to see it in the Emu48 settings. If the adapter is unplugged then plugged back in, you may need to set the option again.
|
||||
|
||||
|
||||
NOT WORKING YET
|
||||
|
||||
- Disassembler
|
||||
- Debugger
|
||||
- Serial Ports (Wire or Ir)
|
||||
|
||||
|
||||
LINKS
|
||||
|
@ -63,6 +55,18 @@ LINKS
|
|||
|
||||
CHANGES
|
||||
|
||||
Version 2.3 (2021-02-xx)
|
||||
|
||||
- Add the serial port support (via USB OTG).
|
||||
TODO: When stop the app, Serial exception seems to delay the save of the calc state!!!!
|
||||
TODO: Inform if the connection is not possible.
|
||||
TODO: What's happen if I hot unplug?
|
||||
TODO: Check the self test about UART (http://regis.cosnier.free.fr/private/private.php?journal=HP48&index=-4637&nomenu)
|
||||
- Allows pressing a calculator button with the right button of the mouse but prevents its release to allow the On+A+F key combination (with Android version >= 5.0).
|
||||
- Update the embedded help file "Emu48.html" to the latest version.
|
||||
- Open an external web browser when you click an external links in the Help.
|
||||
|
||||
|
||||
Version 2.2 (2020-12-09)
|
||||
|
||||
- The KML folder is now well saved when changing the KML script for a custom one via the menu "Change KML Script...".
|
||||
|
@ -226,17 +230,17 @@ You should have received a copy of the GNU General Public License along with thi
|
|||
Note: some included files are not covered by the GPL; these include ROM image files (copyrighted by HP), KML files and faceplate images (copyrighted by their authors).
|
||||
The Eric's Real scripts ("real*.kml" and "real*.bmp/png") are embedded in this application with the kind permission of Eric Rechlin.
|
||||
|
||||
Portions of this source code (about the usb-serial) were originally created by Google Inc. in 2011-2013 and Mike Wakerly in 2013.
|
||||
|
||||
TODO
|
||||
|
||||
- Add a Cancel button to the HP48 memory card creator dialog.
|
||||
- Show KML log on request.
|
||||
- Serial port support (via USB OTG and maybe Bluetooth).
|
||||
- Manage the HP 48 port 2 with the same kind of interface for the memory card.
|
||||
- The render pixels are very nice. A solution to obtain uniform pixel size could be a preset (a multiplier, auto) so the user could decide and upscale/downscale (Michael P).
|
||||
- Somehow LEFT (Shift on the keyboard) + 7 activates the DIVIDE-key (z-Key)..., but with the NUM-Key it can make it work without problems...
|
||||
I think it might have something to do with the "/" sign on the Shifted-7-key.
|
||||
- The clock seems unsynchronized sometimes (Michael P).
|
||||
- The clock seems not synchronized sometimes (Michael P).
|
||||
- Retain a key by right clicking if it is from a mouse.
|
||||
- Sometimes, the calculator seems to lag and finally freeze.
|
||||
- In Chrome OS, sometimes there is no OK button in the KML Script Compilation Result.
|
||||
|
|
|
@ -2,7 +2,10 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.emulator.forty.eight">
|
||||
|
||||
<uses-feature android:name="android.hardware.usb.host" android:required="false"/>
|
||||
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
|
||||
<application
|
||||
android:name="org.emulator.calculator.EmuApplication"
|
||||
android:allowBackup="true"
|
||||
|
|
|
@ -30,26 +30,18 @@ NOTES
|
|||
And then, to use with the HP48SX or the HP48GX, you must select this generated file in the "Settings/Port2 File".
|
||||
- By default when you create a new HP49/50 with the embedded readonly file "rom.49g",
|
||||
everything that you store in port 2 is lost just because the file "rom.49g" is READONLY.
|
||||
But it works exactly like with Windows. If you can write in the ROM file,
|
||||
it should save the content of port 2 in the ROM file with Android too.
|
||||
To save the port 2 in the HP49/50 with Emu48 for Android:
|
||||
* copy "real50g-lc.kml", "real50g-lc.png", "keyb4950.kmi", "rom.49g" in a FOLDER of your Android device,
|
||||
* in the menu:
|
||||
- touch "New..." to create a new device
|
||||
- or touch "Change KML Script..." to change the current KML script and ROM location
|
||||
* select "[Custom KML script...]"
|
||||
* select the FOLDER
|
||||
* pick the calculator (which should be "Eric's Real 50g (Large Cropped)")!
|
||||
And because, the file "FOLDER/rom.49g" is not readonly anymore, you can save your port 2.
|
||||
BUT for the moment, it is saved ONLY when you CLOSE (or change) the state file. Not when you end the application.
|
||||
Since version 2.0, it is now possible from the menu to manage the Flash ROM file (it will fully replaces the ROM).
|
||||
- To speed up printing, set the 'delay' to 0 in the calculator's print options.
|
||||
- The serial ports, wire or infrared (infrared limited to 2400 baud) are now supported through the USB port in mode OTG.
|
||||
It uses the library https://github.com/mik3y/usb-serial-for-android, which allows to plug most of the serial USB adapters (only tested with Prolific),
|
||||
without the need to be root. If it is not automatic, please, activate the OTG mode in your Android device, and then,
|
||||
you should be able to see it in the Emu48 settings. If the adapter is unplugged then plugged back in, you may need to set the option again.
|
||||
|
||||
|
||||
NOT WORKING YET
|
||||
|
||||
- Disassembler
|
||||
- Debugger
|
||||
- Serial Ports (Wire or Ir)
|
||||
|
||||
|
||||
LINKS
|
||||
|
@ -63,6 +55,18 @@ LINKS
|
|||
|
||||
CHANGES
|
||||
|
||||
Version 2.3 (2021-02-xx)
|
||||
|
||||
- Add the serial port support (via USB OTG).
|
||||
TODO: When stop the app, Serial exception seems to delay the save of the calc state!!!!
|
||||
TODO: Inform if the connection is not possible.
|
||||
TODO: What's happen if I hot unplug?
|
||||
TODO: Check the self test about UART (http://regis.cosnier.free.fr/private/private.php?journal=HP48&index=-4637&nomenu)
|
||||
- Allows pressing a calculator button with the right button of the mouse but prevents its release to allow the On+A+F key combination (with Android version >= 5.0).
|
||||
- Update the embedded help file "Emu48.html" to the latest version.
|
||||
- Open an external web browser when you click an external links in the Help.
|
||||
|
||||
|
||||
Version 2.2 (2020-12-09)
|
||||
|
||||
- The KML folder is now well saved when changing the KML script for a custom one via the menu "Change KML Script...".
|
||||
|
@ -225,3 +229,5 @@ You should have received a copy of the GNU General Public License along with thi
|
|||
|
||||
Note: some included files are not covered by the GPL; these include ROM image files (copyrighted by HP), KML files and faceplate images (copyrighted by their authors).
|
||||
The Eric's Real scripts ("real*.kml" and "real*.bmp/png") are embedded in this application with the kind permission of Eric Rechlin.
|
||||
|
||||
Portions of this source code (about the usb-serial) were originally created by Google Inc. in 2011-2013 and Mike Wakerly in 2013.
|
||||
|
|
|
@ -291,6 +291,119 @@ void setKMLIcon(int imageWidth, int imageHeight, LPBYTE buffer, int bufferSize)
|
|||
}
|
||||
}
|
||||
|
||||
int openSerialPort(const TCHAR * serialPort) {
|
||||
int result = -1;
|
||||
JNIEnv *jniEnv = getJNIEnvironment();
|
||||
if(jniEnv) {
|
||||
jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity);
|
||||
if(mainActivityClass) {
|
||||
jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "openSerialPort", "(Ljava/lang/String;)I");
|
||||
jstring utfFileURL = (*jniEnv)->NewStringUTF(jniEnv, serialPort);
|
||||
result = (*jniEnv)->CallIntMethod(jniEnv, mainActivity, midStr, utfFileURL);
|
||||
(*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int closeSerialPort(int serialPortId) {
|
||||
int result = -1;
|
||||
JNIEnv *jniEnv = getJNIEnvironment();
|
||||
if(jniEnv) {
|
||||
jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity);
|
||||
if(mainActivityClass) {
|
||||
jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "closeSerialPort", "(I)I");
|
||||
result = (*jniEnv)->CallIntMethod(jniEnv, mainActivity, midStr, serialPortId);
|
||||
(*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int setSerialPortParameters(int serialPortId, int baudRate) {
|
||||
int result = -1;
|
||||
JNIEnv *jniEnv = getJNIEnvironment();
|
||||
if(jniEnv) {
|
||||
jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity);
|
||||
if(mainActivityClass) {
|
||||
jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "setSerialPortParameters", "(II)I");
|
||||
result = (*jniEnv)->CallIntMethod(jniEnv, mainActivity, midStr, serialPortId, baudRate);
|
||||
(*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int readSerialPort(int serialPortId, LPBYTE buffer, int nNumberOfBytesToRead) {
|
||||
int nNumberOfReadBytes = 0;
|
||||
JNIEnv *jniEnv = getJNIEnvironment();
|
||||
if(jniEnv && buffer && nNumberOfBytesToRead > 0) {
|
||||
jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity);
|
||||
if(mainActivityClass) {
|
||||
jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "readSerialPort", "(II)[B");
|
||||
jbyteArray readBytes = (jbyteArray)(*jniEnv)->CallObjectMethod(jniEnv, mainActivity, midStr, serialPortId, nNumberOfBytesToRead);
|
||||
nNumberOfReadBytes = (*jniEnv)->GetArrayLength(jniEnv, readBytes);
|
||||
jbyte* elements = (*jniEnv)->GetByteArrayElements(jniEnv, readBytes, NULL);
|
||||
if (elements) {
|
||||
for(int i = 0; i < nNumberOfReadBytes; i++)
|
||||
buffer[i] = elements[i];
|
||||
(*jniEnv)->ReleaseByteArrayElements(jniEnv, readBytes, elements, JNI_ABORT);
|
||||
}
|
||||
(*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass);
|
||||
}
|
||||
}
|
||||
return nNumberOfReadBytes;
|
||||
}
|
||||
|
||||
int writeSerialPort(int serialPortId, LPBYTE buffer, int bufferSize) {
|
||||
int result = 0;
|
||||
JNIEnv *jniEnv = getJNIEnvironment();
|
||||
if(jniEnv) {
|
||||
jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity);
|
||||
if(mainActivityClass) {
|
||||
jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "writeSerialPort", "(I[B)I");
|
||||
|
||||
jbyteArray javaBuffer = NULL;
|
||||
if(buffer) {
|
||||
javaBuffer = (*jniEnv)->NewByteArray(jniEnv, bufferSize);
|
||||
(*jniEnv)->SetByteArrayRegion(jniEnv, javaBuffer, 0, bufferSize, (jbyte *) buffer);
|
||||
}
|
||||
result = (*jniEnv)->CallIntMethod(jniEnv, mainActivity, midStr, serialPortId, javaBuffer);
|
||||
(*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int serialPortSetBreak(int serialPortId) {
|
||||
int result = 0;
|
||||
JNIEnv *jniEnv = getJNIEnvironment();
|
||||
if(jniEnv) {
|
||||
jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity);
|
||||
if(mainActivityClass) {
|
||||
jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "serialPortSetBreak", "(I)I");
|
||||
result = (*jniEnv)->CallIntMethod(jniEnv, mainActivity, midStr, serialPortId);
|
||||
(*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int serialPortClearBreak(int serialPortId) {
|
||||
int result = 0;
|
||||
JNIEnv *jniEnv = getJNIEnvironment();
|
||||
if(jniEnv) {
|
||||
jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity);
|
||||
if(mainActivityClass) {
|
||||
jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "serialPortClearBreak", "(I)I");
|
||||
result = (*jniEnv)->CallIntMethod(jniEnv, mainActivity, midStr, serialPortId);
|
||||
(*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_start(JNIEnv *env, jobject thisz, jobject assetMgr, jobject activity) {
|
||||
|
||||
chooseCurrentKmlMode = ChooseKmlMode_UNKNOWN;
|
||||
|
@ -1169,6 +1282,10 @@ JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_setConfiguration(J
|
|||
//LOGE("NativeLib_setConfiguration port2 end SwitchToState %d", nOldState);
|
||||
SwitchToState(nOldState);
|
||||
}
|
||||
} else if(_tcscmp(_T("settings_serial_ports_wire"), configKey) == 0) {
|
||||
_tcsncpy(szSerialWire, _tcscmp(_T("0,0"), configStringValue) == 0 ? NO_SERIAL : configStringValue, sizeof(szSerialWire));
|
||||
} else if(_tcscmp(_T("settings_serial_ports_ir"), configKey) == 0) {
|
||||
_tcsncpy(szSerialIr, _tcscmp(_T("0,0"), configStringValue) == 0 ? NO_SERIAL : configStringValue, sizeof(szSerialIr));
|
||||
}
|
||||
|
||||
if(configKey)
|
||||
|
@ -1211,3 +1328,7 @@ JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_getLCDBackgroundCo
|
|||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_commEvent(JNIEnv *env, jclass clazz, jint commId, jint eventMask) {
|
||||
commEvent(commId, eventMask);
|
||||
}
|
|
@ -42,6 +42,8 @@ const TCHAR * contentScheme = _T("content://");
|
|||
size_t contentSchemeLength;
|
||||
const TCHAR * documentScheme = _T("document:");
|
||||
size_t documentSchemeLength;
|
||||
const TCHAR * comPrefix = _T("\\\\.\\");
|
||||
size_t comPrefixLength;
|
||||
TCHAR szFilePathTmp[MAX_PATH];
|
||||
|
||||
|
||||
|
@ -66,6 +68,7 @@ void win32Init() {
|
|||
assetsPrefixLength = _tcslen(assetsPrefix);
|
||||
contentSchemeLength = _tcslen(contentScheme);
|
||||
documentSchemeLength = _tcslen(documentScheme);
|
||||
comPrefixLength = _tcslen(comPrefix);
|
||||
}
|
||||
|
||||
int abs (int i) {
|
||||
|
@ -109,8 +112,42 @@ BOOL SetCurrentDirectory(LPCTSTR path) {
|
|||
extern BOOL settingsPort2en;
|
||||
extern BOOL settingsPort2wr;
|
||||
|
||||
#define MAX_CREATED_COMM 30
|
||||
static HANDLE comms[MAX_CREATED_COMM];
|
||||
static pthread_mutex_t commsLock;
|
||||
|
||||
|
||||
HANDLE CreateFile(LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPVOID lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, LPVOID hTemplateFile) {
|
||||
FILE_LOGD("CreateFile(lpFileName: \"%s\", dwDesiredAccess: 0x%08x)", lpFileName, dwShareMode);
|
||||
|
||||
BOOL foundCOMPrefix = _tcsncmp(lpFileName, comPrefix, comPrefixLength) == 0;
|
||||
if(foundCOMPrefix) {
|
||||
|
||||
int serialPortId = openSerialPort(lpFileName);
|
||||
if(serialPortId > 0) {
|
||||
// We try to open a COM/Serial port
|
||||
HANDLE handle = malloc(sizeof(struct _HANDLE));
|
||||
memset(handle, 0, sizeof(struct _HANDLE));
|
||||
handle->handleType = HANDLE_TYPE_COM;
|
||||
handle->commEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
handle->commId = serialPortId;
|
||||
|
||||
pthread_mutex_lock(&commsLock);
|
||||
handle->commIndex = -1;
|
||||
for(int i = 0; i < MAX_CREATED_COMM; i++) {
|
||||
if(comms[i] == NULL) {
|
||||
handle->commIndex = i;
|
||||
comms[handle->commIndex] = handle;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&commsLock);
|
||||
|
||||
return handle;
|
||||
} else
|
||||
return (HANDLE) INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
BOOL forceNormalFile = FALSE;
|
||||
securityExceptionOccured = FALSE;
|
||||
#if EMUXX == 48
|
||||
|
@ -249,6 +286,8 @@ BOOL ReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD
|
|||
readByteCount = (DWORD) read(hFile->fileDescriptor, lpBuffer, nNumberOfBytesToRead);
|
||||
} else if(hFile->handleType == HANDLE_TYPE_FILE_ASSET) {
|
||||
readByteCount = (DWORD) AAsset_read(hFile->fileAsset, lpBuffer, nNumberOfBytesToRead);
|
||||
} else if(hFile->handleType == HANDLE_TYPE_COM) {
|
||||
readByteCount = (DWORD) readSerialPort(hFile->commId, lpBuffer, nNumberOfBytesToRead);
|
||||
}
|
||||
if(lpNumberOfBytesRead)
|
||||
*lpNumberOfBytesRead = readByteCount;
|
||||
|
@ -257,12 +296,19 @@ BOOL ReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD
|
|||
|
||||
BOOL WriteFile(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped) {
|
||||
FILE_LOGD("WriteFile(hFile: %p, lpBuffer: 0x%08x, nNumberOfBytesToWrite: %d)", hFile, lpBuffer, nNumberOfBytesToWrite);
|
||||
if(hFile->handleType == HANDLE_TYPE_FILE_ASSET)
|
||||
return FALSE;
|
||||
if(hFile->handleType == HANDLE_TYPE_FILE) {
|
||||
ssize_t writenByteCount = write(hFile->fileDescriptor, lpBuffer, nNumberOfBytesToWrite);
|
||||
if(lpNumberOfBytesWritten)
|
||||
*lpNumberOfBytesWritten = (DWORD) writenByteCount;
|
||||
return writenByteCount >= 0;
|
||||
} else if(hFile->handleType == HANDLE_TYPE_COM) {
|
||||
ssize_t writenByteCount = writeSerialPort(hFile->commId, lpBuffer, nNumberOfBytesToWrite);
|
||||
commEvent(hFile->commId, EV_TXEMPTY); // Not sure about that (not the same thread)!
|
||||
if(lpNumberOfBytesWritten)
|
||||
*lpNumberOfBytesWritten = (DWORD) writenByteCount;
|
||||
return writenByteCount >= 0;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
DWORD SetFilePointer(HANDLE hFile, LONG lDistanceToMove, PLONG lpDistanceToMoveHigh, DWORD dwMoveMethod) {
|
||||
|
@ -472,8 +518,7 @@ BOOL SetEvent(HANDLE hEvent) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
BOOL ResetEvent(HANDLE hEvent)
|
||||
{
|
||||
BOOL ResetEvent(HANDLE hEvent) {
|
||||
if(hEvent) {
|
||||
int result = pthread_mutex_lock(&hEvent->eventMutex);
|
||||
_ASSERT(result == 0);
|
||||
|
@ -488,8 +533,7 @@ BOOL ResetEvent(HANDLE hEvent)
|
|||
return FALSE;
|
||||
}
|
||||
|
||||
int UnlockedWaitForEvent(HANDLE hHandle, uint64_t milliseconds)
|
||||
{
|
||||
int UnlockedWaitForEvent(HANDLE hHandle, uint64_t milliseconds) {
|
||||
int result = 0;
|
||||
if (!hHandle->eventState)
|
||||
{
|
||||
|
@ -670,6 +714,8 @@ DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds)
|
|||
|
||||
BOOL WINAPI CloseHandle(HANDLE hObject) {
|
||||
FILE_LOGD("CloseHandle(hObject: %p)", hObject);
|
||||
if(hObject)
|
||||
return FALSE;
|
||||
//https://msdn.microsoft.com/en-us/9b84891d-62ca-4ddc-97b7-c4c79482abd9
|
||||
// Can be a thread/event/file handle!
|
||||
switch(hObject->handleType) {
|
||||
|
@ -738,6 +784,29 @@ BOOL WINAPI CloseHandle(HANDLE hObject) {
|
|||
hObject->threadParameter = NULL;
|
||||
free(hObject);
|
||||
return TRUE;
|
||||
case HANDLE_TYPE_COM: {
|
||||
FILE_LOGD("CloseHandle() HANDLE_TYPE_COM");
|
||||
|
||||
closeSerialPort(hObject->commId);
|
||||
hObject->commId = 0;
|
||||
|
||||
hObject->handleType = HANDLE_TYPE_INVALID;
|
||||
if(hObject->commState) {
|
||||
free(hObject->commState);
|
||||
hObject->commState = NULL;
|
||||
}
|
||||
if(hObject->commEvent) {
|
||||
CloseHandle(hObject->commEvent);
|
||||
hObject->commEvent = NULL;
|
||||
}
|
||||
if(hObject->commIndex != -1) {
|
||||
pthread_mutex_lock(&commsLock);
|
||||
comms[hObject->commIndex] = NULL;
|
||||
pthread_mutex_unlock(&commsLock);
|
||||
}
|
||||
free(hObject);
|
||||
return TRUE;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -2971,8 +3040,36 @@ BOOL GetOverlappedResult(HANDLE hFile, LPOVERLAPPED lpOverlapped, LPDWORD lpNumb
|
|||
//TODO
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// This is not a win32 API
|
||||
void commEvent(int commId, int eventMask) {
|
||||
HANDLE commHandleFound = NULL;
|
||||
pthread_mutex_lock(&commsLock);
|
||||
for(int i = 0; i < MAX_CREATED_COMM; i++) {
|
||||
HANDLE commHandle = comms[i];
|
||||
if (commHandle && commHandle->commId == commId) {
|
||||
commHandleFound = commHandle;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&commsLock);
|
||||
if(commHandleFound) {
|
||||
commHandleFound->commEventMask = eventMask;
|
||||
SetEvent(commHandleFound->commEvent);
|
||||
}
|
||||
}
|
||||
|
||||
BOOL WaitCommEvent(HANDLE hFile, LPDWORD lpEvtMask, LPOVERLAPPED lpOverlapped) {
|
||||
//TODO
|
||||
if(hFile && hFile->handleType == HANDLE_TYPE_COM) {
|
||||
WaitForSingleObject(hFile->commEvent, INFINITE);
|
||||
pthread_mutex_lock(&commsLock);
|
||||
if(lpEvtMask && hFile->commEventMask) {
|
||||
*lpEvtMask = hFile->commEventMask; //EV_RXCHAR | EV_TXEMPTY | EV_ERR
|
||||
hFile->commEventMask = 0;
|
||||
}
|
||||
pthread_mutex_unlock(&commsLock);
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
BOOL ClearCommError(HANDLE hFile, LPDWORD lpErrors, LPCOMSTAT lpStat) {
|
||||
|
@ -2985,10 +3082,26 @@ BOOL SetCommTimeouts(HANDLE hFile, LPCOMMTIMEOUTS lpCommTimeouts) {
|
|||
}
|
||||
BOOL SetCommMask(HANDLE hFile, DWORD dwEvtMask) {
|
||||
//TODO
|
||||
if(hFile && hFile->handleType == HANDLE_TYPE_COM) {
|
||||
if(dwEvtMask == 0) {
|
||||
// When 0, clear all events and force WaitCommEvent to return.
|
||||
SetEvent(hFile->commEvent);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
BOOL SetCommState(HANDLE hFile, LPDCB lpDCB) {
|
||||
//TODO
|
||||
if(hFile && hFile->handleType == HANDLE_TYPE_COM && lpDCB) {
|
||||
int result = setSerialPortParameters(hFile->commId, lpDCB->BaudRate); //TODO 2 stop bits?
|
||||
if(result > 0) {
|
||||
if (hFile->commState)
|
||||
free(hFile->commState);
|
||||
hFile->commState = malloc(sizeof(struct _DCB));
|
||||
memcpy(hFile->commState, lpDCB, sizeof(struct _DCB));
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
BOOL PurgeComm(HANDLE hFile, DWORD dwFlags) {
|
||||
|
@ -2996,14 +3109,17 @@ BOOL PurgeComm(HANDLE hFile, DWORD dwFlags) {
|
|||
return FALSE;
|
||||
}
|
||||
BOOL SetCommBreak(HANDLE hFile) {
|
||||
//TODO
|
||||
if(hFile && hFile->handleType == HANDLE_TYPE_COM)
|
||||
return serialPortSetBreak(hFile->commId);
|
||||
return FALSE;
|
||||
}
|
||||
BOOL ClearCommBreak(HANDLE hFile) {
|
||||
//TODO
|
||||
if(hFile && hFile->handleType == HANDLE_TYPE_COM)
|
||||
return serialPortClearBreak(hFile->commId);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
int WSAGetLastError() {
|
||||
//TODO
|
||||
// Win9x break with WSAEINTR (a blocking socket call was canceled)
|
||||
|
|
|
@ -475,6 +475,38 @@ struct _HDC {
|
|||
int windowOriginY;
|
||||
};
|
||||
|
||||
// Comm
|
||||
|
||||
typedef struct _DCB {
|
||||
DWORD DCBlength;
|
||||
DWORD BaudRate;
|
||||
DWORD fBinary: 1;
|
||||
DWORD fParity: 1;
|
||||
DWORD fOutxCtsFlow:1;
|
||||
DWORD fOutxDsrFlow:1;
|
||||
DWORD fDtrControl:2;
|
||||
DWORD fDsrSensitivity:1;
|
||||
DWORD fTXContinueOnXoff: 1;
|
||||
DWORD fOutX: 1;
|
||||
DWORD fInX: 1;
|
||||
DWORD fErrorChar: 1;
|
||||
DWORD fNull: 1;
|
||||
DWORD fRtsControl:2;
|
||||
DWORD fAbortOnError:1;
|
||||
DWORD fDummy2:17;
|
||||
WORD wReserved;
|
||||
WORD XonLim;
|
||||
WORD XoffLim;
|
||||
BYTE ByteSize;
|
||||
BYTE Parity;
|
||||
BYTE StopBits;
|
||||
char XonChar;
|
||||
char XoffChar;
|
||||
char ErrorChar;
|
||||
char EofChar;
|
||||
char EvtChar;
|
||||
WORD wReserved1;
|
||||
} DCB, *LPDCB;
|
||||
|
||||
// Handle
|
||||
|
||||
|
@ -511,10 +543,12 @@ enum HANDLE_TYPE {
|
|||
HANDLE_TYPE_THREAD,
|
||||
HANDLE_TYPE_WINDOW,
|
||||
HANDLE_TYPE_ICON,
|
||||
HANDLE_TYPE_COM,
|
||||
};
|
||||
struct _HANDLE {
|
||||
enum HANDLE_TYPE handleType;
|
||||
|
||||
union {
|
||||
struct {
|
||||
// HANDLE_TYPE_FILE*
|
||||
int fileDescriptor;
|
||||
BOOL fileOpenFileFromContentResolver;
|
||||
|
@ -526,7 +560,9 @@ struct _HANDLE {
|
|||
size_t fileMappingSize;
|
||||
void* fileMappingAddress;
|
||||
DWORD fileMappingProtect;
|
||||
};
|
||||
|
||||
struct {
|
||||
// HANDLE_TYPE_THREAD
|
||||
pthread_t threadId;
|
||||
DWORD (*threadStartAddress)(LPVOID);
|
||||
|
@ -534,19 +570,36 @@ struct _HANDLE {
|
|||
struct _HANDLE * threadEventMessage;
|
||||
struct tagMSG threadMessage;
|
||||
int threadIndex;
|
||||
};
|
||||
|
||||
struct {
|
||||
// HANDLE_TYPE_EVENT
|
||||
pthread_cond_t eventCVariable;
|
||||
pthread_mutex_t eventMutex;
|
||||
BOOL eventAutoReset;
|
||||
BOOL eventState;
|
||||
};
|
||||
|
||||
struct {
|
||||
// HANDLE_TYPE_WINDOW
|
||||
HDC windowDC;
|
||||
};
|
||||
|
||||
struct {
|
||||
// HANDLE_TYPE_ICON
|
||||
HBITMAP icon;
|
||||
};
|
||||
|
||||
struct {
|
||||
// HANDLE_TYPE_COM
|
||||
LPDCB commState;
|
||||
struct _HANDLE * commEvent;
|
||||
int commIndex;
|
||||
int commId;
|
||||
int commEventMask;
|
||||
};
|
||||
};
|
||||
};
|
||||
typedef struct _HANDLE * HANDLE;
|
||||
|
||||
typedef HANDLE HMENU;
|
||||
|
@ -1214,6 +1267,13 @@ extern void mainViewResizeCallback(int x, int y);
|
|||
extern int openFileFromContentResolver(const TCHAR * fileURL, int writeAccess);
|
||||
extern int openFileInFolderFromContentResolver(const TCHAR * filename, const TCHAR * folderURL, int writeAccess);
|
||||
extern int closeFileFromContentResolver(int fd);
|
||||
extern int openSerialPort(const TCHAR * serialPort);
|
||||
extern int closeSerialPort(int serialPortId);
|
||||
extern int setSerialPortParameters(int serialPortId, int baudRate);
|
||||
extern int readSerialPort(int serialPortId, LPBYTE buffer, int nNumberOfBytesToRead);
|
||||
extern int writeSerialPort(int serialPortId, LPBYTE buffer, int bufferSize);
|
||||
extern int serialPortSetBreak(int serialPortId);
|
||||
extern int serialPortClearBreak(int serialPortId);
|
||||
extern int showAlert(const TCHAR * messageText, int flags);
|
||||
extern void sendMenuItemCommand(int menuItem);
|
||||
extern TCHAR szCurrentKml[MAX_PATH];
|
||||
|
@ -1292,38 +1352,8 @@ typedef struct _COMMTIMEOUTS {
|
|||
DWORD WriteTotalTimeoutConstant;
|
||||
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;
|
||||
|
||||
typedef struct _DCB {
|
||||
DWORD DCBlength;
|
||||
DWORD BaudRate;
|
||||
DWORD fBinary: 1;
|
||||
DWORD fParity: 1;
|
||||
DWORD fOutxCtsFlow:1;
|
||||
DWORD fOutxDsrFlow:1;
|
||||
DWORD fDtrControl:2;
|
||||
DWORD fDsrSensitivity:1;
|
||||
DWORD fTXContinueOnXoff: 1;
|
||||
DWORD fOutX: 1;
|
||||
DWORD fInX: 1;
|
||||
DWORD fErrorChar: 1;
|
||||
DWORD fNull: 1;
|
||||
DWORD fRtsControl:2;
|
||||
DWORD fAbortOnError:1;
|
||||
DWORD fDummy2:17;
|
||||
WORD wReserved;
|
||||
WORD XonLim;
|
||||
WORD XoffLim;
|
||||
BYTE ByteSize;
|
||||
BYTE Parity;
|
||||
BYTE StopBits;
|
||||
char XonChar;
|
||||
char XoffChar;
|
||||
char ErrorChar;
|
||||
char EofChar;
|
||||
char EvtChar;
|
||||
WORD wReserved1;
|
||||
} DCB, *LPDCB;
|
||||
|
||||
extern BOOL GetOverlappedResult(HANDLE hFile, LPOVERLAPPED lpOverlapped, LPDWORD lpNumberOfBytesTransferred, BOOL bWait);
|
||||
extern void commEvent(int commId, int eventMask);
|
||||
extern BOOL WaitCommEvent(HANDLE hFile, LPDWORD lpEvtMask, LPOVERLAPPED lpOverlapped);
|
||||
extern BOOL ClearCommError(HANDLE hFile, LPDWORD lpErrors, LPCOMSTAT lpStat);
|
||||
extern BOOL SetCommTimeouts(HANDLE hFile, LPCOMMTIMEOUTS lpCommTimeouts);
|
||||
|
|
|
@ -90,4 +90,9 @@ public class NativeLib {
|
|||
public static native int getScreenWidthNative();
|
||||
public static native int getScreenHeightNative();
|
||||
public static native int getLCDBackgroundColor();
|
||||
|
||||
static final int EV_RXCHAR = 0x0001;
|
||||
static final int EV_TXEMPTY = 0x0004;
|
||||
static final int EV_ERR = 0x0080;
|
||||
public static native void commEvent(int commId, int eventMask);
|
||||
}
|
||||
|
|
285
app/src/main/java/org/emulator/calculator/Serial.java
Normal file
285
app/src/main/java/org/emulator/calculator/Serial.java
Normal file
|
@ -0,0 +1,285 @@
|
|||
package org.emulator.calculator;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.hardware.usb.UsbDeviceConnection;
|
||||
import android.hardware.usb.UsbManager;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import org.emulator.calculator.usbserial.CustomProber;
|
||||
import org.emulator.calculator.usbserial.driver.UsbSerialDriver;
|
||||
import org.emulator.calculator.usbserial.driver.UsbSerialPort;
|
||||
import org.emulator.calculator.usbserial.driver.UsbSerialProber;
|
||||
import org.emulator.calculator.usbserial.util.SerialInputOutputManager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class Serial {
|
||||
|
||||
private static final String TAG = "Serial";
|
||||
private final boolean debug = true;
|
||||
|
||||
private final Context context;
|
||||
private final int serialPortId;
|
||||
private static final String INTENT_ACTION_GRANT_USB = "EMU48.GRANT_USB";
|
||||
private static final int WRITE_WAIT_MILLIS = 2000;
|
||||
|
||||
private final Handler mainLooper;
|
||||
private SerialInputOutputManager usbIoManager;
|
||||
private UsbSerialPort usbSerialPort;
|
||||
private enum UsbPermission { Unknown, Requested, Granted, Denied }
|
||||
private UsbPermission usbPermission = UsbPermission.Unknown;
|
||||
private boolean connected = false;
|
||||
private String connectionStatus = "";
|
||||
|
||||
private final ArrayDeque<Byte> reveivedByteQueue = new ArrayDeque<>();
|
||||
|
||||
|
||||
public Serial(Context context, int serialPortId) {
|
||||
this.context = context;
|
||||
this.serialPortId = serialPortId;
|
||||
|
||||
// Will connect at the next try!
|
||||
new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent != null && Objects.equals(intent.getAction(), INTENT_ACTION_GRANT_USB)) {
|
||||
usbPermission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)
|
||||
? UsbPermission.Granted : UsbPermission.Denied;
|
||||
// Will connect at the next try!
|
||||
}
|
||||
}
|
||||
};
|
||||
mainLooper = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
public boolean connect(String serialPort) {
|
||||
if(debug) Log.d(TAG, "connect( " + serialPort + ")");
|
||||
|
||||
Pattern patternSerialPort = Pattern.compile("\\\\.\\\\(\\d+),(\\d+)");
|
||||
Matcher m = patternSerialPort.matcher(serialPort);
|
||||
if (m.find()) {
|
||||
String deviceText = m.group(1);
|
||||
String portText = m.group(2);
|
||||
int deviceId = 0;
|
||||
try {
|
||||
deviceId = Integer.parseInt(deviceText);
|
||||
} catch (NumberFormatException ex) {
|
||||
// Catch bad number format
|
||||
}
|
||||
int portNum = 0;
|
||||
try {
|
||||
portNum = Integer.parseInt(portText);
|
||||
} catch (NumberFormatException ex) {
|
||||
// Catch bad number format
|
||||
}
|
||||
return connect(deviceId, portNum);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getConnectionStatus() {
|
||||
return connectionStatus;
|
||||
}
|
||||
|
||||
public boolean connect(int deviceId, int portNum) {
|
||||
if(debug) Log.d(TAG, "connect(deviceId: " + deviceId + ", portNum" + portNum + ")");
|
||||
|
||||
UsbDevice device = null;
|
||||
UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
|
||||
for(UsbDevice v : usbManager.getDeviceList().values())
|
||||
if(v.getDeviceId() == deviceId)
|
||||
device = v;
|
||||
if(device == null) {
|
||||
connectionStatus = "serial_connection_failed_device_not_found";
|
||||
if(debug) Log.d(TAG, "connectionStatus = " + connectionStatus);
|
||||
return false;
|
||||
}
|
||||
UsbSerialDriver driver = UsbSerialProber.getDefaultProber().probeDevice(device);
|
||||
if(driver == null) {
|
||||
driver = CustomProber.getCustomProber().probeDevice(device);
|
||||
}
|
||||
if(driver == null) {
|
||||
connectionStatus = "serial_connection_failed_no_driver_for_device";
|
||||
if(debug) Log.d(TAG, "connectionStatus = " + connectionStatus);
|
||||
return false;
|
||||
}
|
||||
if(driver.getPorts().size() < portNum) {
|
||||
connectionStatus = "serial_connection_failed_not_enough_ports_at_device";
|
||||
if(debug) Log.d(TAG, "connectionStatus = " + connectionStatus);
|
||||
return false;
|
||||
}
|
||||
usbSerialPort = driver.getPorts().get(portNum);
|
||||
UsbDeviceConnection usbConnection = null;
|
||||
try {
|
||||
usbConnection = usbManager.openDevice(driver.getDevice());
|
||||
} catch (SecurityException e) {
|
||||
connectionStatus = "serial_connection_failed_user_has_not_given_permission";
|
||||
if(debug) Log.d(TAG, "connectionStatus = " + connectionStatus + ", " + e.getMessage());
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
connectionStatus = "serial_connection_failed_for_unknown_reason";
|
||||
if(debug) Log.d(TAG, "connectionStatus = " + connectionStatus + ", " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
if(usbConnection == null && usbPermission == UsbPermission.Unknown && !usbManager.hasPermission(driver.getDevice())) {
|
||||
usbPermission = UsbPermission.Requested;
|
||||
PendingIntent usbPermissionIntent = PendingIntent.getBroadcast(context, 0, new Intent(INTENT_ACTION_GRANT_USB), 0);
|
||||
usbManager.requestPermission(driver.getDevice(), usbPermissionIntent);
|
||||
if(debug) Log.d(TAG, "Request permission");
|
||||
return false;
|
||||
}
|
||||
if(usbConnection == null) {
|
||||
if (!usbManager.hasPermission(driver.getDevice()))
|
||||
connectionStatus = "serial_connection_failed_permission_denied";
|
||||
else
|
||||
connectionStatus = "serial_connection_failed_open_device_failed";
|
||||
if(debug) Log.d(TAG, "connectionStatus = " + connectionStatus);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
usbSerialPort.open(usbConnection);
|
||||
usbIoManager = new SerialInputOutputManager(usbSerialPort, new SerialInputOutputManager.Listener() {
|
||||
@Override
|
||||
public void onNewData(byte[] data) {
|
||||
if(debug) Log.d(TAG, "onNewData: " + Utils.bytesToHex(data));
|
||||
boolean wasEmpty = reveivedByteQueue.isEmpty();
|
||||
synchronized (reveivedByteQueue) {
|
||||
for (byte datum : data)
|
||||
reveivedByteQueue.add(datum);
|
||||
}
|
||||
if (wasEmpty)
|
||||
onReceivedByteQueueNotEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRunError(Exception e) {
|
||||
onReceivedError(e);
|
||||
}
|
||||
});
|
||||
Thread usbIo = new Thread(usbIoManager);
|
||||
usbIo.setDaemon(true);
|
||||
usbIo.start();
|
||||
connected = true;
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
connectionStatus = "serial_connection_failed_open_failed";
|
||||
if(debug) Log.d(TAG, "connectionStatus = " + connectionStatus + ", " + e.getMessage());
|
||||
disconnect();
|
||||
}
|
||||
if(debug) Log.d(TAG, "connected!");
|
||||
connectionStatus = "";
|
||||
return false;
|
||||
}
|
||||
|
||||
private void onReceivedByteQueueNotEmpty() {
|
||||
NativeLib.commEvent(serialPortId, NativeLib.EV_RXCHAR);
|
||||
}
|
||||
|
||||
|
||||
private void onReceivedError(Exception e) {
|
||||
mainLooper.post(() -> {
|
||||
if(debug) {
|
||||
Log.d(TAG, "onRunError: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
disconnect();
|
||||
});
|
||||
}
|
||||
|
||||
public boolean setParameters(int baudRate) {
|
||||
return setParameters(baudRate, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE);
|
||||
}
|
||||
|
||||
public boolean setParameters(int baudRate, int dataBits, int stopBits, int parity) {
|
||||
if(debug) Log.d(TAG, "setParameters(baudRate: " + baudRate + ", dataBits: " + dataBits + ", stopBits: " + stopBits + ", parity: " + parity +")");
|
||||
|
||||
try {
|
||||
usbSerialPort.setParameters(baudRate, dataBits, stopBits, parity);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
if(debug) Log.d(TAG, "setParameters() failed: " + e.getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public byte[] receive(int nNumberOfBytesToRead) {
|
||||
byte[] result;
|
||||
synchronized (reveivedByteQueue) {
|
||||
int nNumberOfReadBytes = Math.min(nNumberOfBytesToRead, reveivedByteQueue.size());
|
||||
result = new byte[nNumberOfReadBytes];
|
||||
for (int i = 0; i < nNumberOfReadBytes; i++) {
|
||||
Byte byteRead = reveivedByteQueue.poll();
|
||||
if(byteRead != null)
|
||||
result[i] = byteRead;
|
||||
}
|
||||
}
|
||||
if(result == null) {
|
||||
result = new byte[0];
|
||||
} else if(reveivedByteQueue.size() > 0) {
|
||||
mainLooper.post(() -> NativeLib.commEvent(serialPortId, NativeLib.EV_RXCHAR));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public int send(byte[] data) {
|
||||
if(!connected)
|
||||
return 0;
|
||||
|
||||
try {
|
||||
return usbSerialPort.write(data, WRITE_WAIT_MILLIS);
|
||||
} catch (Exception e) {
|
||||
onReceivedError(e);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int setBreak() {
|
||||
if(!connected)
|
||||
return 0;
|
||||
|
||||
try {
|
||||
usbSerialPort.setBreak(true);
|
||||
return 1;
|
||||
} catch (Exception e) {
|
||||
// Exception mean return 0
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int clearBreak() {
|
||||
if(!connected)
|
||||
return 0;
|
||||
|
||||
try {
|
||||
usbSerialPort.setBreak(false);
|
||||
return 1;
|
||||
} catch (Exception e) {
|
||||
// Exception mean return 0
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void disconnect() {
|
||||
if(debug) Log.d(TAG, "disconnect()");
|
||||
|
||||
connected = false;
|
||||
if(usbIoManager != null)
|
||||
usbIoManager.stop();
|
||||
usbIoManager = null;
|
||||
try {
|
||||
usbSerialPort.close();
|
||||
} catch (IOException ignored) {}
|
||||
usbSerialPort = null;
|
||||
}
|
||||
}
|
|
@ -56,7 +56,7 @@ public class Utils {
|
|||
toast.show();
|
||||
}
|
||||
|
||||
static int resId(Context context, String resourceName, String variableName) {
|
||||
public static int resId(Context context, String resourceName, String variableName) {
|
||||
try {
|
||||
return context.getResources().getIdentifier(variableName, resourceName, context.getApplicationContext().getPackageName());
|
||||
} catch (Exception e) {
|
||||
|
@ -91,9 +91,9 @@ public class Utils {
|
|||
public static void colorizeDrawableWithColor(Context context, Drawable icon, int colorAttribute) {
|
||||
if(icon != null) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
||||
icon.setColorFilter(new BlendModeColorFilter(Utils.getThemedColor(context, colorAttribute), BlendMode.SRC_ATOP));
|
||||
icon.setColorFilter(new BlendModeColorFilter(getThemedColor(context, colorAttribute), BlendMode.SRC_ATOP));
|
||||
else
|
||||
icon.setColorFilter(Utils.getThemedColor(context, colorAttribute), PorterDuff.Mode.SRC_ATOP);
|
||||
icon.setColorFilter(getThemedColor(context, colorAttribute), PorterDuff.Mode.SRC_ATOP);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -213,4 +213,15 @@ public class Utils {
|
|||
vibrator.vibrate(durationInMilliSecond);
|
||||
}
|
||||
}
|
||||
|
||||
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
|
||||
public static String bytesToHex(byte[] bytes) {
|
||||
char[] hexChars = new char[bytes.length * 2];
|
||||
for (int j = 0; j < bytes.length; j++) {
|
||||
int v = bytes[j] & 0xFF;
|
||||
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
|
||||
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
|
||||
}
|
||||
return new String(hexChars);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package org.emulator.calculator.usbserial;
|
||||
|
||||
import org.emulator.calculator.usbserial.driver.CdcAcmSerialDriver;
|
||||
import org.emulator.calculator.usbserial.driver.ProbeTable;
|
||||
import org.emulator.calculator.usbserial.driver.UsbSerialProber;
|
||||
|
||||
/**
|
||||
* add devices here, that are not known to DefaultProber
|
||||
*
|
||||
* if the App should auto start for these devices, also
|
||||
* add IDs to app/src/main/res/xml/device_filter.xml
|
||||
*/
|
||||
public class CustomProber {
|
||||
|
||||
public static UsbSerialProber getCustomProber() {
|
||||
ProbeTable customTable = new ProbeTable();
|
||||
customTable.addProduct(0x16d0, 0x087e, CdcAcmSerialDriver.class); // e.g. Digispark CDC
|
||||
return new UsbSerialProber(customTable);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program 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 General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
package org.emulator.calculator.usbserial;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatDialogFragment;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import org.emulator.calculator.Utils;
|
||||
|
||||
public class DevicesDialogFragment extends AppCompatDialogFragment {
|
||||
|
||||
private DevicesFragment devicesFragment;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setStyle(AppCompatDialogFragment.STYLE_NO_FRAME, Utils.resId(this, "style", "AppTheme"));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
Dialog dialog = super.onCreateDialog(savedInstanceState);
|
||||
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
|
||||
String title = getString(Utils.resId(this, "string", "serial_fragment_title"));
|
||||
Dialog dialog = getDialog();
|
||||
if(dialog != null)
|
||||
dialog.setTitle(title);
|
||||
|
||||
View view = inflater.inflate(Utils.resId(this, "layout", "fragment_serial"), container, false);
|
||||
|
||||
// Toolbar
|
||||
|
||||
Toolbar toolbar = view.findViewById(Utils.resId(this, "id", "toolbar"));
|
||||
toolbar.setTitle(title);
|
||||
Utils.colorizeDrawableWithColor(requireContext(), toolbar.getNavigationIcon(), android.R.attr.colorForeground);
|
||||
toolbar.setNavigationOnClickListener(v -> dismiss());
|
||||
|
||||
devicesFragment = new DevicesFragment();
|
||||
devicesFragment.setOnSerialDeviceClickedListener((serialConnectParameters) -> {
|
||||
if(onSerialDeviceClickedListener != null)
|
||||
onSerialDeviceClickedListener.onSerialDeviceClicked(serialConnectParameters);
|
||||
dismiss();
|
||||
});
|
||||
getChildFragmentManager().beginTransaction().add(Utils.resId(this, "id", "serialport_devices_fragment"), devicesFragment, "devices").commit();
|
||||
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private DevicesFragment.OnSerialDeviceClickedListener onSerialDeviceClickedListener;
|
||||
|
||||
/**
|
||||
* Register a callback to be invoked when a serial device has been chosen.
|
||||
* @param onSerialDeviceClickedListener The callback that will run
|
||||
*/
|
||||
public void setOnSerialDeviceClickedListener(DevicesFragment.OnSerialDeviceClickedListener onSerialDeviceClickedListener) {
|
||||
this.onSerialDeviceClickedListener = onSerialDeviceClickedListener;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,221 @@
|
|||
package org.emulator.calculator.usbserial;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.hardware.usb.UsbManager;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.ListFragment;
|
||||
|
||||
import org.emulator.calculator.Utils;
|
||||
import org.emulator.calculator.usbserial.driver.UsbSerialDriver;
|
||||
import org.emulator.calculator.usbserial.driver.UsbSerialProber;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class DevicesFragment extends ListFragment {
|
||||
|
||||
static class ListItem {
|
||||
UsbDevice device;
|
||||
int port;
|
||||
UsbSerialDriver driver;
|
||||
|
||||
ListItem(UsbDevice device, int port, UsbSerialDriver driver) {
|
||||
this.device = device;
|
||||
this.port = port;
|
||||
this.driver = driver;
|
||||
}
|
||||
}
|
||||
|
||||
private final ArrayList<ListItem> listItems = new ArrayList<>();
|
||||
private ArrayAdapter<ListItem> listAdapter;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
listAdapter = new ArrayAdapter<ListItem>(getActivity(), 0, listItems) {
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, View view, @NonNull ViewGroup parent) {
|
||||
ListItem item = listItems.get(position);
|
||||
if (view == null)
|
||||
view = getActivity().getLayoutInflater().inflate(Utils.resId(DevicesFragment.this, "layout", "device_list_item"), parent, false);
|
||||
TextView text1 = view.findViewById(Utils.resId(DevicesFragment.this, "id", "text1"));
|
||||
TextView text2 = view.findViewById(Utils.resId(DevicesFragment.this, "id", "text2"));
|
||||
if(item.driver == null)
|
||||
text1.setText(getString(Utils.resId(DevicesFragment.this, "string", "serial_ports_device_no_driver")));
|
||||
else {
|
||||
String deviceName = item.driver.getClass().getSimpleName().replace("SerialDriver","");
|
||||
if(item.driver.getPorts().size() == 1)
|
||||
text1.setText(deviceName);
|
||||
else
|
||||
text1.setText(String.format(Locale.US, getString(Utils.resId(DevicesFragment.this, "string", "serial_ports_device_item_title")), deviceName, item.port));
|
||||
}
|
||||
text2.setText(String.format(Locale.US, getString(Utils.resId(DevicesFragment.this, "string", "serial_ports_device_item_description")), item.device.getVendorId(), item.device.getProductId()));
|
||||
return view;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
setListAdapter(null);
|
||||
View header = getActivity().getLayoutInflater().inflate(Utils.resId(DevicesFragment.this, "layout", "device_list_header"), null, false);
|
||||
getListView().addHeaderView(header, null, false);
|
||||
setEmptyText(getString(Utils.resId(this, "string", "serial_no_device")));
|
||||
((TextView) getListView().getEmptyView()).setTextSize(18);
|
||||
setListAdapter(listAdapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
refresh();
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
UsbManager usbManager = (UsbManager) getActivity().getSystemService(Context.USB_SERVICE);
|
||||
UsbSerialProber usbDefaultProber = UsbSerialProber.getDefaultProber();
|
||||
UsbSerialProber usbCustomProber = CustomProber.getCustomProber();
|
||||
listItems.clear();
|
||||
for(UsbDevice device : usbManager.getDeviceList().values()) {
|
||||
UsbSerialDriver driver = usbDefaultProber.probeDevice(device);
|
||||
if(driver == null) {
|
||||
driver = usbCustomProber.probeDevice(device);
|
||||
}
|
||||
if(driver != null) {
|
||||
for(int port = 0; port < driver.getPorts().size(); port++)
|
||||
listItems.add(new ListItem(device, port, driver));
|
||||
} else {
|
||||
listItems.add(new ListItem(device, 0, null));
|
||||
}
|
||||
}
|
||||
listAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public static class SerialConnectParameters {
|
||||
public int deviceId;
|
||||
public int port;
|
||||
public int vendorId;
|
||||
public int productId;
|
||||
public String modelName;
|
||||
|
||||
public SerialConnectParameters() {
|
||||
this.deviceId = 0;
|
||||
this.port = 0;
|
||||
this.vendorId = 0;
|
||||
this.productId = 0;
|
||||
this.modelName = "";
|
||||
}
|
||||
public SerialConnectParameters(int device, int port, int vendorId, int productId, String modelName) {
|
||||
this.deviceId = device;
|
||||
this.port = port;
|
||||
this.vendorId = vendorId;
|
||||
this.productId = productId;
|
||||
this.modelName = modelName;
|
||||
}
|
||||
|
||||
public String toSettingsString() {
|
||||
return String.format(Locale.US, "%d,%d,%d,%d,%s", deviceId, port, vendorId, productId, modelName);
|
||||
}
|
||||
|
||||
public String toWin32String() {
|
||||
return String.format(Locale.US, "%d,%d", deviceId, port);
|
||||
}
|
||||
|
||||
public String toDisplayString(Context context) {
|
||||
if(deviceId == 0 && port == 0)
|
||||
return context.getResources().getString(Utils.resId(context, "string", "serial_ports_device_no_driver"));
|
||||
else
|
||||
return String.format(Locale.US, context.getResources().getString(Utils.resId(context, "string", "serial_ports_device")), modelName, vendorId, productId, deviceId, port);
|
||||
}
|
||||
|
||||
public static SerialConnectParameters fromSettingsString(String serialPorts) {
|
||||
SerialConnectParameters serialConnectParameters = new SerialConnectParameters();
|
||||
serialConnectParameters.fromSettingsStringExtractor(serialPorts);
|
||||
return serialConnectParameters;
|
||||
}
|
||||
|
||||
private void fromSettingsStringExtractor(String serialPorts) {
|
||||
Pattern patternSerialPort = Pattern.compile("(\\d+),(\\d+),(\\d+),(\\d+),([^,]+)");
|
||||
Matcher m = patternSerialPort.matcher(serialPorts);
|
||||
if (m.find()) {
|
||||
String deviceText = m.group(1);
|
||||
String portText = m.group(2);
|
||||
String vendorIdText = m.group(3);
|
||||
String productIdText = m.group(4);
|
||||
modelName = m.group(5);
|
||||
try {
|
||||
deviceId = Integer.parseInt(deviceText);
|
||||
} catch (NumberFormatException ex) {
|
||||
// Catch bad number format
|
||||
}
|
||||
try {
|
||||
port = Integer.parseInt(portText);
|
||||
} catch (NumberFormatException ex) {
|
||||
// Catch bad number format
|
||||
}
|
||||
try {
|
||||
vendorId = Integer.parseInt(vendorIdText);
|
||||
} catch (NumberFormatException ex) {
|
||||
// Catch bad number format
|
||||
}
|
||||
try {
|
||||
productId = Integer.parseInt(productIdText);
|
||||
} catch (NumberFormatException ex) {
|
||||
// Catch bad number format
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be invoked when the printer just has print something.
|
||||
*/
|
||||
public interface OnSerialDeviceClickedListener {
|
||||
/**
|
||||
* Called when the printer just has print something.
|
||||
*/
|
||||
void onSerialDeviceClicked(SerialConnectParameters serialConnectParameters);
|
||||
}
|
||||
|
||||
private OnSerialDeviceClickedListener onSerialDeviceClickedListener;
|
||||
|
||||
/**
|
||||
* Register a callback to be invoked when a serial device has been chosen.
|
||||
* @param onSerialDeviceClickedListener The callback that will run
|
||||
*/
|
||||
void setOnSerialDeviceClickedListener(OnSerialDeviceClickedListener onSerialDeviceClickedListener) {
|
||||
this.onSerialDeviceClickedListener = onSerialDeviceClickedListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListItemClick(@NonNull ListView l, @NonNull View v, int position, long id) {
|
||||
ListItem item = listItems.get(position-1);
|
||||
if(item.driver == null) {
|
||||
Toast.makeText(getActivity(), getString(Utils.resId(this, "string", "serial_ports_device_no_driver")), Toast.LENGTH_SHORT).show();
|
||||
if(onSerialDeviceClickedListener != null)
|
||||
onSerialDeviceClickedListener.onSerialDeviceClicked(new SerialConnectParameters(0, 0,
|
||||
0, 0,
|
||||
""));
|
||||
} else if(item.device != null) {
|
||||
if(onSerialDeviceClickedListener != null)
|
||||
onSerialDeviceClickedListener.onSerialDeviceClicked(new SerialConnectParameters(item.device.getDeviceId(), item.port,
|
||||
item.device.getVendorId(), item.device.getProductId(),
|
||||
item.driver.getClass().getSimpleName().replace("SerialDriver","")));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,323 @@
|
|||
/* Copyright 2011-2013 Google Inc.
|
||||
* Copyright 2013 mike wakerly <opensource@hoho.com>
|
||||
*
|
||||
* Project home page: https://github.com/mik3y/usb-serial-for-android
|
||||
*/
|
||||
|
||||
package org.emulator.calculator.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.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* USB CDC/ACM serial driver implementation.
|
||||
*
|
||||
* @author mike wakerly (opensource@hoho.com)
|
||||
* @see <a
|
||||
* href="http://www.usb.org/developers/devclass_docs/usbcdc11.pdf">Universal
|
||||
* Serial Bus Class Definitions for Communication Devices, v1.1</a>
|
||||
*/
|
||||
public class CdcAcmSerialDriver implements UsbSerialDriver {
|
||||
|
||||
private final String TAG = CdcAcmSerialDriver.class.getSimpleName();
|
||||
|
||||
private final UsbDevice mDevice;
|
||||
private final List<UsbSerialPort> mPorts;
|
||||
|
||||
public CdcAcmSerialDriver(UsbDevice device) {
|
||||
mDevice = device;
|
||||
mPorts = new ArrayList<>();
|
||||
|
||||
int controlInterfaceCount = 0;
|
||||
int dataInterfaceCount = 0;
|
||||
for( int i = 0; i < device.getInterfaceCount(); i++) {
|
||||
if(device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_COMM)
|
||||
controlInterfaceCount++;
|
||||
if(device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA)
|
||||
dataInterfaceCount++;
|
||||
}
|
||||
for( int port = 0; port < Math.min(controlInterfaceCount, dataInterfaceCount); port++) {
|
||||
mPorts.add(new CdcAcmSerialPort(mDevice, port));
|
||||
}
|
||||
if(mPorts.size() == 0) {
|
||||
mPorts.add(new CdcAcmSerialPort(mDevice, -1));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UsbDevice getDevice() {
|
||||
return mDevice;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UsbSerialPort> getPorts() {
|
||||
return mPorts;
|
||||
}
|
||||
|
||||
public class CdcAcmSerialPort extends CommonUsbSerialPort {
|
||||
|
||||
private UsbInterface mControlInterface;
|
||||
private UsbInterface mDataInterface;
|
||||
|
||||
private UsbEndpoint mControlEndpoint;
|
||||
|
||||
private int mControlIndex;
|
||||
|
||||
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 CdcAcmSerialPort(UsbDevice device, int portNumber) {
|
||||
super(device, portNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UsbSerialDriver getDriver() {
|
||||
return CdcAcmSerialDriver.this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void openInt(UsbDeviceConnection connection) throws IOException {
|
||||
if (mPortNumber == -1) {
|
||||
Log.d(TAG,"device might be castrated ACM device, trying single interface logic");
|
||||
openSingleInterface();
|
||||
} else {
|
||||
Log.d(TAG,"trying default interface logic");
|
||||
openInterface();
|
||||
}
|
||||
}
|
||||
|
||||
private void openSingleInterface() throws IOException {
|
||||
// the following code is inspired by the cdc-acm driver in the linux kernel
|
||||
|
||||
mControlIndex = 0;
|
||||
mControlInterface = mDevice.getInterface(0);
|
||||
mDataInterface = mDevice.getInterface(0);
|
||||
if (!mConnection.claimInterface(mControlInterface, true)) {
|
||||
throw new IOException("Could not claim shared control/data interface");
|
||||
}
|
||||
|
||||
for (int i = 0; i < mControlInterface.getEndpointCount(); ++i) {
|
||||
UsbEndpoint ep = mControlInterface.getEndpoint(i);
|
||||
if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_INT)) {
|
||||
mControlEndpoint = ep;
|
||||
} else if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) {
|
||||
mReadEndpoint = ep;
|
||||
} else if ((ep.getDirection() == UsbConstants.USB_DIR_OUT) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) {
|
||||
mWriteEndpoint = ep;
|
||||
}
|
||||
}
|
||||
if (mControlEndpoint == null) {
|
||||
throw new IOException("No control endpoint");
|
||||
}
|
||||
}
|
||||
|
||||
private void openInterface() throws IOException {
|
||||
Log.d(TAG, "claiming interfaces, count=" + mDevice.getInterfaceCount());
|
||||
|
||||
int controlInterfaceCount = 0;
|
||||
int dataInterfaceCount = 0;
|
||||
mControlInterface = null;
|
||||
mDataInterface = null;
|
||||
for (int i = 0; i < mDevice.getInterfaceCount(); i++) {
|
||||
UsbInterface usbInterface = mDevice.getInterface(i);
|
||||
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_COMM) {
|
||||
if(controlInterfaceCount == mPortNumber) {
|
||||
mControlIndex = i;
|
||||
mControlInterface = usbInterface;
|
||||
}
|
||||
controlInterfaceCount++;
|
||||
}
|
||||
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) {
|
||||
if(dataInterfaceCount == mPortNumber) {
|
||||
mDataInterface = usbInterface;
|
||||
}
|
||||
dataInterfaceCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if(mControlInterface == null) {
|
||||
throw new IOException("No control interface");
|
||||
}
|
||||
Log.d(TAG, "Control iface=" + mControlInterface);
|
||||
|
||||
if (!mConnection.claimInterface(mControlInterface, true)) {
|
||||
throw new IOException("Could not claim control interface");
|
||||
}
|
||||
|
||||
mControlEndpoint = mControlInterface.getEndpoint(0);
|
||||
if (mControlEndpoint.getDirection() != UsbConstants.USB_DIR_IN || mControlEndpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_INT) {
|
||||
throw new IOException("Invalid control endpoint");
|
||||
}
|
||||
|
||||
if(mDataInterface == null) {
|
||||
throw new IOException("No data interface");
|
||||
}
|
||||
Log.d(TAG, "data iface=" + mDataInterface);
|
||||
|
||||
if (!mConnection.claimInterface(mDataInterface, true)) {
|
||||
throw new IOException("Could not claim data interface");
|
||||
}
|
||||
|
||||
for (int i = 0; i < mDataInterface.getEndpointCount(); i++) {
|
||||
UsbEndpoint ep = mDataInterface.getEndpoint(i);
|
||||
if (ep.getDirection() == UsbConstants.USB_DIR_IN && ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)
|
||||
mReadEndpoint = ep;
|
||||
if (ep.getDirection() == UsbConstants.USB_DIR_OUT && ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)
|
||||
mWriteEndpoint = ep;
|
||||
}
|
||||
}
|
||||
|
||||
private int sendAcmControlMessage(int request, int value, byte[] buf) throws IOException {
|
||||
int len = mConnection.controlTransfer(
|
||||
USB_RT_ACM, request, value, mControlIndex, buf, buf != null ? buf.length : 0, 5000);
|
||||
if(len < 0) {
|
||||
throw new IOException("controlTransfer failed");
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void closeInt() {
|
||||
try {
|
||||
mConnection.releaseInterface(mControlInterface);
|
||||
mConnection.releaseInterface(mDataInterface);
|
||||
} catch(Exception ignored) {}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException {
|
||||
if(baudRate <= 0) {
|
||||
throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
|
||||
}
|
||||
if(dataBits < DATABITS_5 || dataBits > DATABITS_8) {
|
||||
throw new IllegalArgumentException("Invalid data bits: " + dataBits);
|
||||
}
|
||||
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("Invalid stop bits: " + 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("Invalid 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 getDTR() throws IOException {
|
||||
return mDtr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDTR(boolean value) throws IOException {
|
||||
mDtr = value;
|
||||
setDtrRts();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getRTS() throws IOException {
|
||||
return mRts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRTS(boolean value) throws IOException {
|
||||
mRts = value;
|
||||
setDtrRts();
|
||||
}
|
||||
|
||||
private void setDtrRts() throws IOException {
|
||||
int value = (mRts ? 0x2 : 0) | (mDtr ? 0x1 : 0);
|
||||
sendAcmControlMessage(SET_CONTROL_LINE_STATE, value, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnumSet<ControlLine> getControlLines() throws IOException {
|
||||
EnumSet<ControlLine> set = EnumSet.noneOf(ControlLine.class);
|
||||
if(mRts) set.add(ControlLine.RTS);
|
||||
if(mDtr) set.add(ControlLine.DTR);
|
||||
return set;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnumSet<ControlLine> getSupportedControlLines() throws IOException {
|
||||
return EnumSet.of(ControlLine.RTS, ControlLine.DTR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBreak(boolean value) throws IOException {
|
||||
sendAcmControlMessage(SEND_BREAK, value ? 0xffff : 0, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static Map<Integer, int[]> getSupportedDevices() {
|
||||
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<Integer, int[]>();
|
||||
supportedDevices.put(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,
|
||||
UsbId.ARDUINO_MICRO,
|
||||
});
|
||||
supportedDevices.put(UsbId.VENDOR_VAN_OOIJEN_TECH,
|
||||
new int[] {
|
||||
UsbId.VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL,
|
||||
});
|
||||
supportedDevices.put(UsbId.VENDOR_ATMEL,
|
||||
new int[] {
|
||||
UsbId.ATMEL_LUFA_CDC_DEMO_APP,
|
||||
});
|
||||
supportedDevices.put(UsbId.VENDOR_LEAFLABS,
|
||||
new int[] {
|
||||
UsbId.LEAFLABS_MAPLE,
|
||||
});
|
||||
supportedDevices.put(UsbId.VENDOR_ARM,
|
||||
new int[] {
|
||||
UsbId.ARM_MBED,
|
||||
});
|
||||
return supportedDevices;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,374 @@
|
|||
/* Copyright 2014 Andreas Butti
|
||||
*
|
||||
* Project home page: https://github.com/mik3y/usb-serial-for-android
|
||||
*/
|
||||
|
||||
package org.emulator.calculator.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 java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class Ch34xSerialDriver implements UsbSerialDriver {
|
||||
|
||||
private static final String TAG = Ch34xSerialDriver.class.getSimpleName();
|
||||
|
||||
private final UsbDevice mDevice;
|
||||
private final UsbSerialPort mPort;
|
||||
|
||||
private static final int LCR_ENABLE_RX = 0x80;
|
||||
private static final int LCR_ENABLE_TX = 0x40;
|
||||
private static final int LCR_MARK_SPACE = 0x20;
|
||||
private static final int LCR_PAR_EVEN = 0x10;
|
||||
private static final int LCR_ENABLE_PAR = 0x08;
|
||||
private static final int LCR_STOP_BITS_2 = 0x04;
|
||||
private static final int LCR_CS8 = 0x03;
|
||||
private static final int LCR_CS7 = 0x02;
|
||||
private static final int LCR_CS6 = 0x01;
|
||||
private static final int LCR_CS5 = 0x00;
|
||||
|
||||
private static final int GCL_CTS = 0x01;
|
||||
private static final int GCL_DSR = 0x02;
|
||||
private static final int GCL_RI = 0x04;
|
||||
private static final int GCL_CD = 0x08;
|
||||
private static final int SCL_DTR = 0x20;
|
||||
private static final int SCL_RTS = 0x40;
|
||||
|
||||
public Ch34xSerialDriver(UsbDevice device) {
|
||||
mDevice = device;
|
||||
mPort = new Ch340SerialPort(mDevice, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UsbDevice getDevice() {
|
||||
return mDevice;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UsbSerialPort> getPorts() {
|
||||
return Collections.singletonList(mPort);
|
||||
}
|
||||
|
||||
public class Ch340SerialPort extends CommonUsbSerialPort {
|
||||
|
||||
private static final int USB_TIMEOUT_MILLIS = 5000;
|
||||
|
||||
private final int DEFAULT_BAUD_RATE = 9600;
|
||||
|
||||
private boolean dtr = false;
|
||||
private boolean rts = false;
|
||||
|
||||
public Ch340SerialPort(UsbDevice device, int portNumber) {
|
||||
super(device, portNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UsbSerialDriver getDriver() {
|
||||
return Ch34xSerialDriver.this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void openInt(UsbDeviceConnection connection) throws IOException {
|
||||
for (int i = 0; i < mDevice.getInterfaceCount(); i++) {
|
||||
UsbInterface usbIface = mDevice.getInterface(i);
|
||||
if (!mConnection.claimInterface(usbIface, true)) {
|
||||
throw new IOException("Could not claim data interface");
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
initialize();
|
||||
setBaudRate(DEFAULT_BAUD_RATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void closeInt() {
|
||||
try {
|
||||
for (int i = 0; i < mDevice.getInterfaceCount(); i++)
|
||||
mConnection.releaseInterface(mDevice.getInterface(i));
|
||||
} catch(Exception ignored) {}
|
||||
}
|
||||
|
||||
private int controlOut(int request, int value, int index) {
|
||||
final int REQTYPE_HOST_TO_DEVICE = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_OUT;
|
||||
return mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request,
|
||||
value, index, null, 0, USB_TIMEOUT_MILLIS);
|
||||
}
|
||||
|
||||
|
||||
private int controlIn(int request, int value, int index, byte[] buffer) {
|
||||
final int REQTYPE_DEVICE_TO_HOST = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_IN;
|
||||
return mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, request,
|
||||
value, index, buffer, buffer.length, USB_TIMEOUT_MILLIS);
|
||||
}
|
||||
|
||||
|
||||
private void checkState(String msg, int request, int value, int[] expected) throws IOException {
|
||||
byte[] buffer = new byte[expected.length];
|
||||
int ret = controlIn(request, value, 0, buffer);
|
||||
|
||||
if (ret < 0) {
|
||||
throw new IOException("Failed send cmd [" + msg + "]");
|
||||
}
|
||||
|
||||
if (ret != expected.length) {
|
||||
throw new IOException("Expected " + expected.length + " bytes, but get " + ret + " [" + msg + "]");
|
||||
}
|
||||
|
||||
for (int i = 0; i < expected.length; i++) {
|
||||
if (expected[i] == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int current = buffer[i] & 0xff;
|
||||
if (expected[i] != current) {
|
||||
throw new IOException("Expected 0x" + Integer.toHexString(expected[i]) + " byte, but get 0x" + Integer.toHexString(current) + " [" + msg + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setControlLines() throws IOException {
|
||||
if (controlOut(0xa4, ~((dtr ? SCL_DTR : 0) | (rts ? SCL_RTS : 0)), 0) < 0) {
|
||||
throw new IOException("Failed to set control lines");
|
||||
}
|
||||
}
|
||||
|
||||
private byte getStatus() throws IOException {
|
||||
byte[] buffer = new byte[2];
|
||||
int ret = controlIn(0x95, 0x0706, 0, buffer);
|
||||
if (ret < 0)
|
||||
throw new IOException("Error getting control lines");
|
||||
return buffer[0];
|
||||
}
|
||||
|
||||
private void initialize() throws IOException {
|
||||
checkState("init #1", 0x5f, 0, new int[]{-1 /* 0x27, 0x30 */, 0x00});
|
||||
|
||||
if (controlOut(0xa1, 0, 0) < 0) {
|
||||
throw new IOException("Init failed: #2");
|
||||
}
|
||||
|
||||
setBaudRate(DEFAULT_BAUD_RATE);
|
||||
|
||||
checkState("init #4", 0x95, 0x2518, new int[]{-1 /* 0x56, c3*/, 0x00});
|
||||
|
||||
if (controlOut(0x9a, 0x2518, LCR_ENABLE_RX | LCR_ENABLE_TX | LCR_CS8) < 0) {
|
||||
throw new IOException("Init failed: #5");
|
||||
}
|
||||
|
||||
checkState("init #6", 0x95, 0x0706, new int[]{-1/*0xf?*/, -1/*0xec,0xee*/});
|
||||
|
||||
if (controlOut(0xa1, 0x501f, 0xd90a) < 0) {
|
||||
throw new IOException("Init failed: #7");
|
||||
}
|
||||
|
||||
setBaudRate(DEFAULT_BAUD_RATE);
|
||||
|
||||
setControlLines();
|
||||
|
||||
checkState("init #10", 0x95, 0x0706, new int[]{-1/* 0x9f, 0xff*/, -1/*0xec,0xee*/});
|
||||
}
|
||||
|
||||
|
||||
private void setBaudRate(int baudRate) throws IOException {
|
||||
final long CH341_BAUDBASE_FACTOR = 1532620800;
|
||||
final int CH341_BAUDBASE_DIVMAX = 3;
|
||||
|
||||
long factor = CH341_BAUDBASE_FACTOR / baudRate;
|
||||
int divisor = CH341_BAUDBASE_DIVMAX;
|
||||
|
||||
while ((factor > 0xfff0) && divisor > 0) {
|
||||
factor >>= 3;
|
||||
divisor--;
|
||||
}
|
||||
|
||||
if (factor > 0xfff0) {
|
||||
throw new UnsupportedOperationException("Unsupported baud rate: " + baudRate);
|
||||
}
|
||||
|
||||
factor = 0x10000 - factor;
|
||||
divisor |= 0x0080; // else ch341a waits until buffer full
|
||||
int ret = controlOut(0x9a, 0x1312, (int) ((factor & 0xff00) | divisor));
|
||||
if (ret < 0) {
|
||||
throw new IOException("Error setting baud rate: #1)");
|
||||
}
|
||||
|
||||
ret = controlOut(0x9a, 0x0f2c, (int) (factor & 0xff));
|
||||
if (ret < 0) {
|
||||
throw new IOException("Error setting baud rate: #2");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException {
|
||||
if(baudRate <= 0) {
|
||||
throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
|
||||
}
|
||||
setBaudRate(baudRate);
|
||||
|
||||
int lcr = LCR_ENABLE_RX | LCR_ENABLE_TX;
|
||||
|
||||
switch (dataBits) {
|
||||
case DATABITS_5:
|
||||
lcr |= LCR_CS5;
|
||||
break;
|
||||
case DATABITS_6:
|
||||
lcr |= LCR_CS6;
|
||||
break;
|
||||
case DATABITS_7:
|
||||
lcr |= LCR_CS7;
|
||||
break;
|
||||
case DATABITS_8:
|
||||
lcr |= LCR_CS8;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid data bits: " + dataBits);
|
||||
}
|
||||
|
||||
switch (parity) {
|
||||
case PARITY_NONE:
|
||||
break;
|
||||
case PARITY_ODD:
|
||||
lcr |= LCR_ENABLE_PAR;
|
||||
break;
|
||||
case PARITY_EVEN:
|
||||
lcr |= LCR_ENABLE_PAR | LCR_PAR_EVEN;
|
||||
break;
|
||||
case PARITY_MARK:
|
||||
lcr |= LCR_ENABLE_PAR | LCR_MARK_SPACE;
|
||||
break;
|
||||
case PARITY_SPACE:
|
||||
lcr |= LCR_ENABLE_PAR | LCR_MARK_SPACE | LCR_PAR_EVEN;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid parity: " + parity);
|
||||
}
|
||||
|
||||
switch (stopBits) {
|
||||
case STOPBITS_1:
|
||||
break;
|
||||
case STOPBITS_1_5:
|
||||
throw new UnsupportedOperationException("Unsupported stop bits: 1.5");
|
||||
case STOPBITS_2:
|
||||
lcr |= LCR_STOP_BITS_2;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid stop bits: " + stopBits);
|
||||
}
|
||||
|
||||
int ret = controlOut(0x9a, 0x2518, lcr);
|
||||
if (ret < 0) {
|
||||
throw new IOException("Error setting control byte");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getCD() throws IOException {
|
||||
return (getStatus() & GCL_CD) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getCTS() throws IOException {
|
||||
return (getStatus() & GCL_CTS) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getDSR() throws IOException {
|
||||
return (getStatus() & GCL_DSR) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getDTR() throws IOException {
|
||||
return dtr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDTR(boolean value) throws IOException {
|
||||
dtr = value;
|
||||
setControlLines();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getRI() throws IOException {
|
||||
return (getStatus() & GCL_RI) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getRTS() throws IOException {
|
||||
return rts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRTS(boolean value) throws IOException {
|
||||
rts = value;
|
||||
setControlLines();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnumSet<ControlLine> getControlLines() throws IOException {
|
||||
int status = getStatus();
|
||||
EnumSet<ControlLine> set = EnumSet.noneOf(ControlLine.class);
|
||||
if(rts) set.add(ControlLine.RTS);
|
||||
if((status & GCL_CTS) == 0) set.add(ControlLine.CTS);
|
||||
if(dtr) set.add(ControlLine.DTR);
|
||||
if((status & GCL_DSR) == 0) set.add(ControlLine.DSR);
|
||||
if((status & GCL_CD) == 0) set.add(ControlLine.CD);
|
||||
if((status & GCL_RI) == 0) set.add(ControlLine.RI);
|
||||
return set;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnumSet<ControlLine> getSupportedControlLines() throws IOException {
|
||||
return EnumSet.allOf(ControlLine.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBreak(boolean value) throws IOException {
|
||||
byte[] req = new byte[2];
|
||||
if(controlIn(0x95, 0x1805, 0, req) < 0) {
|
||||
throw new IOException("Error getting BREAK condition");
|
||||
}
|
||||
if(value) {
|
||||
req[0] &= ~1;
|
||||
req[1] &= ~0x40;
|
||||
} else {
|
||||
req[0] |= 1;
|
||||
req[1] |= 0x40;
|
||||
}
|
||||
int val = (req[1] & 0xff) << 8 | (req[0] & 0xff);
|
||||
if(controlOut(0x9a, 0x1805, val) < 0) {
|
||||
throw new IOException("Error setting BREAK condition");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<Integer, int[]> getSupportedDevices() {
|
||||
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<Integer, int[]>();
|
||||
supportedDevices.put(UsbId.VENDOR_QINHENG, new int[]{
|
||||
UsbId.QINHENG_CH340,
|
||||
UsbId.QINHENG_CH341A,
|
||||
});
|
||||
return supportedDevices;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,270 @@
|
|||
/* Copyright 2011-2013 Google Inc.
|
||||
* Copyright 2013 mike wakerly <opensource@hoho.com>
|
||||
*
|
||||
* Project home page: https://github.com/mik3y/usb-serial-for-android
|
||||
*/
|
||||
|
||||
package org.emulator.calculator.usbserial.driver;
|
||||
|
||||
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.EnumSet;
|
||||
|
||||
/**
|
||||
* A base class shared by several driver implementations.
|
||||
*
|
||||
* @author mike wakerly (opensource@hoho.com)
|
||||
*/
|
||||
public abstract class CommonUsbSerialPort implements UsbSerialPort {
|
||||
|
||||
private static final String TAG = CommonUsbSerialPort.class.getSimpleName();
|
||||
private static final int DEFAULT_WRITE_BUFFER_SIZE = 16 * 1024;
|
||||
private static final int MAX_READ_SIZE = 16 * 1024; // = old bulkTransfer limit
|
||||
|
||||
protected final UsbDevice mDevice;
|
||||
protected final int mPortNumber;
|
||||
|
||||
// non-null when open()
|
||||
protected UsbDeviceConnection mConnection = null;
|
||||
protected UsbEndpoint mReadEndpoint;
|
||||
protected UsbEndpoint mWriteEndpoint;
|
||||
protected UsbRequest mUsbRequest;
|
||||
|
||||
protected final Object mWriteBufferLock = new Object();
|
||||
/** Internal write buffer. Guarded by {@link #mWriteBufferLock}. */
|
||||
protected byte[] mWriteBuffer;
|
||||
|
||||
public CommonUsbSerialPort(UsbDevice device, int portNumber) {
|
||||
mDevice = device;
|
||||
mPortNumber = portNumber;
|
||||
|
||||
mWriteBuffer = new byte[DEFAULT_WRITE_BUFFER_SIZE];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("<%s device_name=%s device_id=%s port_number=%s>",
|
||||
getClass().getSimpleName(), mDevice.getDeviceName(),
|
||||
mDevice.getDeviceId(), mPortNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UsbDevice getDevice() {
|
||||
return mDevice;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPortNumber() {
|
||||
return mPortNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the device serial number
|
||||
* @return serial number
|
||||
*/
|
||||
@Override
|
||||
public String getSerial() {
|
||||
return mConnection.getSerial();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 void open(UsbDeviceConnection connection) throws IOException {
|
||||
if (mConnection != null) {
|
||||
throw new IOException("Already open");
|
||||
}
|
||||
mConnection = connection;
|
||||
try {
|
||||
openInt(connection);
|
||||
if (mReadEndpoint == null || mWriteEndpoint == null) {
|
||||
throw new IOException("Could not get read & write endpoints");
|
||||
}
|
||||
mUsbRequest = new UsbRequest();
|
||||
mUsbRequest.initialize(mConnection, mReadEndpoint);
|
||||
} catch(Exception e) {
|
||||
close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void openInt(UsbDeviceConnection connection) throws IOException;
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (mConnection == null) {
|
||||
throw new IOException("Already closed");
|
||||
}
|
||||
try {
|
||||
mUsbRequest.cancel();
|
||||
} catch(Exception ignored) {}
|
||||
mUsbRequest = null;
|
||||
try {
|
||||
closeInt();
|
||||
} catch(Exception ignored) {}
|
||||
try {
|
||||
mConnection.close();
|
||||
} catch(Exception ignored) {}
|
||||
mConnection = null;
|
||||
}
|
||||
|
||||
protected abstract void closeInt();
|
||||
|
||||
/**
|
||||
* use simple USB request supported by all devices to test if connection is still valid
|
||||
*/
|
||||
protected void testConnection() throws IOException {
|
||||
byte[] buf = new byte[2];
|
||||
int len = mConnection.controlTransfer(0x80 /*DEVICE*/, 0 /*GET_STATUS*/, 0, 0, buf, buf.length, 200);
|
||||
if(len < 0)
|
||||
throw new IOException("USB get_status request failed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(final byte[] dest, final int timeout) throws IOException {
|
||||
return read(dest, timeout, true);
|
||||
}
|
||||
|
||||
protected int read(final byte[] dest, final int timeout, boolean testConnection) throws IOException {
|
||||
if(mConnection == null) {
|
||||
throw new IOException("Connection closed");
|
||||
}
|
||||
if(dest.length <= 0) {
|
||||
throw new IllegalArgumentException("Read buffer to small");
|
||||
}
|
||||
final int nread;
|
||||
if (timeout != 0) {
|
||||
// bulkTransfer will cause data loss with short timeout + high baud rates + continuous transfer
|
||||
// https://stackoverflow.com/questions/9108548/android-usb-host-bulktransfer-is-losing-data
|
||||
// but mConnection.requestWait(timeout) available since Android 8.0 es even worse,
|
||||
// as it crashes with short timeout, e.g.
|
||||
// A/libc: Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x276a in tid 29846 (pool-2-thread-1), pid 29618 (.usbserial.test)
|
||||
// /system/lib64/libusbhost.so (usb_request_wait+192)
|
||||
// /system/lib64/libandroid_runtime.so (android_hardware_UsbDeviceConnection_request_wait(_JNIEnv*, _jobject*, long)+84)
|
||||
// data loss / crashes were observed with timeout up to 200 msec
|
||||
long endTime = testConnection ? System.currentTimeMillis() + timeout : 0;
|
||||
int readMax = Math.min(dest.length, MAX_READ_SIZE);
|
||||
nread = mConnection.bulkTransfer(mReadEndpoint, dest, readMax, timeout);
|
||||
// Android error propagation is improvable, nread == -1 can be: timeout, connection lost, buffer undersized, ...
|
||||
if(nread == -1 && testConnection && System.currentTimeMillis() < endTime)
|
||||
testConnection();
|
||||
|
||||
} else {
|
||||
final ByteBuffer buf = ByteBuffer.wrap(dest);
|
||||
if (!mUsbRequest.queue(buf, dest.length)) {
|
||||
throw new IOException("Queueing USB request failed");
|
||||
}
|
||||
final UsbRequest response = mConnection.requestWait();
|
||||
if (response == null) {
|
||||
throw new IOException("Waiting for USB request failed");
|
||||
}
|
||||
nread = buf.position();
|
||||
}
|
||||
if (nread > 0)
|
||||
return nread;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int write(final byte[] src, final int timeout) throws IOException {
|
||||
int offset = 0;
|
||||
|
||||
if(mConnection == null) {
|
||||
throw new IOException("Connection closed");
|
||||
}
|
||||
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, timeout);
|
||||
}
|
||||
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 boolean isOpen() {
|
||||
return mConnection != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException;
|
||||
|
||||
@Override
|
||||
public boolean getCD() throws IOException { throw new UnsupportedOperationException(); }
|
||||
|
||||
@Override
|
||||
public boolean getCTS() throws IOException { throw new UnsupportedOperationException(); }
|
||||
|
||||
@Override
|
||||
public boolean getDSR() throws IOException { throw new UnsupportedOperationException(); }
|
||||
|
||||
@Override
|
||||
public boolean getDTR() throws IOException { throw new UnsupportedOperationException(); }
|
||||
|
||||
@Override
|
||||
public void setDTR(boolean value) throws IOException { throw new UnsupportedOperationException(); }
|
||||
|
||||
@Override
|
||||
public boolean getRI() throws IOException { throw new UnsupportedOperationException(); }
|
||||
|
||||
@Override
|
||||
public boolean getRTS() throws IOException { throw new UnsupportedOperationException(); }
|
||||
|
||||
@Override
|
||||
public void setRTS(boolean value) throws IOException { throw new UnsupportedOperationException(); }
|
||||
|
||||
@Override
|
||||
public abstract EnumSet<ControlLine> getControlLines() throws IOException;
|
||||
|
||||
@Override
|
||||
public abstract EnumSet<ControlLine> getSupportedControlLines() throws IOException;
|
||||
|
||||
@Override
|
||||
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBreak(boolean value) throws IOException { throw new UnsupportedOperationException(); }
|
||||
|
||||
}
|
|
@ -0,0 +1,335 @@
|
|||
/* Copyright 2011-2013 Google Inc.
|
||||
* Copyright 2013 mike wakerly <opensource@hoho.com>
|
||||
*
|
||||
* Project home page: https://github.com/mik3y/usb-serial-for-android
|
||||
*/
|
||||
|
||||
package org.emulator.calculator.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 java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class Cp21xxSerialDriver implements UsbSerialDriver {
|
||||
|
||||
private static final String TAG = Cp21xxSerialDriver.class.getSimpleName();
|
||||
|
||||
private final UsbDevice mDevice;
|
||||
private final List<UsbSerialPort> mPorts;
|
||||
|
||||
public Cp21xxSerialDriver(UsbDevice device) {
|
||||
mDevice = device;
|
||||
mPorts = new ArrayList<>();
|
||||
for( int port = 0; port < device.getInterfaceCount(); port++) {
|
||||
mPorts.add(new Cp21xxSerialPort(mDevice, port));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UsbDevice getDevice() {
|
||||
return mDevice;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UsbSerialPort> getPorts() {
|
||||
return mPorts;
|
||||
}
|
||||
|
||||
public class Cp21xxSerialPort extends CommonUsbSerialPort {
|
||||
|
||||
private static final int USB_WRITE_TIMEOUT_MILLIS = 5000;
|
||||
|
||||
/*
|
||||
* Configuration Request Types
|
||||
*/
|
||||
private static final int REQTYPE_HOST_TO_DEVICE = 0x41;
|
||||
private static final int REQTYPE_DEVICE_TO_HOST = 0xc1;
|
||||
|
||||
/*
|
||||
* Configuration Request Codes
|
||||
*/
|
||||
private static final int SILABSER_IFC_ENABLE_REQUEST_CODE = 0x00;
|
||||
private static final int SILABSER_SET_LINE_CTL_REQUEST_CODE = 0x03;
|
||||
private static final int SILABSER_SET_BREAK_REQUEST_CODE = 0x05;
|
||||
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 SILABSER_GET_MDMSTS_REQUEST_CODE = 0x08;
|
||||
|
||||
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_MHS_REQUEST_CODE
|
||||
*/
|
||||
private static final int DTR_ENABLE = 0x101;
|
||||
private static final int DTR_DISABLE = 0x100;
|
||||
private static final int RTS_ENABLE = 0x202;
|
||||
private static final int RTS_DISABLE = 0x200;
|
||||
|
||||
/*
|
||||
* SILABSER_GET_MDMSTS_REQUEST_CODE
|
||||
*/
|
||||
private static final int STATUS_CTS = 0x10;
|
||||
private static final int STATUS_DSR = 0x20;
|
||||
private static final int STATUS_RI = 0x40;
|
||||
private static final int STATUS_CD = 0x80;
|
||||
|
||||
|
||||
private boolean dtr = false;
|
||||
private boolean rts = false;
|
||||
|
||||
// second port of Cp2105 has limited baudRate, dataBits, stopBits, parity
|
||||
// unsupported baudrate returns error at controlTransfer(), other parameters are silently ignored
|
||||
private boolean mIsRestrictedPort;
|
||||
|
||||
public Cp21xxSerialPort(UsbDevice device, int portNumber) {
|
||||
super(device, portNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UsbSerialDriver getDriver() {
|
||||
return Cp21xxSerialDriver.this;
|
||||
}
|
||||
|
||||
private void setConfigSingle(int request, int value) throws IOException {
|
||||
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request, value,
|
||||
mPortNumber, null, 0, USB_WRITE_TIMEOUT_MILLIS);
|
||||
if (result != 0) {
|
||||
throw new IOException("Control transfer failed: " + request + " / " + value + " -> " + result);
|
||||
}
|
||||
}
|
||||
|
||||
private byte getStatus() throws IOException {
|
||||
byte[] buffer = new byte[1];
|
||||
int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, SILABSER_GET_MDMSTS_REQUEST_CODE, 0,
|
||||
mPortNumber, buffer, buffer.length, USB_WRITE_TIMEOUT_MILLIS);
|
||||
if (result != 1) {
|
||||
throw new IOException("Control transfer failed: " + SILABSER_GET_MDMSTS_REQUEST_CODE + " / " + 0 + " -> " + result);
|
||||
}
|
||||
return buffer[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void openInt(UsbDeviceConnection connection) throws IOException {
|
||||
mIsRestrictedPort = mDevice.getInterfaceCount() == 2 && mPortNumber == 1;
|
||||
if(mPortNumber >= mDevice.getInterfaceCount()) {
|
||||
throw new IOException("Unknown port number");
|
||||
}
|
||||
UsbInterface dataIface = mDevice.getInterface(mPortNumber);
|
||||
if (!mConnection.claimInterface(dataIface, true)) {
|
||||
throw new IOException("Could not claim interface " + mPortNumber);
|
||||
}
|
||||
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, (dtr ? DTR_ENABLE : DTR_DISABLE) | (rts ? RTS_ENABLE : RTS_DISABLE));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void closeInt() {
|
||||
try {
|
||||
setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_DISABLE);
|
||||
} catch (Exception ignored) {}
|
||||
try {
|
||||
mConnection.releaseInterface(mDevice.getInterface(mPortNumber));
|
||||
} catch(Exception ignored) {}
|
||||
}
|
||||
|
||||
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, mPortNumber, 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 {
|
||||
if(baudRate <= 0) {
|
||||
throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
|
||||
}
|
||||
setBaudRate(baudRate);
|
||||
|
||||
int configDataBits = 0;
|
||||
switch (dataBits) {
|
||||
case DATABITS_5:
|
||||
if(mIsRestrictedPort)
|
||||
throw new UnsupportedOperationException("Unsupported data bits: " + dataBits);
|
||||
configDataBits |= 0x0500;
|
||||
break;
|
||||
case DATABITS_6:
|
||||
if(mIsRestrictedPort)
|
||||
throw new UnsupportedOperationException("Unsupported data bits: " + dataBits);
|
||||
configDataBits |= 0x0600;
|
||||
break;
|
||||
case DATABITS_7:
|
||||
if(mIsRestrictedPort)
|
||||
throw new UnsupportedOperationException("Unsupported data bits: " + dataBits);
|
||||
configDataBits |= 0x0700;
|
||||
break;
|
||||
case DATABITS_8:
|
||||
configDataBits |= 0x0800;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid data bits: " + dataBits);
|
||||
}
|
||||
|
||||
switch (parity) {
|
||||
case PARITY_NONE:
|
||||
break;
|
||||
case PARITY_ODD:
|
||||
configDataBits |= 0x0010;
|
||||
break;
|
||||
case PARITY_EVEN:
|
||||
configDataBits |= 0x0020;
|
||||
break;
|
||||
case PARITY_MARK:
|
||||
if(mIsRestrictedPort)
|
||||
throw new UnsupportedOperationException("Unsupported parity: mark");
|
||||
configDataBits |= 0x0030;
|
||||
break;
|
||||
case PARITY_SPACE:
|
||||
if(mIsRestrictedPort)
|
||||
throw new UnsupportedOperationException("Unsupported parity: space");
|
||||
configDataBits |= 0x0040;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid parity: " + parity);
|
||||
}
|
||||
|
||||
switch (stopBits) {
|
||||
case STOPBITS_1:
|
||||
break;
|
||||
case STOPBITS_1_5:
|
||||
throw new UnsupportedOperationException("Unsupported stop bits: 1.5");
|
||||
case STOPBITS_2:
|
||||
if(mIsRestrictedPort)
|
||||
throw new UnsupportedOperationException("Unsupported stop bits: 2");
|
||||
configDataBits |= 2;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid stop bits: " + stopBits);
|
||||
}
|
||||
setConfigSingle(SILABSER_SET_LINE_CTL_REQUEST_CODE, configDataBits);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getCD() throws IOException {
|
||||
return (getStatus() & STATUS_CD) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getCTS() throws IOException {
|
||||
return (getStatus() & STATUS_CTS) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getDSR() throws IOException {
|
||||
return (getStatus() & STATUS_DSR) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getDTR() throws IOException {
|
||||
return dtr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDTR(boolean value) throws IOException {
|
||||
dtr = value;
|
||||
setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, dtr ? DTR_ENABLE : DTR_DISABLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getRI() throws IOException {
|
||||
return (getStatus() & STATUS_RI) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getRTS() throws IOException {
|
||||
return rts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRTS(boolean value) throws IOException {
|
||||
rts = value;
|
||||
setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, rts ? RTS_ENABLE : RTS_DISABLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnumSet<ControlLine> getControlLines() throws IOException {
|
||||
byte status = getStatus();
|
||||
EnumSet<ControlLine> set = EnumSet.noneOf(ControlLine.class);
|
||||
if(rts) set.add(ControlLine.RTS);
|
||||
if((status & STATUS_CTS) != 0) set.add(ControlLine.CTS);
|
||||
if(dtr) set.add(ControlLine.DTR);
|
||||
if((status & STATUS_DSR) != 0) set.add(ControlLine.DSR);
|
||||
if((status & STATUS_CD) != 0) set.add(ControlLine.CD);
|
||||
if((status & STATUS_RI) != 0) set.add(ControlLine.RI);
|
||||
return set;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnumSet<ControlLine> getSupportedControlLines() throws IOException {
|
||||
return EnumSet.allOf(ControlLine.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
// note: only working on some devices, on other devices ignored w/o error
|
||||
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException {
|
||||
int value = (purgeReadBuffers ? FLUSH_READ_CODE : 0)
|
||||
| (purgeWriteBuffers ? FLUSH_WRITE_CODE : 0);
|
||||
|
||||
if (value != 0) {
|
||||
setConfigSingle(SILABSER_FLUSH_REQUEST_CODE, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBreak(boolean value) throws IOException {
|
||||
setConfigSingle(SILABSER_SET_BREAK_REQUEST_CODE, value ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<Integer, int[]> getSupportedDevices() {
|
||||
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<Integer, int[]>();
|
||||
supportedDevices.put(UsbId.VENDOR_SILABS,
|
||||
new int[] {
|
||||
UsbId.SILABS_CP2102, // same ID for CP2101, CP2103, CP2104, CP2109
|
||||
UsbId.SILABS_CP2105,
|
||||
UsbId.SILABS_CP2108,
|
||||
});
|
||||
return supportedDevices;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,428 @@
|
|||
/* Copyright 2011-2013 Google Inc.
|
||||
* Copyright 2013 mike wakerly <opensource@hoho.com>
|
||||
* Copyright 2020 kai morich <mail@kai-morich.de>
|
||||
*
|
||||
* Project home page: https://github.com/mik3y/usb-serial-for-android
|
||||
*/
|
||||
|
||||
package org.emulator.calculator.usbserial.driver;
|
||||
|
||||
import android.hardware.usb.UsbConstants;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.hardware.usb.UsbDeviceConnection;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/*
|
||||
* driver is implemented from various information scattered over FTDI documentation
|
||||
*
|
||||
* baud rate calculation https://www.ftdichip.com/Support/Documents/AppNotes/AN232B-05_BaudRates.pdf
|
||||
* control bits https://www.ftdichip.com/Firmware/Precompiled/UM_VinculumFirmware_V205.pdf
|
||||
* device type https://www.ftdichip.com/Support/Documents/AppNotes/AN_233_Java_D2XX_for_Android_API_User_Manual.pdf -> bvdDevice
|
||||
*
|
||||
*/
|
||||
|
||||
public class FtdiSerialDriver implements UsbSerialDriver {
|
||||
|
||||
private static final String TAG = FtdiSerialPort.class.getSimpleName();
|
||||
|
||||
private final UsbDevice mDevice;
|
||||
private final List<UsbSerialPort> mPorts;
|
||||
|
||||
public FtdiSerialDriver(UsbDevice device) {
|
||||
mDevice = device;
|
||||
mPorts = new ArrayList<>();
|
||||
for( int port = 0; port < device.getInterfaceCount(); port++) {
|
||||
mPorts.add(new FtdiSerialPort(mDevice, port));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UsbDevice getDevice() {
|
||||
return mDevice;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UsbSerialPort> getPorts() {
|
||||
return mPorts;
|
||||
}
|
||||
|
||||
public class FtdiSerialPort extends CommonUsbSerialPort {
|
||||
|
||||
private static final int USB_WRITE_TIMEOUT_MILLIS = 5000;
|
||||
private static final int READ_HEADER_LENGTH = 2; // contains MODEM_STATUS
|
||||
|
||||
private static final int REQTYPE_HOST_TO_DEVICE = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_OUT;
|
||||
private static final int REQTYPE_DEVICE_TO_HOST = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_IN;
|
||||
|
||||
private static final int RESET_REQUEST = 0;
|
||||
private static final int MODEM_CONTROL_REQUEST = 1;
|
||||
private static final int SET_BAUD_RATE_REQUEST = 3;
|
||||
private static final int SET_DATA_REQUEST = 4;
|
||||
private static final int GET_MODEM_STATUS_REQUEST = 5;
|
||||
private static final int SET_LATENCY_TIMER_REQUEST = 9;
|
||||
private static final int GET_LATENCY_TIMER_REQUEST = 10;
|
||||
|
||||
private static final int MODEM_CONTROL_DTR_ENABLE = 0x0101;
|
||||
private static final int MODEM_CONTROL_DTR_DISABLE = 0x0100;
|
||||
private static final int MODEM_CONTROL_RTS_ENABLE = 0x0202;
|
||||
private static final int MODEM_CONTROL_RTS_DISABLE = 0x0200;
|
||||
private static final int MODEM_STATUS_CTS = 0x10;
|
||||
private static final int MODEM_STATUS_DSR = 0x20;
|
||||
private static final int MODEM_STATUS_RI = 0x40;
|
||||
private static final int MODEM_STATUS_CD = 0x80;
|
||||
private static final int RESET_ALL = 0;
|
||||
private static final int RESET_PURGE_RX = 1;
|
||||
private static final int RESET_PURGE_TX = 2;
|
||||
|
||||
private boolean baudRateWithPort = false;
|
||||
private boolean dtr = false;
|
||||
private boolean rts = false;
|
||||
private int breakConfig = 0;
|
||||
|
||||
public FtdiSerialPort(UsbDevice device, int portNumber) {
|
||||
super(device, portNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UsbSerialDriver getDriver() {
|
||||
return FtdiSerialDriver.this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void openInt(UsbDeviceConnection connection) throws IOException {
|
||||
if (!connection.claimInterface(mDevice.getInterface(mPortNumber), true)) {
|
||||
throw new IOException("Could not claim interface " + mPortNumber);
|
||||
}
|
||||
if (mDevice.getInterface(mPortNumber).getEndpointCount() < 2) {
|
||||
throw new IOException("Not enough endpoints");
|
||||
}
|
||||
mReadEndpoint = mDevice.getInterface(mPortNumber).getEndpoint(0);
|
||||
mWriteEndpoint = mDevice.getInterface(mPortNumber).getEndpoint(1);
|
||||
|
||||
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST,
|
||||
RESET_ALL, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
|
||||
if (result != 0) {
|
||||
throw new IOException("Reset failed: result=" + result);
|
||||
}
|
||||
result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST,
|
||||
(dtr ? MODEM_CONTROL_DTR_ENABLE : MODEM_CONTROL_DTR_DISABLE) |
|
||||
(rts ? MODEM_CONTROL_RTS_ENABLE : MODEM_CONTROL_RTS_DISABLE),
|
||||
mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
|
||||
if (result != 0) {
|
||||
throw new IOException("Init RTS,DTR failed: result=" + result);
|
||||
}
|
||||
|
||||
// mDevice.getVersion() would require API 23
|
||||
byte[] rawDescriptors = connection.getRawDescriptors();
|
||||
if(rawDescriptors == null || rawDescriptors.length < 14) {
|
||||
throw new IOException("Could not get device descriptors");
|
||||
}
|
||||
int deviceType = rawDescriptors[13];
|
||||
baudRateWithPort = deviceType == 7 || deviceType == 8 || deviceType == 9; // ...H devices
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void closeInt() {
|
||||
try {
|
||||
mConnection.releaseInterface(mDevice.getInterface(mPortNumber));
|
||||
} catch(Exception ignored) {}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(final byte[] dest, final int timeout) throws IOException {
|
||||
if(dest.length <= READ_HEADER_LENGTH) {
|
||||
throw new IllegalArgumentException("Read buffer to small");
|
||||
// could allocate larger buffer, including space for 2 header bytes, but this would
|
||||
// result in buffers not being 64 byte aligned any more, causing data loss at continuous
|
||||
// data transfer at high baud rates when buffers are fully filled.
|
||||
}
|
||||
int nread;
|
||||
if (timeout != 0) {
|
||||
long endTime = System.currentTimeMillis() + timeout;
|
||||
do {
|
||||
nread = super.read(dest, Math.max(1, (int)(endTime - System.currentTimeMillis())), false);
|
||||
} while (nread == READ_HEADER_LENGTH && System.currentTimeMillis() < endTime);
|
||||
if(nread <= 0 && System.currentTimeMillis() < endTime)
|
||||
testConnection();
|
||||
} else {
|
||||
do {
|
||||
nread = super.read(dest, timeout, false);
|
||||
} while (nread == READ_HEADER_LENGTH);
|
||||
}
|
||||
return readFilter(dest, nread);
|
||||
}
|
||||
|
||||
private int readFilter(byte[] buffer, int totalBytesRead) throws IOException {
|
||||
final int maxPacketSize = mReadEndpoint.getMaxPacketSize();
|
||||
int destPos = 0;
|
||||
for(int srcPos = 0; srcPos < totalBytesRead; srcPos += maxPacketSize) {
|
||||
int length = Math.min(srcPos + maxPacketSize, totalBytesRead) - (srcPos + READ_HEADER_LENGTH);
|
||||
if (length < 0)
|
||||
throw new IOException("Expected at least " + READ_HEADER_LENGTH + " bytes");
|
||||
System.arraycopy(buffer, srcPos + READ_HEADER_LENGTH, buffer, destPos, length);
|
||||
destPos += length;
|
||||
}
|
||||
//Log.d(TAG, "read filter " + totalBytesRead + " -> " + destPos);
|
||||
return destPos;
|
||||
}
|
||||
|
||||
private void setBaudrate(int baudRate) throws IOException {
|
||||
int divisor, subdivisor, effectiveBaudRate;
|
||||
if (baudRate > 3500000) {
|
||||
throw new UnsupportedOperationException("Baud rate to high");
|
||||
} else if(baudRate >= 2500000) {
|
||||
divisor = 0;
|
||||
subdivisor = 0;
|
||||
effectiveBaudRate = 3000000;
|
||||
} else if(baudRate >= 1750000) {
|
||||
divisor = 1;
|
||||
subdivisor = 0;
|
||||
effectiveBaudRate = 2000000;
|
||||
} else {
|
||||
divisor = (24000000 << 1) / baudRate;
|
||||
divisor = (divisor + 1) >> 1; // round
|
||||
subdivisor = divisor & 0x07;
|
||||
divisor >>= 3;
|
||||
if (divisor > 0x3fff) // exceeds bit 13 at 183 baud
|
||||
throw new UnsupportedOperationException("Baud rate to low");
|
||||
effectiveBaudRate = (24000000 << 1) / ((divisor << 3) + subdivisor);
|
||||
effectiveBaudRate = (effectiveBaudRate +1) >> 1;
|
||||
}
|
||||
double baudRateError = Math.abs(1.0 - (effectiveBaudRate / (double)baudRate));
|
||||
if(baudRateError >= 0.031) // can happen only > 1.5Mbaud
|
||||
throw new UnsupportedOperationException(String.format("Baud rate deviation %.1f%% is higher than allowed 3%%", baudRateError*100));
|
||||
int value = divisor;
|
||||
int index = 0;
|
||||
switch(subdivisor) {
|
||||
case 0: break; // 16,15,14 = 000 - sub-integer divisor = 0
|
||||
case 4: value |= 0x4000; break; // 16,15,14 = 001 - sub-integer divisor = 0.5
|
||||
case 2: value |= 0x8000; break; // 16,15,14 = 010 - sub-integer divisor = 0.25
|
||||
case 1: value |= 0xc000; break; // 16,15,14 = 011 - sub-integer divisor = 0.125
|
||||
case 3: value |= 0x0000; index |= 1; break; // 16,15,14 = 100 - sub-integer divisor = 0.375
|
||||
case 5: value |= 0x4000; index |= 1; break; // 16,15,14 = 101 - sub-integer divisor = 0.625
|
||||
case 6: value |= 0x8000; index |= 1; break; // 16,15,14 = 110 - sub-integer divisor = 0.75
|
||||
case 7: value |= 0xc000; index |= 1; break; // 16,15,14 = 111 - sub-integer divisor = 0.875
|
||||
}
|
||||
if(baudRateWithPort) {
|
||||
index <<= 8;
|
||||
index |= mPortNumber+1;
|
||||
}
|
||||
Log.d(TAG, String.format("baud rate=%d, effective=%d, error=%.1f%%, value=0x%04x, index=0x%04x, divisor=%d, subdivisor=%d",
|
||||
baudRate, effectiveBaudRate, baudRateError*100, value, index, divisor, subdivisor));
|
||||
|
||||
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_BAUD_RATE_REQUEST,
|
||||
value, index, null, 0, USB_WRITE_TIMEOUT_MILLIS);
|
||||
if (result != 0) {
|
||||
throw new IOException("Setting baudrate failed: result=" + result);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException {
|
||||
if(baudRate <= 0) {
|
||||
throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
|
||||
}
|
||||
setBaudrate(baudRate);
|
||||
|
||||
int config = 0;
|
||||
switch (dataBits) {
|
||||
case DATABITS_5:
|
||||
case DATABITS_6:
|
||||
throw new UnsupportedOperationException("Unsupported data bits: " + dataBits);
|
||||
case DATABITS_7:
|
||||
case DATABITS_8:
|
||||
config |= dataBits;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid data bits: " + dataBits);
|
||||
}
|
||||
|
||||
switch (parity) {
|
||||
case PARITY_NONE:
|
||||
break;
|
||||
case PARITY_ODD:
|
||||
config |= 0x100;
|
||||
break;
|
||||
case PARITY_EVEN:
|
||||
config |= 0x200;
|
||||
break;
|
||||
case PARITY_MARK:
|
||||
config |= 0x300;
|
||||
break;
|
||||
case PARITY_SPACE:
|
||||
config |= 0x400;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid parity: " + parity);
|
||||
}
|
||||
|
||||
switch (stopBits) {
|
||||
case STOPBITS_1:
|
||||
break;
|
||||
case STOPBITS_1_5:
|
||||
throw new UnsupportedOperationException("Unsupported stop bits: 1.5");
|
||||
case STOPBITS_2:
|
||||
config |= 0x1000;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid stop bits: " + stopBits);
|
||||
}
|
||||
|
||||
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_DATA_REQUEST,
|
||||
config, mPortNumber+1,null, 0, USB_WRITE_TIMEOUT_MILLIS);
|
||||
if (result != 0) {
|
||||
throw new IOException("Setting parameters failed: result=" + result);
|
||||
}
|
||||
breakConfig = config;
|
||||
}
|
||||
|
||||
private int getStatus() throws IOException {
|
||||
byte[] data = new byte[2];
|
||||
int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, GET_MODEM_STATUS_REQUEST,
|
||||
0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS);
|
||||
if (result != 2) {
|
||||
throw new IOException("Get modem status failed: result=" + result);
|
||||
}
|
||||
return data[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getCD() throws IOException {
|
||||
return (getStatus() & MODEM_STATUS_CD) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getCTS() throws IOException {
|
||||
return (getStatus() & MODEM_STATUS_CTS) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getDSR() throws IOException {
|
||||
return (getStatus() & MODEM_STATUS_DSR) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getDTR() throws IOException {
|
||||
return dtr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDTR(boolean value) throws IOException {
|
||||
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST,
|
||||
value ? MODEM_CONTROL_DTR_ENABLE : MODEM_CONTROL_DTR_DISABLE, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
|
||||
if (result != 0) {
|
||||
throw new IOException("Set DTR failed: result=" + result);
|
||||
}
|
||||
dtr = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getRI() throws IOException {
|
||||
return (getStatus() & MODEM_STATUS_RI) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getRTS() throws IOException {
|
||||
return rts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRTS(boolean value) throws IOException {
|
||||
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST,
|
||||
value ? MODEM_CONTROL_RTS_ENABLE : MODEM_CONTROL_RTS_DISABLE, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
|
||||
if (result != 0) {
|
||||
throw new IOException("Set DTR failed: result=" + result);
|
||||
}
|
||||
rts = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnumSet<ControlLine> getControlLines() throws IOException {
|
||||
int status = getStatus();
|
||||
EnumSet<ControlLine> set = EnumSet.noneOf(ControlLine.class);
|
||||
if(rts) set.add(ControlLine.RTS);
|
||||
if((status & MODEM_STATUS_CTS) != 0) set.add(ControlLine.CTS);
|
||||
if(dtr) set.add(ControlLine.DTR);
|
||||
if((status & MODEM_STATUS_DSR) != 0) set.add(ControlLine.DSR);
|
||||
if((status & MODEM_STATUS_CD) != 0) set.add(ControlLine.CD);
|
||||
if((status & MODEM_STATUS_RI) != 0) set.add(ControlLine.RI);
|
||||
return set;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnumSet<ControlLine> getSupportedControlLines() throws IOException {
|
||||
return EnumSet.allOf(ControlLine.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException {
|
||||
if (purgeWriteBuffers) {
|
||||
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST,
|
||||
RESET_PURGE_RX, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
|
||||
if (result != 0) {
|
||||
throw new IOException("Purge write buffer failed: result=" + result);
|
||||
}
|
||||
}
|
||||
|
||||
if (purgeReadBuffers) {
|
||||
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST,
|
||||
RESET_PURGE_TX, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
|
||||
if (result != 0) {
|
||||
throw new IOException("Purge read buffer failed: result=" + result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBreak(boolean value) throws IOException {
|
||||
int config = breakConfig;
|
||||
if(value) config |= 0x4000;
|
||||
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_DATA_REQUEST,
|
||||
config, mPortNumber+1,null, 0, USB_WRITE_TIMEOUT_MILLIS);
|
||||
if (result != 0) {
|
||||
throw new IOException("Setting BREAK failed: result=" + result);
|
||||
}
|
||||
}
|
||||
|
||||
public void setLatencyTimer(int latencyTime) throws IOException {
|
||||
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_LATENCY_TIMER_REQUEST,
|
||||
latencyTime, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
|
||||
if (result != 0) {
|
||||
throw new IOException("Set latency timer failed: result=" + result);
|
||||
}
|
||||
}
|
||||
|
||||
public int getLatencyTimer() throws IOException {
|
||||
byte[] data = new byte[1];
|
||||
int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, GET_LATENCY_TIMER_REQUEST,
|
||||
0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS);
|
||||
if (result != 1) {
|
||||
throw new IOException("Get latency timer failed: result=" + result);
|
||||
}
|
||||
return data[0];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static Map<Integer, int[]> getSupportedDevices() {
|
||||
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<Integer, int[]>();
|
||||
supportedDevices.put(UsbId.VENDOR_FTDI,
|
||||
new int[] {
|
||||
UsbId.FTDI_FT232R,
|
||||
UsbId.FTDI_FT232H,
|
||||
UsbId.FTDI_FT2232H,
|
||||
UsbId.FTDI_FT4232H,
|
||||
UsbId.FTDI_FT231X, // same ID for FT230X, FT231X, FT234XD
|
||||
});
|
||||
return supportedDevices;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/* Copyright 2011-2013 Google Inc.
|
||||
* Copyright 2013 mike wakerly <opensource@hoho.com>
|
||||
*
|
||||
* Project home page: https://github.com/mik3y/usb-serial-for-android
|
||||
*/
|
||||
|
||||
package org.emulator.calculator.usbserial.driver;
|
||||
|
||||
import android.util.Pair;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Maps (vendor id, product id) pairs to the corresponding serial driver.
|
||||
*
|
||||
* @author mike wakerly (opensource@hoho.com)
|
||||
*/
|
||||
public class ProbeTable {
|
||||
|
||||
private final Map<Pair<Integer, Integer>, Class<? extends UsbSerialDriver>> mProbeTable =
|
||||
new LinkedHashMap<Pair<Integer,Integer>, Class<? extends UsbSerialDriver>>();
|
||||
|
||||
/**
|
||||
* Adds or updates a (vendor, product) pair in the table.
|
||||
*
|
||||
* @param vendorId the USB vendor id
|
||||
* @param productId the USB product id
|
||||
* @param driverClass the driver class responsible for this pair
|
||||
* @return {@code this}, for chaining
|
||||
*/
|
||||
public ProbeTable addProduct(int vendorId, int productId,
|
||||
Class<? extends UsbSerialDriver> driverClass) {
|
||||
mProbeTable.put(Pair.create(vendorId, productId), driverClass);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to add all supported products from
|
||||
* {@code getSupportedProducts} static method.
|
||||
*
|
||||
* @param driverClass
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
ProbeTable addDriver(Class<? extends UsbSerialDriver> driverClass) {
|
||||
final Method method;
|
||||
|
||||
try {
|
||||
method = driverClass.getMethod("getSupportedDevices");
|
||||
} catch (SecurityException | NoSuchMethodException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
final Map<Integer, int[]> devices;
|
||||
try {
|
||||
devices = (Map<Integer, int[]>) method.invoke(null);
|
||||
} catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
for (Map.Entry<Integer, int[]> entry : devices.entrySet()) {
|
||||
final int vendorId = entry.getKey();
|
||||
for (int productId : entry.getValue()) {
|
||||
addProduct(vendorId, productId, driverClass);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the driver for the given (vendor, product) pair, or {@code null}
|
||||
* if no match.
|
||||
*
|
||||
* @param vendorId the USB vendor id
|
||||
* @param productId the USB product id
|
||||
* @return the driver class matching this pair, or {@code null}
|
||||
*/
|
||||
public Class<? extends UsbSerialDriver> findDriver(int vendorId, int productId) {
|
||||
final Pair<Integer, Integer> pair = Pair.create(vendorId, productId);
|
||||
return mProbeTable.get(pair);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,507 @@
|
|||
/*
|
||||
* Ported to usb-serial-for-android by Felix Hädicke <felixhaedicke@web.de>
|
||||
*
|
||||
* Based on the pyprolific driver written by Emmanuel Blot <emmanuel.blot@free.fr>
|
||||
* See https://github.com/eblot/pyftdi
|
||||
*
|
||||
* Project home page: https://github.com/mik3y/usb-serial-for-android
|
||||
*/
|
||||
|
||||
package org.emulator.calculator.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.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ProlificSerialDriver implements UsbSerialDriver {
|
||||
|
||||
private final String TAG = ProlificSerialDriver.class.getSimpleName();
|
||||
|
||||
private final static int[] standardBaudRates = {
|
||||
75, 150, 300, 600, 1200, 1800, 2400, 3600, 4800, 7200, 9600, 14400, 19200,
|
||||
28800, 38400, 57600, 115200, 128000, 134400, 161280, 201600, 230400, 268800,
|
||||
403200, 460800, 614400, 806400, 921600, 1228800, 2457600, 3000000, 6000000
|
||||
};
|
||||
private enum DeviceType { DEVICE_TYPE_01, DEVICE_TYPE_HX};
|
||||
|
||||
private final UsbDevice mDevice;
|
||||
private final UsbSerialPort mPort;
|
||||
|
||||
public ProlificSerialDriver(UsbDevice device) {
|
||||
mDevice = device;
|
||||
mPort = new ProlificSerialPort(mDevice, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UsbSerialPort> getPorts() {
|
||||
return Collections.singletonList(mPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UsbDevice getDevice() {
|
||||
return mDevice;
|
||||
}
|
||||
|
||||
class ProlificSerialPort extends CommonUsbSerialPort {
|
||||
|
||||
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; // RX @ Prolific device = write @ usb-serial-for-android library
|
||||
private static final int FLUSH_TX_REQUEST = 0x09;
|
||||
|
||||
private static final int SET_LINE_REQUEST = 0x20; // same as CDC SET_LINE_CODING
|
||||
private static final int SET_CONTROL_REQUEST = 0x22; // same as CDC SET_CONTROL_LINE_STATE
|
||||
private static final int SEND_BREAK_REQUEST = 0x23; // same as CDC SEND_BREAK
|
||||
private static final int GET_CONTROL_REQUEST = 0x87;
|
||||
private static final int STATUS_NOTIFICATION = 0xa1; // similar to CDC SERIAL_STATE but different length
|
||||
|
||||
private static final int CONTROL_DTR = 0x01;
|
||||
private static final int CONTROL_RTS = 0x02;
|
||||
|
||||
private static final int GET_CONTROL_FLAG_CD = 0x02;
|
||||
private static final int GET_CONTROL_FLAG_DSR = 0x04;
|
||||
private static final int GET_CONTROL_FLAG_RI = 0x01;
|
||||
private static final int GET_CONTROL_FLAG_CTS = 0x08;
|
||||
|
||||
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 DeviceType mDeviceType = DeviceType.DEVICE_TYPE_HX;
|
||||
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();
|
||||
private boolean mStopReadStatusThread = false;
|
||||
private IOException mReadStatusException = null;
|
||||
|
||||
|
||||
public ProlificSerialPort(UsbDevice device, int portNumber) {
|
||||
super(device, portNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UsbSerialDriver getDriver() {
|
||||
return ProlificSerialDriver.this;
|
||||
}
|
||||
|
||||
private 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 0x%x failed: %d",value, result));
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private 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 0x%x failed: %d", value, result));
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] vendorIn(int value, int index, int length) throws IOException {
|
||||
return inControlTransfer(PROLIFIC_VENDOR_IN_REQTYPE, PROLIFIC_VENDOR_READ_REQUEST, value, index, length);
|
||||
}
|
||||
|
||||
private void vendorOut(int value, int index, byte[] data) throws IOException {
|
||||
outControlTransfer(PROLIFIC_VENDOR_OUT_REQTYPE, PROLIFIC_VENDOR_WRITE_REQUEST, value, index, data);
|
||||
}
|
||||
|
||||
private void resetDevice() throws IOException {
|
||||
purgeHwBuffers(true, true);
|
||||
}
|
||||
|
||||
private 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 == DeviceType.DEVICE_TYPE_HX) ? 0x44 : 0x24, null);
|
||||
}
|
||||
|
||||
private void setControlLines(int newControlLinesValue) throws IOException {
|
||||
ctrlOut(SET_CONTROL_REQUEST, newControlLinesValue, 0, null);
|
||||
mControlLinesValue = newControlLinesValue;
|
||||
}
|
||||
|
||||
private void readStatusThreadFunction() {
|
||||
try {
|
||||
while (!mStopReadStatusThread) {
|
||||
byte[] buffer = new byte[STATUS_BUFFER_SIZE];
|
||||
long endTime = System.currentTimeMillis() + 500;
|
||||
int readBytesCount = mConnection.bulkTransfer(mInterruptEndpoint, buffer, STATUS_BUFFER_SIZE, 500);
|
||||
if(readBytesCount == -1 && System.currentTimeMillis() < endTime)
|
||||
testConnection();
|
||||
if (readBytesCount > 0) {
|
||||
if (readBytesCount != STATUS_BUFFER_SIZE) {
|
||||
throw new IOException("Invalid status notification, expected " + STATUS_BUFFER_SIZE + " bytes, got " + readBytesCount);
|
||||
} else if(buffer[0] != (byte)STATUS_NOTIFICATION ) {
|
||||
throw new IOException("Invalid status notification, expected " + STATUS_NOTIFICATION + " request, got " + buffer[0]);
|
||||
} else {
|
||||
mStatus = buffer[STATUS_BYTE_IDX] & 0xff;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
mReadStatusException = e;
|
||||
}
|
||||
//Log.d(TAG, "end control line status thread " + mStopReadStatusThread + " " + (mReadStatusException == null ? "-" : mReadStatusException.getMessage()));
|
||||
}
|
||||
|
||||
private int getStatus() throws IOException {
|
||||
if ((mReadStatusThread == null) && (mReadStatusException == null)) {
|
||||
synchronized (mReadStatusThreadLock) {
|
||||
if (mReadStatusThread == null) {
|
||||
byte[] data = vendorIn(GET_CONTROL_REQUEST, 0, 1);
|
||||
mStatus = 0;
|
||||
if((data[0] & GET_CONTROL_FLAG_CTS) == 0) mStatus |= STATUS_FLAG_CTS;
|
||||
if((data[0] & GET_CONTROL_FLAG_DSR) == 0) mStatus |= STATUS_FLAG_DSR;
|
||||
if((data[0] & GET_CONTROL_FLAG_CD) == 0) mStatus |= STATUS_FLAG_CD;
|
||||
if((data[0] & GET_CONTROL_FLAG_RI) == 0) mStatus |= STATUS_FLAG_RI;
|
||||
//Log.d(TAG, "start control line status thread " + mStatus);
|
||||
mReadStatusThread = new Thread(this::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 boolean testStatusFlag(int flag) throws IOException {
|
||||
return ((getStatus() & flag) == flag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openInt(UsbDeviceConnection connection) throws IOException {
|
||||
UsbInterface usbInterface = mDevice.getInterface(0);
|
||||
|
||||
if (!connection.claimInterface(usbInterface, true)) {
|
||||
throw new IOException("Error claiming Prolific interface 0");
|
||||
}
|
||||
|
||||
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 = DeviceType.DEVICE_TYPE_01;
|
||||
} else {
|
||||
byte[] rawDescriptors = connection.getRawDescriptors();
|
||||
if(rawDescriptors == null || rawDescriptors.length <8) {
|
||||
Log.w(TAG, "Could not get device descriptors, Assuming that it is a HX device");
|
||||
mDeviceType = DeviceType.DEVICE_TYPE_HX;
|
||||
} else {
|
||||
byte maxPacketSize0 = rawDescriptors[7];
|
||||
if (maxPacketSize0 == 64) {
|
||||
mDeviceType = DeviceType.DEVICE_TYPE_HX;
|
||||
} else if ((mDevice.getDeviceClass() == 0x00)
|
||||
|| (mDevice.getDeviceClass() == 0xff)) {
|
||||
mDeviceType = DeviceType.DEVICE_TYPE_01;
|
||||
} else {
|
||||
Log.w(TAG, "Could not detect PL2303 subtype, Assuming that it is a HX device");
|
||||
mDeviceType = DeviceType.DEVICE_TYPE_HX;
|
||||
}
|
||||
}
|
||||
}
|
||||
setControlLines(mControlLinesValue);
|
||||
resetDevice();
|
||||
doBlackMagic();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeInt() {
|
||||
try {
|
||||
synchronized (mReadStatusThreadLock) {
|
||||
if (mReadStatusThread != null) {
|
||||
try {
|
||||
mStopReadStatusThread = true;
|
||||
mReadStatusThread.join();
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "An error occured while waiting for status read thread", e);
|
||||
}
|
||||
mStopReadStatusThread = false;
|
||||
mReadStatusThread = null;
|
||||
mReadStatusException = null;
|
||||
}
|
||||
}
|
||||
resetDevice();
|
||||
} catch(Exception ignored) {}
|
||||
try {
|
||||
mConnection.releaseInterface(mDevice.getInterface(0));
|
||||
} catch(Exception ignored) {}
|
||||
}
|
||||
|
||||
private int filterBaudRate(int baudRate) {
|
||||
// if(BuildConfig.DEBUG && (baudRate & (3<<29)) == (1<<29)) {
|
||||
// return baudRate & ~(1<<29); // for testing purposes accept without further checks
|
||||
// }
|
||||
if (baudRate <= 0) {
|
||||
throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
|
||||
}
|
||||
for(int br : standardBaudRates) {
|
||||
if (br == baudRate) {
|
||||
return baudRate;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Formula taken from Linux + FreeBSD.
|
||||
* baudrate = baseline / (mantissa * 4^exponent)
|
||||
* where
|
||||
* mantissa = buf[8:0]
|
||||
* exponent = buf[11:9]
|
||||
*
|
||||
* Note: The formula does not work for all PL2303 variants.
|
||||
* Ok for PL2303HX. Not ok for PL2303TA. Other variants unknown.
|
||||
*/
|
||||
int baseline, mantissa, exponent;
|
||||
baseline = 12000000 * 32;
|
||||
mantissa = baseline / baudRate;
|
||||
if (mantissa == 0) { // > unrealistic 384 MBaud
|
||||
throw new UnsupportedOperationException("Baud rate to high");
|
||||
}
|
||||
exponent = 0;
|
||||
while (mantissa >= 512) {
|
||||
if (exponent < 7) {
|
||||
mantissa >>= 2; /* divide by 4 */
|
||||
exponent++;
|
||||
} else { // < 45.8 baud
|
||||
throw new UnsupportedOperationException("Baud rate to low");
|
||||
}
|
||||
}
|
||||
int effectiveBaudRate = (baseline / mantissa) >> (exponent << 1);
|
||||
double baudRateError = Math.abs(1.0 - (effectiveBaudRate / (double)baudRate));
|
||||
if(baudRateError >= 0.031) // > unrealistic 11.6 Mbaud
|
||||
throw new UnsupportedOperationException(String.format("Baud rate deviation %.1f%% is higher than allowed 3%%", baudRateError*100));
|
||||
int buf = mantissa + (exponent<<9) + (1<<31);
|
||||
|
||||
Log.d(TAG, String.format("baud rate=%d, effective=%d, error=%.1f%%, value=0x%08x, mantissa=%d, exponent=%d",
|
||||
baudRate, effectiveBaudRate, baudRateError*100, buf, mantissa, exponent));
|
||||
return buf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException {
|
||||
baudRate = filterBaudRate(baudRate);
|
||||
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("Invalid stop bits: " + 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("Invalid parity: " + parity);
|
||||
}
|
||||
|
||||
if(dataBits < DATABITS_5 || dataBits > DATABITS_8) {
|
||||
throw new IllegalArgumentException("Invalid data bits: " + dataBits);
|
||||
}
|
||||
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) != 0;
|
||||
}
|
||||
|
||||
@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) != 0;
|
||||
}
|
||||
|
||||
@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 EnumSet<ControlLine> getControlLines() throws IOException {
|
||||
int status = getStatus();
|
||||
EnumSet<ControlLine> set = EnumSet.noneOf(ControlLine.class);
|
||||
if((mControlLinesValue & CONTROL_RTS) != 0) set.add(ControlLine.RTS);
|
||||
if((status & STATUS_FLAG_CTS) != 0) set.add(ControlLine.CTS);
|
||||
if((mControlLinesValue & CONTROL_DTR) != 0) set.add(ControlLine.DTR);
|
||||
if((status & STATUS_FLAG_DSR) != 0) set.add(ControlLine.DSR);
|
||||
if((status & STATUS_FLAG_CD) != 0) set.add(ControlLine.CD);
|
||||
if((status & STATUS_FLAG_RI) != 0) set.add(ControlLine.RI);
|
||||
return set;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnumSet<ControlLine> getSupportedControlLines() throws IOException {
|
||||
return EnumSet.allOf(ControlLine.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException {
|
||||
if (purgeWriteBuffers) {
|
||||
vendorOut(FLUSH_RX_REQUEST, 0, null);
|
||||
}
|
||||
|
||||
if (purgeReadBuffers) {
|
||||
vendorOut(FLUSH_TX_REQUEST, 0, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBreak(boolean value) throws IOException {
|
||||
ctrlOut(SEND_BREAK_REQUEST, value ? 0xffff : 0, 0, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<Integer, int[]> getSupportedDevices() {
|
||||
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<Integer, int[]>();
|
||||
supportedDevices.put(UsbId.VENDOR_PROLIFIC,
|
||||
new int[] { UsbId.PROLIFIC_PL2303, });
|
||||
return supportedDevices;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/* Copyright 2011-2013 Google Inc.
|
||||
* Copyright 2013 mike wakerly <opensource@hoho.com>
|
||||
*
|
||||
* Project home page: https://github.com/mik3y/usb-serial-for-android
|
||||
*/
|
||||
|
||||
package org.emulator.calculator.usbserial.driver;
|
||||
|
||||
/**
|
||||
* Registry of USB vendor/product ID constants.
|
||||
*
|
||||
* Culled from various sources; see
|
||||
* <a href="http://www.linux-usb.org/usb.ids">usb.ids</a> 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_FT2232H = 0x6010;
|
||||
public static final int FTDI_FT4232H = 0x6011;
|
||||
public static final int FTDI_FT232H = 0x6014;
|
||||
public static final int FTDI_FT231X = 0x6015; // same ID for FT230X, FT231X, FT234XD
|
||||
|
||||
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 ARDUINO_MICRO = 0x8037;
|
||||
|
||||
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_SILABS = 0x10c4;
|
||||
public static final int SILABS_CP2102 = 0xea60; // same ID for CP2101, CP2103, CP2104, CP2109
|
||||
public static final int SILABS_CP2105 = 0xea70;
|
||||
public static final int SILABS_CP2108 = 0xea71;
|
||||
|
||||
public static final int VENDOR_PROLIFIC = 0x067b;
|
||||
public static final int PROLIFIC_PL2303 = 0x2303;
|
||||
|
||||
public static final int VENDOR_QINHENG = 0x1a86;
|
||||
public static final int QINHENG_CH340 = 0x7523;
|
||||
public static final int QINHENG_CH341A = 0x5523;
|
||||
|
||||
// at www.linux-usb.org/usb.ids listed for NXP/LPC1768, but all processors supported by ARM mbed DAPLink firmware report these ids
|
||||
public static final int VENDOR_ARM = 0x0d28;
|
||||
public static final int ARM_MBED = 0x0204;
|
||||
|
||||
private UsbId() {
|
||||
throw new IllegalAccessError("Non-instantiable class");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/* Copyright 2011-2013 Google Inc.
|
||||
* Copyright 2013 mike wakerly <opensource@hoho.com>
|
||||
*
|
||||
* Project home page: https://github.com/mik3y/usb-serial-for-android
|
||||
*/
|
||||
|
||||
package org.emulator.calculator.usbserial.driver;
|
||||
|
||||
import android.hardware.usb.UsbDevice;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author mike wakerly (opensource@hoho.com)
|
||||
*/
|
||||
public interface UsbSerialDriver {
|
||||
|
||||
/**
|
||||
* Returns the raw {@link UsbDevice} backing this port.
|
||||
*
|
||||
* @return the device
|
||||
*/
|
||||
public UsbDevice getDevice();
|
||||
|
||||
/**
|
||||
* Returns all available ports for this device. This list must have at least
|
||||
* one entry.
|
||||
*
|
||||
* @return the ports
|
||||
*/
|
||||
public List<UsbSerialPort> getPorts();
|
||||
}
|
|
@ -0,0 +1,263 @@
|
|||
/* Copyright 2011-2013 Google Inc.
|
||||
* Copyright 2013 mike wakerly <opensource@hoho.com>
|
||||
*
|
||||
* Project home page: https://github.com/mik3y/usb-serial-for-android
|
||||
*/
|
||||
|
||||
package org.emulator.calculator.usbserial.driver;
|
||||
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.hardware.usb.UsbDeviceConnection;
|
||||
import android.hardware.usb.UsbManager;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* Interface for a single serial port.
|
||||
*
|
||||
* @author mike wakerly (opensource@hoho.com)
|
||||
*/
|
||||
public interface UsbSerialPort extends Closeable {
|
||||
|
||||
/** 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;
|
||||
|
||||
/** values for get[Supported]ControlLines() */
|
||||
public enum ControlLine { RTS, CTS, DTR, DSR, CD, RI };
|
||||
|
||||
/**
|
||||
* Returns the driver used by this port.
|
||||
*/
|
||||
public UsbSerialDriver getDriver();
|
||||
|
||||
/**
|
||||
* Returns the currently-bound USB device.
|
||||
*/
|
||||
public UsbDevice getDevice();
|
||||
|
||||
/**
|
||||
* Port number within driver.
|
||||
*/
|
||||
public int getPortNumber();
|
||||
|
||||
/**
|
||||
* The serial number of the underlying UsbDeviceConnection, or {@code null}.
|
||||
*
|
||||
* @return value from {@link UsbDeviceConnection#getSerial()}
|
||||
* @throws SecurityException starting with target SDK 29 (Android 10) if permission for USB device is not granted
|
||||
*/
|
||||
public String getSerial();
|
||||
|
||||
/**
|
||||
* Opens and initializes the port. Upon success, caller must ensure that
|
||||
* {@link #close()} is eventually called.
|
||||
*
|
||||
* @param connection an open device connection, acquired with
|
||||
* {@link UsbManager#openDevice(UsbDevice)}
|
||||
* @throws IOException on error opening or initializing the port.
|
||||
*/
|
||||
public void open(UsbDeviceConnection connection) throws IOException;
|
||||
|
||||
/**
|
||||
* Closes the port and {@link UsbDeviceConnection}
|
||||
*
|
||||
* @throws IOException on error closing the port.
|
||||
*/
|
||||
public void close() throws IOException;
|
||||
|
||||
/**
|
||||
* Reads as many bytes as possible into the destination buffer.
|
||||
*
|
||||
* @param dest the destination byte buffer
|
||||
* @param timeout the timeout for reading in milliseconds, 0 is infinite
|
||||
* @return the actual number of bytes read
|
||||
* @throws IOException if an error occurred during reading
|
||||
*/
|
||||
public int read(final byte[] dest, final int timeout) throws IOException;
|
||||
|
||||
/**
|
||||
* Writes as many bytes as possible from the source buffer.
|
||||
*
|
||||
* @param src the source byte buffer
|
||||
* @param timeout the timeout for writing in milliseconds, 0 is infinite
|
||||
* @return the actual number of bytes written
|
||||
* @throws IOException if an error occurred during writing
|
||||
*/
|
||||
public int write(final byte[] src, final int timeout) 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
|
||||
* @throws UnsupportedOperationException if values are not supported by a specific device
|
||||
*/
|
||||
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
|
||||
* @throws IOException if an error occurred during reading
|
||||
* @throws UnsupportedOperationException if not supported
|
||||
*/
|
||||
public boolean getCD() throws IOException;
|
||||
|
||||
/**
|
||||
* Gets the CTS (Clear To Send) bit from the underlying UART.
|
||||
*
|
||||
* @return the current state
|
||||
* @throws IOException if an error occurred during reading
|
||||
* @throws UnsupportedOperationException if not supported
|
||||
*/
|
||||
public boolean getCTS() throws IOException;
|
||||
|
||||
/**
|
||||
* Gets the DSR (Data Set Ready) bit from the underlying UART.
|
||||
*
|
||||
* @return the current state
|
||||
* @throws IOException if an error occurred during reading
|
||||
* @throws UnsupportedOperationException if not supported
|
||||
*/
|
||||
public boolean getDSR() throws IOException;
|
||||
|
||||
/**
|
||||
* Gets the DTR (Data Terminal Ready) bit from the underlying UART.
|
||||
*
|
||||
* @return the current state
|
||||
* @throws IOException if an error occurred during reading
|
||||
* @throws UnsupportedOperationException if not supported
|
||||
*/
|
||||
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
|
||||
* @throws UnsupportedOperationException if not supported
|
||||
*/
|
||||
public void setDTR(boolean value) throws IOException;
|
||||
|
||||
/**
|
||||
* Gets the RI (Ring Indicator) bit from the underlying UART.
|
||||
*
|
||||
* @return the current state
|
||||
* @throws IOException if an error occurred during reading
|
||||
* @throws UnsupportedOperationException if not supported
|
||||
*/
|
||||
public boolean getRI() throws IOException;
|
||||
|
||||
/**
|
||||
* Gets the RTS (Request To Send) bit from the underlying UART.
|
||||
*
|
||||
* @return the current state
|
||||
* @throws IOException if an error occurred during reading
|
||||
* @throws UnsupportedOperationException if not supported
|
||||
*/
|
||||
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
|
||||
* @throws UnsupportedOperationException if not supported
|
||||
*/
|
||||
public void setRTS(boolean value) throws IOException;
|
||||
|
||||
/**
|
||||
* Gets all control line values from the underlying UART, if supported.
|
||||
* Requires less USB calls than calling getRTS() + ... + getRI() individually.
|
||||
*
|
||||
* @return EnumSet.contains(...) is {@code true} if set, else {@code false}
|
||||
* @throws IOException if an error occurred during reading
|
||||
*/
|
||||
public EnumSet<ControlLine> getControlLines() throws IOException;
|
||||
|
||||
/**
|
||||
* Gets all control line supported flags.
|
||||
*
|
||||
* @return EnumSet.contains(...) is {@code true} if supported, else {@code false}
|
||||
* @throws IOException if an error occurred during reading
|
||||
*/
|
||||
public EnumSet<ControlLine> getSupportedControlLines() throws IOException;
|
||||
|
||||
/**
|
||||
* Purge non-transmitted output data and / or non-read input data.
|
||||
*
|
||||
* @param purgeWriteBuffers {@code true} to discard non-transmitted output data
|
||||
* @param purgeReadBuffers {@code true} to discard non-read input data
|
||||
* @throws IOException if an error occurred during flush
|
||||
* @throws UnsupportedOperationException if not supported
|
||||
*/
|
||||
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException;
|
||||
|
||||
/**
|
||||
* send BREAK condition.
|
||||
*
|
||||
* @param value set/reset
|
||||
*/
|
||||
public void setBreak(boolean value) throws IOException;
|
||||
|
||||
/**
|
||||
* Returns the current state of the connection.
|
||||
*/
|
||||
public boolean isOpen();
|
||||
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
/* Copyright 2011-2013 Google Inc.
|
||||
* Copyright 2013 mike wakerly <opensource@hoho.com>
|
||||
*
|
||||
* Project home page: https://github.com/mik3y/usb-serial-for-android
|
||||
*/
|
||||
|
||||
package org.emulator.calculator.usbserial.driver;
|
||||
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.hardware.usb.UsbManager;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author mike wakerly (opensource@hoho.com)
|
||||
*/
|
||||
public class UsbSerialProber {
|
||||
|
||||
private final ProbeTable mProbeTable;
|
||||
|
||||
public UsbSerialProber(ProbeTable probeTable) {
|
||||
mProbeTable = probeTable;
|
||||
}
|
||||
|
||||
public static UsbSerialProber getDefaultProber() {
|
||||
return new UsbSerialProber(getDefaultProbeTable());
|
||||
}
|
||||
|
||||
public static ProbeTable getDefaultProbeTable() {
|
||||
final ProbeTable probeTable = new ProbeTable();
|
||||
probeTable.addDriver(CdcAcmSerialDriver.class);
|
||||
probeTable.addDriver(Cp21xxSerialDriver.class);
|
||||
probeTable.addDriver(FtdiSerialDriver.class);
|
||||
probeTable.addDriver(ProlificSerialDriver.class);
|
||||
probeTable.addDriver(Ch34xSerialDriver.class);
|
||||
return probeTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and builds all possible {@link UsbSerialDriver UsbSerialDrivers}
|
||||
* from the currently-attached {@link UsbDevice} hierarchy. This method does
|
||||
* not require permission from the Android USB system, since it does not
|
||||
* open any of the devices.
|
||||
*
|
||||
* @param usbManager
|
||||
* @return a list, possibly empty, of all compatible drivers
|
||||
*/
|
||||
public List<UsbSerialDriver> findAllDrivers(final UsbManager usbManager) {
|
||||
final List<UsbSerialDriver> result = new ArrayList<UsbSerialDriver>();
|
||||
|
||||
for (final UsbDevice usbDevice : usbManager.getDeviceList().values()) {
|
||||
final UsbSerialDriver driver = probeDevice(usbDevice);
|
||||
if (driver != null) {
|
||||
result.add(driver);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Probes a single device for a compatible driver.
|
||||
*
|
||||
* @param usbDevice the usb device to probe
|
||||
* @return a new {@link UsbSerialDriver} compatible with this device, or
|
||||
* {@code null} if none available.
|
||||
*/
|
||||
public UsbSerialDriver probeDevice(final UsbDevice usbDevice) {
|
||||
final int vendorId = usbDevice.getVendorId();
|
||||
final int productId = usbDevice.getProductId();
|
||||
|
||||
final Class<? extends UsbSerialDriver> driverClass =
|
||||
mProbeTable.findDriver(vendorId, productId);
|
||||
if (driverClass != null) {
|
||||
final UsbSerialDriver driver;
|
||||
try {
|
||||
final Constructor<? extends UsbSerialDriver> ctor =
|
||||
driverClass.getConstructor(UsbDevice.class);
|
||||
driver = ctor.newInstance(usbDevice);
|
||||
} catch (NoSuchMethodException | IllegalArgumentException | InstantiationException |
|
||||
IllegalAccessException | InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return driver;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,239 @@
|
|||
/* Copyright 2011-2013 Google Inc.
|
||||
* Copyright 2013 mike wakerly <opensource@hoho.com>
|
||||
*
|
||||
* Project home page: https://github.com/mik3y/usb-serial-for-android
|
||||
*/
|
||||
|
||||
package org.emulator.calculator.usbserial.util;
|
||||
|
||||
import android.os.Process;
|
||||
import android.util.Log;
|
||||
|
||||
import org.emulator.calculator.usbserial.driver.UsbSerialPort;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Utility class which services a {@link UsbSerialPort} in its {@link #run()} method.
|
||||
*
|
||||
* @author mike wakerly (opensource@hoho.com)
|
||||
*/
|
||||
public class SerialInputOutputManager implements Runnable {
|
||||
|
||||
private static final String TAG = SerialInputOutputManager.class.getSimpleName();
|
||||
private static final boolean DEBUG = false;
|
||||
private static final int BUFSIZ = 4096;
|
||||
|
||||
/**
|
||||
* default read timeout is infinite, to avoid data loss with bulkTransfer API
|
||||
*/
|
||||
private int mReadTimeout = 0;
|
||||
private int mWriteTimeout = 0;
|
||||
|
||||
private final Object mReadBufferLock = new Object();
|
||||
private final Object mWriteBufferLock = new Object();
|
||||
|
||||
private ByteBuffer mReadBuffer = ByteBuffer.allocate(BUFSIZ);
|
||||
private ByteBuffer mWriteBuffer = ByteBuffer.allocate(BUFSIZ);
|
||||
|
||||
public enum State {
|
||||
STOPPED,
|
||||
RUNNING,
|
||||
STOPPING
|
||||
}
|
||||
|
||||
private int mThreadPriority = Process.THREAD_PRIORITY_URGENT_AUDIO;
|
||||
private State mState = State.STOPPED; // Synchronized by 'this'
|
||||
private Listener mListener; // Synchronized by 'this'
|
||||
private final UsbSerialPort mSerialPort;
|
||||
|
||||
public interface Listener {
|
||||
/**
|
||||
* Called when new incoming data is available.
|
||||
*/
|
||||
public void onNewData(byte[] data);
|
||||
|
||||
/**
|
||||
* Called when {@link SerialInputOutputManager#run()} aborts due to an error.
|
||||
*/
|
||||
public void onRunError(Exception e);
|
||||
}
|
||||
|
||||
public SerialInputOutputManager(UsbSerialPort serialPort) {
|
||||
mSerialPort = serialPort;
|
||||
}
|
||||
|
||||
public SerialInputOutputManager(UsbSerialPort serialPort, Listener listener) {
|
||||
mSerialPort = serialPort;
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public synchronized void setListener(Listener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public synchronized Listener getListener() {
|
||||
return mListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* setThreadPriority. By default use higher priority than UI thread to prevent data loss
|
||||
*
|
||||
* @param threadPriority see {@link Process#setThreadPriority(int)}
|
||||
* */
|
||||
public void setThreadPriority(int threadPriority) {
|
||||
if (mState != State.STOPPED)
|
||||
throw new IllegalStateException("threadPriority only configurable before SerialInputOutputManager is started");
|
||||
mThreadPriority = threadPriority;
|
||||
}
|
||||
|
||||
/**
|
||||
* read/write timeout
|
||||
*/
|
||||
public void setReadTimeout(int timeout) {
|
||||
// when set if already running, read already blocks and the new value will not become effective now
|
||||
if(mReadTimeout == 0 && timeout != 0 && mState != State.STOPPED)
|
||||
throw new IllegalStateException("readTimeout only configurable before SerialInputOutputManager is started");
|
||||
mReadTimeout = timeout;
|
||||
}
|
||||
|
||||
public int getReadTimeout() {
|
||||
return mReadTimeout;
|
||||
}
|
||||
|
||||
public void setWriteTimeout(int timeout) {
|
||||
mWriteTimeout = timeout;
|
||||
}
|
||||
|
||||
public int getWriteTimeout() {
|
||||
return mWriteTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* read/write buffer size
|
||||
*/
|
||||
public void setReadBufferSize(int bufferSize) {
|
||||
if (getReadBufferSize() == bufferSize)
|
||||
return;
|
||||
synchronized (mReadBufferLock) {
|
||||
mReadBuffer = ByteBuffer.allocate(bufferSize);
|
||||
}
|
||||
}
|
||||
|
||||
public int getReadBufferSize() {
|
||||
return mReadBuffer.capacity();
|
||||
}
|
||||
|
||||
public void setWriteBufferSize(int bufferSize) {
|
||||
if(getWriteBufferSize() == bufferSize)
|
||||
return;
|
||||
synchronized (mWriteBufferLock) {
|
||||
ByteBuffer newWriteBuffer = ByteBuffer.allocate(bufferSize);
|
||||
if(mWriteBuffer.position() > 0)
|
||||
newWriteBuffer.put(mWriteBuffer.array(), 0, mWriteBuffer.position());
|
||||
mWriteBuffer = newWriteBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
public int getWriteBufferSize() {
|
||||
return mWriteBuffer.capacity();
|
||||
}
|
||||
|
||||
/*
|
||||
* when writeAsync is used, it is recommended to use readTimeout != 0,
|
||||
* else the write will be delayed until read data is available
|
||||
*/
|
||||
public void writeAsync(byte[] data) {
|
||||
synchronized (mWriteBufferLock) {
|
||||
mWriteBuffer.put(data);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void stop() {
|
||||
if (getState() == State.RUNNING) {
|
||||
Log.i(TAG, "Stop requested");
|
||||
mState = State.STOPPING;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized State getState() {
|
||||
return mState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Continuously services the read and write buffers until {@link #stop()} is
|
||||
* called, or until a driver exception is raised.
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
if(mThreadPriority != Process.THREAD_PRIORITY_DEFAULT)
|
||||
setThreadPriority(mThreadPriority);
|
||||
|
||||
synchronized (this) {
|
||||
if (getState() != State.STOPPED) {
|
||||
throw new IllegalStateException("Already running");
|
||||
}
|
||||
mState = State.RUNNING;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Running ...");
|
||||
try {
|
||||
while (true) {
|
||||
if (getState() != State.RUNNING) {
|
||||
Log.i(TAG, "Stopping mState=" + getState());
|
||||
break;
|
||||
}
|
||||
step();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Run ending due to exception: " + e.getMessage(), e);
|
||||
final Listener listener = getListener();
|
||||
if (listener != null) {
|
||||
listener.onRunError(e);
|
||||
}
|
||||
} finally {
|
||||
synchronized (this) {
|
||||
mState = State.STOPPED;
|
||||
Log.i(TAG, "Stopped");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void step() throws IOException {
|
||||
// Handle incoming data.
|
||||
byte[] buffer = null;
|
||||
synchronized (mReadBufferLock) {
|
||||
buffer = mReadBuffer.array();
|
||||
}
|
||||
int len = mSerialPort.read(buffer, mReadTimeout);
|
||||
if (len > 0) {
|
||||
if (DEBUG) Log.d(TAG, "Read data len=" + len);
|
||||
final Listener listener = getListener();
|
||||
if (listener != null) {
|
||||
final byte[] data = new byte[len];
|
||||
System.arraycopy(buffer, 0, data, 0, len);
|
||||
listener.onNewData(data);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle outgoing data.
|
||||
buffer = null;
|
||||
synchronized (mWriteBufferLock) {
|
||||
len = mWriteBuffer.position();
|
||||
if (len > 0) {
|
||||
buffer = new byte[len];
|
||||
mWriteBuffer.rewind();
|
||||
mWriteBuffer.get(buffer, 0, len);
|
||||
mWriteBuffer.clear();
|
||||
}
|
||||
}
|
||||
if (buffer != null) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Writing data len=" + len);
|
||||
}
|
||||
mSerialPort.write(buffer, mWriteTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -65,8 +65,10 @@ import org.emulator.calculator.MainScreenView;
|
|||
import org.emulator.calculator.NativeLib;
|
||||
import org.emulator.calculator.PrinterSimulator;
|
||||
import org.emulator.calculator.PrinterSimulatorFragment;
|
||||
import org.emulator.calculator.Serial;
|
||||
import org.emulator.calculator.Settings;
|
||||
import org.emulator.calculator.Utils;
|
||||
import org.emulator.calculator.usbserial.DevicesFragment;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
|
@ -2031,6 +2033,86 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
|
|||
changeHeaderIcon();
|
||||
}
|
||||
|
||||
private int serialIndex = 1;
|
||||
private HashMap<Integer, Serial> serialsById = new HashMap<>();
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
int openSerialPort(String serialPort) {
|
||||
Integer serialPortId = serialIndex;
|
||||
Serial serial = new Serial(this, serialPortId);
|
||||
if(serial.connect(serialPort)) {
|
||||
serialsById.put(serialPortId, serial);
|
||||
serialIndex++;
|
||||
return serialPortId;
|
||||
} else {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(MainActivity.this, Utils.resId(MainActivity.this, "string", serial.getConnectionStatus()), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
int closeSerialPort(int serialPortId) {
|
||||
Integer serialPortIdInt = serialPortId;
|
||||
Serial serial = serialsById.get(serialPortIdInt);
|
||||
if(serial != null) {
|
||||
serialsById.remove(serialPortIdInt);
|
||||
serial.disconnect();
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
int setSerialPortParameters(int serialPortId, int baudRate) {
|
||||
Integer serialPortIdInt = serialPortId;
|
||||
Serial serial = serialsById.get(serialPortIdInt);
|
||||
if(serial != null && serial.setParameters(baudRate))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
byte[] readSerialPort(int serialPortId, int nNumberOfBytesToRead) {
|
||||
Integer serialPortIdInt = serialPortId;
|
||||
Serial serial = serialsById.get(serialPortIdInt);
|
||||
if(serial != null)
|
||||
return serial.receive(nNumberOfBytesToRead);
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
int writeSerialPort(int serialPortId, byte[] buffer) {
|
||||
Integer serialPortIdInt = serialPortId;
|
||||
Serial serial = serialsById.get(serialPortIdInt);
|
||||
if(serial != null)
|
||||
return serial.send(buffer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
int serialPortSetBreak(int serialPortId) {
|
||||
Integer serialPortIdInt = serialPortId;
|
||||
Serial serial = serialsById.get(serialPortIdInt);
|
||||
if(serial != null)
|
||||
return serial.setBreak();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
int serialPortClearBreak(int serialPortId) {
|
||||
Integer serialPortIdInt = serialPortId;
|
||||
Serial serial = serialsById.get(serialPortIdInt);
|
||||
if(serial != null)
|
||||
return serial.clearBreak();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
private Bitmap getApplicationIconBitmap() {
|
||||
Drawable drawable = getApplicationInfo().loadIcon(getPackageManager());
|
||||
if (drawable instanceof BitmapDrawable) {
|
||||
|
@ -2062,7 +2144,8 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
|
|||
"settings_background_kml_color", "settings_background_fallback_color",
|
||||
"settings_printer_model", "settings_macro",
|
||||
"settings_kml", "settings_port1", "settings_port2",
|
||||
"settings_flash_port2" };
|
||||
"settings_flash_port2",
|
||||
"settings_serial_ports_wire", "settings_serial_ports_ir" };
|
||||
for (String settingKey : settingKeys)
|
||||
updateFromPreferences(settingKey, false);
|
||||
} else {
|
||||
|
@ -2184,6 +2267,14 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
|
|||
if(settingsFlashPort2Url != null)
|
||||
loadFlashROM(Uri.parse(settingsFlashPort2Url), null);
|
||||
break;
|
||||
case "settings_serial_ports_wire":
|
||||
NativeLib.setConfiguration("settings_serial_ports_wire", isDynamicValue, 0, 0,
|
||||
DevicesFragment.SerialConnectParameters.fromSettingsString(settings.getString("settings_serial_ports_wire", "")).toWin32String());
|
||||
break;
|
||||
case "settings_serial_ports_ir":
|
||||
NativeLib.setConfiguration("settings_serial_ports_ir", isDynamicValue, 0, 0,
|
||||
DevicesFragment.SerialConnectParameters.fromSettingsString(settings.getString("settings_serial_ports_ir", "")).toWin32String());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,12 +19,7 @@ import android.app.Dialog;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.BlendMode;
|
||||
import android.graphics.BlendModeColorFilter;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Vibrator;
|
||||
import android.text.InputType;
|
||||
|
@ -51,6 +46,8 @@ import org.emulator.calculator.EmuApplication;
|
|||
import org.emulator.calculator.NativeLib;
|
||||
import org.emulator.calculator.Settings;
|
||||
import org.emulator.calculator.Utils;
|
||||
import org.emulator.calculator.usbserial.DevicesDialogFragment;
|
||||
import org.emulator.calculator.usbserial.DevicesFragment;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
|
@ -372,6 +369,41 @@ public class SettingsFragment extends AppCompatDialogFragment {
|
|||
} else
|
||||
preferenceFlashPort2.setEnabled(false);
|
||||
}
|
||||
|
||||
|
||||
// Serial ports
|
||||
|
||||
Preference preferenceSerialPortsWire = findPreference("settings_serial_ports_wire");
|
||||
if(preferenceSerialPortsWire != null) {
|
||||
String serialPortsWire = settings.getString(preferenceSerialPortsWire.getKey(), "");
|
||||
serialPortsWire = DevicesFragment.SerialConnectParameters.fromSettingsString(serialPortsWire).toDisplayString(requireContext());
|
||||
preferenceSerialPortsWire.setSummary(serialPortsWire);
|
||||
preferenceSerialPortsWire.setOnPreferenceClickListener(preference -> {
|
||||
DevicesDialogFragment devicesDialogFragment = new DevicesDialogFragment();
|
||||
devicesDialogFragment.setOnSerialDeviceClickedListener((serialConnectParameters) -> {
|
||||
preferenceSerialPortsWire.setSummary(serialConnectParameters.toDisplayString(requireContext()));
|
||||
settings.putString(preferenceSerialPortsWire.getKey(), serialConnectParameters.toSettingsString());
|
||||
});
|
||||
devicesDialogFragment.show(getChildFragmentManager(), "DevicesDialogFragment");
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
Preference preferenceSerialPortsIr = findPreference("settings_serial_ports_ir");
|
||||
if(preferenceSerialPortsIr != null) {
|
||||
String serialPortsIr = settings.getString(preferenceSerialPortsIr.getKey(), "");
|
||||
serialPortsIr = DevicesFragment.SerialConnectParameters.fromSettingsString(serialPortsIr).toDisplayString(requireContext());
|
||||
preferenceSerialPortsIr.setSummary(serialPortsIr);
|
||||
preferenceSerialPortsIr.setOnPreferenceClickListener(preference -> {
|
||||
DevicesDialogFragment devicesDialogFragment = new DevicesDialogFragment();
|
||||
devicesDialogFragment.setOnSerialDeviceClickedListener((serialConnectParameters) -> {
|
||||
preferenceSerialPortsIr.setSummary(serialConnectParameters.toDisplayString(requireContext()));
|
||||
settings.putString(preferenceSerialPortsIr.getKey(), serialConnectParameters.toSettingsString());
|
||||
});
|
||||
devicesDialogFragment.show(getChildFragmentManager(), "DevicesDialogFragment");
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void updatePort2LoadFilename(String port2Filename) {
|
||||
|
|
17
app/src/main/res/layout/device_list_header.xml
Normal file
17
app/src/main/res/layout/device_list_header.xml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:attr/listDivider"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/serial_device_list_header"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
|
||||
</LinearLayout>
|
26
app/src/main/res/layout/device_list_item.xml
Normal file
26
app/src/main/res/layout/device_list_item.xml
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text1"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text2"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
|
||||
|
||||
</LinearLayout>
|
35
app/src/main/res/layout/fragment_serial.xml
Normal file
35
app/src/main/res/layout/fragment_serial.xml
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/frameLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:navigationIcon="?android:attr/homeAsUpIndicator" />
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/serialport_devices_fragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/toolbar" />
|
||||
<!-- android:name="com.example.serialport.ui.home.DevicesFragment"-->
|
||||
|
||||
<!-- <androidx.constraintlayout.widget.ConstraintLayout-->
|
||||
<!-- android:id="@+id/serial_fragment_container"-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="0dp"-->
|
||||
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
|
||||
<!-- app:layout_constraintTop_toBottomOf="@+id/my_toolbar">-->
|
||||
|
||||
<!-- </androidx.constraintlayout.widget.ConstraintLayout>-->
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -127,6 +127,21 @@
|
|||
<string name="message_change_overlapping_lcd_mode_to_manual">The overlapping LCD mode has been changed to "Manual".</string>
|
||||
<string name="message_change_overlapping_lcd_mode_to_auto">The overlapping LCD mode has been changed to "Auto".</string>
|
||||
|
||||
<string name="serial_fragment_title">USB Devices</string>
|
||||
<string name="serial_device_list_header">USB Devices</string>
|
||||
<string name="serial_no_device">no USB devices found</string>
|
||||
<string name="serial_ports_device">%s (%04X:%04X) [%d,%d]</string>
|
||||
<string name="serial_ports_device_no_driver">No driver</string>
|
||||
<string name="serial_ports_device_item_title">%s, Port %d</string>
|
||||
<string name="serial_ports_device_item_description">Vendor %04X, Product %04X</string>
|
||||
<string name="serial_connection_failed_device_not_found">Failed to connect the USB serial device: device not found (check the settings).</string>
|
||||
<string name="serial_connection_failed_no_driver_for_device">Failed to connect the USB serial device: no driver for device (check the settings).</string>
|
||||
<string name="serial_connection_failed_not_enough_ports_at_device">Failed to connect the USB serial device: not enough ports at device (check the settings).</string>
|
||||
<string name="serial_connection_failed_user_has_not_given_permission">Failed to connect the USB serial device: user has not given permission.</string>
|
||||
<string name="serial_connection_failed_for_unknown_reason">Failed to connect the USB serial device: for unknown reason.</string>
|
||||
<string name="serial_connection_failed_permission_denied">Failed to connect the USB serial device: permission denied.</string>
|
||||
<string name="serial_connection_failed_open_device_failed">Failed to connect the USB serial device: open device failed.</string>
|
||||
<string name="serial_connection_failed_open_failed">Failed to connect the USB serial device: open failed.</string>
|
||||
|
||||
<string name="settings_category_general_title">General</string>
|
||||
<string name="settings_realspeed_title">Authentic Calculator Speed</string>
|
||||
|
@ -200,4 +215,8 @@
|
|||
<string name="settings_macro_real_speed_title">Use Real Replay Speed</string>
|
||||
<string name="settings_macro_manual_speed_title">Manual Replay Speed</string>
|
||||
|
||||
<string name="settings_category_serial_ports_title">Serial Ports</string>
|
||||
<string name="settings_serial_ports_wire_title">Wire</string>
|
||||
<string name="settings_serial_ports_ir_title">Infrared (2400 baud)</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -176,4 +176,17 @@
|
|||
/>
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/settings_category_serial_ports_title">
|
||||
<Preference
|
||||
android:key="settings_serial_ports_wire"
|
||||
android:title="@string/settings_serial_ports_wire_title"
|
||||
android:summary=""
|
||||
/>
|
||||
<Preference
|
||||
android:key="settings_serial_ports_ir"
|
||||
android:title="@string/settings_serial_ports_ir_title"
|
||||
android:summary=""
|
||||
/>
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
||||
|
|
Loading…
Reference in a new issue