Skip to content

Commit b77ae58

Browse files
authored
Prepare Release 0.5.0
Prepare Release 0.5.0
2 parents 5d80921 + dc4797d commit b77ae58

19 files changed

+537
-142
lines changed

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,18 @@
55

66
This is being done as a hobby, and for experimenting, so probably there might be some flaws; As an example, the vendor ID of Arduino is hardcoded to only work with Arduino devices, but this is my use case and please feel free to change it to match your needs.
77

8+
## Terminal
9+
A Simple terminal page which does what it is supposed to do interacting with an Arduino manually through the USB cable.
10+
11+
## Joystick
12+
The Joystick is removed for the first release.
13+
14+
## Tests
15+
Under Construction
16+
817
### Knows Issues
9-
On Android 5.1.1, the Arduino serial output cannot be shown. (It is said that an Android internal bug is the issue!)
18+
_On Android 5.1.1, the Arduino serial output cannot be shown. (It is said that an Android internal bug is the issue!)_ This was hopefully solved using LiveData
19+
The Arduino output characters might be shown a bit weird in the app while skipping some characters when the message is too long. This will be fixed as I figure out the reason! Any suggestions will be appreciated. :)
1020

1121
Suggestions and PRs are welcome! :)
1222

app/build.gradle

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
apply plugin: 'com.android.application'
22
apply plugin: 'kotlin-android'
33
apply plugin: 'kotlin-android-extensions'
4+
apply plugin: 'koin'
45

56
android {
67
compileSdkVersion 29
@@ -13,10 +14,10 @@ android {
1314

1415
defaultConfig {
1516
applicationId "org.kabiri.android.usbterminal"
16-
minSdkVersion 22
17+
minSdkVersion 23
1718
targetSdkVersion 29
18-
versionCode 1
19-
versionName "1.0"
19+
versionCode 5
20+
versionName "0.5.0"
2021

2122
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
2223
}
@@ -31,11 +32,23 @@ android {
3132
}
3233

3334
dependencies {
35+
3436
implementation fileTree(dir: 'libs', include: ['*.jar'])
3537
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
3638
implementation 'androidx.appcompat:appcompat:1.1.0'
3739
implementation 'androidx.core:core-ktx:1.2.0'
3840
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
41+
42+
// Koin for Android - ViewModel features
43+
implementation "org.koin:koin-android-viewmodel:$koin_version"
44+
45+
// Coroutines
46+
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
47+
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
48+
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0'
49+
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0'
50+
// testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.0-RC2'
51+
3952
testImplementation 'junit:junit:4.12'
4053
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
4154
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

app/src/main/AndroidManifest.xml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
33
package="org.kabiri.android.usbterminal">
44

5+
<uses-feature android:name="android.hardware.usb.host" />
6+
57
<application
8+
android:name=".MainApplication"
69
android:allowBackup="true"
10+
android:fullBackupContent="true"
711
android:icon="@mipmap/ic_launcher"
812
android:label="@string/app_name"
913
android:roundIcon="@mipmap/ic_launcher_round"
@@ -19,11 +23,10 @@
1923
<action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
2024
</intent-filter>
2125

22-
<meta-data android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
23-
android:resource="@xml/accessory_filter"/>
26+
<meta-data
27+
android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
28+
android:resource="@xml/accessory_filter" />
2429
</activity>
2530
</application>
2631

27-
<uses-feature android:name="android.hardware.usb.host" />
28-
2932
</manifest>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package org.kabiri.android.usbterminal
2+
3+
/**
4+
* Created by Ali Kabiri on 13.04.20.
5+
*/
6+
class Constants {
7+
companion object {
8+
const val ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION"
9+
}
10+
}

app/src/main/java/org/kabiri/android/usbterminal/MainActivity.kt

Lines changed: 26 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -1,164 +1,62 @@
11
package org.kabiri.android.usbterminal
22

3-
import android.app.PendingIntent
4-
import android.content.BroadcastReceiver
5-
import android.content.Context
63
import android.content.Intent
7-
import android.content.IntentFilter
8-
import android.hardware.usb.UsbDevice
9-
import android.hardware.usb.UsbDeviceConnection
10-
import android.hardware.usb.UsbManager
11-
import android.os.Build
124
import android.os.Bundle
5+
import android.text.SpannableString
6+
import android.text.method.ScrollingMovementMethod
137
import android.util.Log
148
import android.view.Menu
159
import android.view.MenuInflater
1610
import android.view.MenuItem
17-
import android.widget.Toast
1811
import androidx.appcompat.app.AppCompatActivity
19-
import com.felhr.usbserial.UsbSerialDevice
20-
import com.felhr.usbserial.UsbSerialInterface
12+
import androidx.lifecycle.Observer
2113
import kotlinx.android.synthetic.main.activity_main.*
22-
import java.io.UnsupportedEncodingException
23-
import java.nio.charset.Charset
14+
import org.kabiri.android.usbterminal.viewmodel.MainActivityViewModel
15+
import org.koin.android.viewmodel.ext.android.viewModel
2416

2517
class MainActivity : AppCompatActivity() {
2618

2719
companion object {
2820
private const val TAG = "MainActivity"
29-
private const val ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION"
3021
}
3122

32-
private lateinit var usbManager: UsbManager
33-
private lateinit var connection: UsbDeviceConnection
34-
private lateinit var serialPort: UsbSerialDevice
35-
private lateinit var usbReceiver: BroadcastReceiver
23+
private val viewModel: MainActivityViewModel by viewModel()
3624

3725
override fun onCreate(savedInstanceState: Bundle?) {
3826
super.onCreate(savedInstanceState)
3927
setContentView(R.layout.activity_main)
4028

41-
usbManager = getSystemService(Context.USB_SERVICE) as UsbManager
42-
usbReceiver = object : BroadcastReceiver() {
29+
// make the text view scrollable:
30+
tvOutput.movementMethod = ScrollingMovementMethod()
4331

44-
override fun onReceive(context: Context?, intent: Intent?) {
45-
when (intent?.action) {
46-
ACTION_USB_PERMISSION -> {
47-
synchronized(this) {
48-
val device: UsbDevice? =
49-
intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
32+
// open the device and port when the permission is granted by user.
33+
viewModel.getGrantedDevice().observe(this, Observer { device ->
34+
viewModel.openDeviceAndPort(device)
35+
})
5036

51-
if (intent.getBooleanExtra(
52-
UsbManager.EXTRA_PERMISSION_GRANTED,
53-
false
54-
)
55-
) {
56-
tvOutput.append("\nPermission granted for ${device?.manufacturerName}")
57-
device?.apply {
58-
// setup the device communication.
59-
connection = usbManager.openDevice(device)
60-
serialPort = UsbSerialDevice
61-
.createUsbSerialDevice(device, connection)
62-
if (::serialPort.isInitialized) serialPort.let {
63-
if (it.open()) {
64-
// set connection params.
65-
it.setBaudRate(9600)
66-
it.setDataBits(UsbSerialInterface.DATA_BITS_8)
67-
it.setStopBits(UsbSerialInterface.STOP_BITS_1)
68-
it.setParity(UsbSerialInterface.PARITY_NONE)
69-
it.setFlowControl(UsbSerialInterface.FLOW_CONTROL_OFF)
70-
it.read { message ->
71-
// check if the Android version is not 5.1.1 Lollipop
72-
// before printing the message into output.
73-
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP_MR1) {
74-
Log.e(
75-
TAG,
76-
"Lollipop 5.1.1 is not supported to show the serial messages from the Arduino."
77-
)
78-
} else {
79-
message?.let {
80-
try {
81-
val encoded =
82-
String(
83-
message,
84-
Charset.defaultCharset()
85-
)
86-
tvOutput.append(encoded)
87-
} catch (e: UnsupportedEncodingException) {
88-
e.printStackTrace()
89-
tvOutput
90-
.append("\n${e.localizedMessage}")
91-
} catch (e: Exception) {
92-
Toast.makeText(
93-
this@MainActivity,
94-
e.localizedMessage,
95-
Toast.LENGTH_SHORT
96-
).show()
97-
}
98-
}
99-
}
100-
}
101-
tvOutput.append("\nSerial Connection Opened")
102-
} else {
103-
tvOutput.append("\nPort not opened")
104-
}
105-
} else {
106-
tvOutput.append("\nSerial Port was null")
107-
}
108-
109-
}
110-
} else {
111-
tvOutput.append("\npermission denied for device $device")
112-
}
113-
}
114-
}
115-
UsbManager.ACTION_USB_DEVICE_ATTACHED -> tvOutput.append("\nDevice attached")
116-
UsbManager.ACTION_USB_DEVICE_DETACHED -> tvOutput.append("\nDevice detached")
117-
}
118-
}
119-
}
37+
viewModel.getLiveOutput().observe(this, Observer {
38+
val spannable = SpannableString(it.text)
39+
spannable.setSpan(
40+
it.getAppearance(this),
41+
0,
42+
it.text.length,
43+
SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE)
44+
tvOutput.append(it.text)
45+
})
12046

47+
// send the command to device when the button is clicked.
12148
btEnter.setOnClickListener {
12249
val input = etInput.text.toString()
123-
try {
124-
if (::serialPort.isInitialized && input.isNotBlank()) {
125-
serialPort.write(input.toByteArray())
126-
tvOutput.append("\n") // this is because the answer might be sent in more than one part.
127-
etInput.setText("") // clear the terminal input.
128-
} else tvOutput.append("\nSerialPortNotOpened")
129-
} catch (e: Exception) {
130-
tvOutput.append("\n${e.localizedMessage}")
131-
}
50+
if (viewModel.serialWrite(input))
51+
etInput.setText("") // clear the terminal input.
52+
else Log.e(TAG, "The message was not sent to Arduino")
13253
}
133-
13454
}
13555

13656
override fun onOptionsItemSelected(item: MenuItem): Boolean {
13757
return when (item.itemId) {
13858
R.id.actionConnect -> {
139-
140-
val usbDevices = usbManager.deviceList
141-
if (usbDevices.isNotEmpty()) {
142-
for (device in usbDevices) {
143-
val deviceVID = device.value.vendorId
144-
if (deviceVID == 0x2341) { // Arduino vendor ID
145-
val permissionIntent = PendingIntent.getBroadcast(
146-
this,
147-
0,
148-
Intent(ACTION_USB_PERMISSION),
149-
0
150-
)
151-
val filter = IntentFilter(ACTION_USB_PERMISSION)
152-
registerReceiver(usbReceiver, filter) // register the broadcast receiver
153-
usbManager.requestPermission(device.value, permissionIntent)
154-
} else {
155-
tvOutput.append("\nArduino Device not found")
156-
connection.close()
157-
}
158-
}
159-
} else {
160-
tvOutput.append("\nNo USB devices are attached")
161-
}
59+
viewModel.askForConnectionPermission()
16260
true
16361
}
16462
else -> false
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.kabiri.android.usbterminal
2+
3+
import android.app.Application
4+
import org.kabiri.android.usbterminal.koin.appModule
5+
import org.koin.android.ext.koin.androidContext
6+
import org.koin.android.ext.koin.androidLogger
7+
import org.koin.core.context.startKoin
8+
import org.koin.core.logger.Level
9+
10+
/**
11+
* Created by Ali Kabiri on 13.04.20.
12+
*/
13+
class MainApplication: Application() {
14+
15+
override fun onCreate() {
16+
super.onCreate()
17+
18+
// start Koin context.
19+
startKoin{
20+
androidContext(this@MainApplication)
21+
androidLogger(Level.DEBUG)
22+
modules(appModule)
23+
}
24+
}
25+
}

0 commit comments

Comments
 (0)