Commit 376a76ae authored by Anon's avatar Anon

Merge branch 'single-connection' into 'master'

Share one connection

See merge request card10/companion-app-android!19
parents 3624ec85 9d7e21ee
......@@ -37,6 +37,10 @@ val SINGLE_LED_CHARACTERISTIC_UUID = UUID.fromString("42230211-2342-2342-2342-23
val LIGHT_SENSOR_CHARACTERISTIC_UUID = UUID.fromString("422302f0-2342-2342-2342-234223422342")
val TIME_CHARACTERISTIC_UUID = UUID.fromString("42230201-2342-2342-2342-234223422342")
val FILE_SERVICE_UUID = UUID.fromString("42230100-2342-2342-2342-234223422342")
val FILE_TX_UUID = UUID.fromString("42230101-2342-2342-2342-234223422342")
val FILE_RX_UUID = UUID.fromString("42230102-2342-2342-2342-234223422342")
const val UPDATE_CLOCK_FREQUENCY_MINS = 5
const val HATCHERY_BASE_URL = "https://badge.team"
......@@ -27,6 +27,7 @@ import android.content.Context
import android.util.Log
import de.ccc.events.badge.card10.CARD10_BLUETOOTH_MAC_PREFIX
import de.ccc.events.badge.card10.CARD10_SERVICE_UUID
import de.ccc.events.badge.card10.FILE_SERVICE_UUID
import de.ccc.events.badge.card10.R
import de.ccc.events.badge.card10.filetransfer.LowEffortService
import de.ccc.events.badge.card10.time.Card10Service
......@@ -47,8 +48,6 @@ object ConnectionService {
private var connectionState = BluetoothGatt.STATE_DISCONNECTED
private var gattListeners = mutableMapOf<String, GattListener>()
private val fileServiceUuid = UUID.fromString("42230100-2342-2342-2342-234223422342")
val deviceName: String?
get() = device?.name
......@@ -99,7 +98,7 @@ object ConnectionService {
for (service in gatt.services) {
Log.d(TAG, "Found service: ${service.uuid}")
if (service.uuid == fileServiceUuid) {
if (service.uuid == FILE_SERVICE_UUID) {
leService = LowEffortService(service)
} else if (service.uuid == CARD10_SERVICE_UUID) {
card10Service = Card10Service(service)
......
......@@ -57,8 +57,7 @@ class BatchTransferFragment : Fragment(), FileTransferListener, GattListener {
inflater.inflate(R.layout.batch_transfer_fragment, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
label_status.text = getString(R.string.batch_transfer_label_initializing)
progress.max = 5
progress.max = queue.size
button_cancel.setOnClickListener {
isCancelled = true
......@@ -72,13 +71,7 @@ class BatchTransferFragment : Fragment(), FileTransferListener, GattListener {
.commit()
}
initConnection()
}
private fun initConnection() {
val ctx = context ?: throw IllegalStateException()
ConnectionService.addGattListener("batchfiletransfer", this)
ConnectionService.connect(ctx)
startTransfer()
}
private fun startTransfer() {
......@@ -123,10 +116,6 @@ class BatchTransferFragment : Fragment(), FileTransferListener, GattListener {
}
}
override fun onConnectionReady() {
startTransfer()
}
override fun onError() {
activity?.runOnUiThread {
label_status.text = getString(R.string.batch_transfer_label_error)
......
......@@ -80,19 +80,12 @@ class FileTransferFragment : Fragment(), GattListener, FileTransferListener{
buttonStartStop = view.findViewById(R.id.button_start_stop_transfer)
try {
initConnection()
toggleControls()
} catch (e: ConnectionException) {
showError(e.message)
} catch (e: Exception) {
showError(getString(R.string.connection_error_generic))
}
}
private fun initConnection() {
val ctx = context ?: throw IllegalStateException()
ConnectionService.connect(ctx)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
......
......@@ -26,6 +26,8 @@ import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattService
import android.util.Log
import de.ccc.events.badge.card10.FILE_RX_UUID
import de.ccc.events.badge.card10.FILE_TX_UUID
import de.ccc.events.badge.card10.common.ConnectionService
import de.ccc.events.badge.card10.common.GattListener
import de.ccc.events.badge.card10.filetransfer.protocol.Packet
......@@ -39,15 +41,12 @@ class LowEffortService(
private val centralTx: BluetoothGattCharacteristic
private val centralRx: BluetoothGattCharacteristic
private val centralTxCharacteristicUuid = UUID.fromString("42230101-2342-2342-2342-234223422342")
private val centralRxCharacteristicUuid = UUID.fromString("42230102-2342-2342-2342-234223422342")
private var notifyEnabled = false
private var listener: OnPacketReceivedListener? = null
init {
val tx = service.getCharacteristic(centralTxCharacteristicUuid)
val rx = service.getCharacteristic(centralRxCharacteristicUuid)
val tx = service.getCharacteristic(FILE_TX_UUID)
val rx = service.getCharacteristic(FILE_RX_UUID)
if (tx == null || rx == null) {
throw IllegalStateException()
......
......@@ -28,12 +28,14 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import de.ccc.events.badge.card10.CARD10_BLUETOOTH_MAC_PREFIX
import de.ccc.events.badge.card10.R
import de.ccc.events.badge.card10.common.ConnectionService
import de.ccc.events.badge.card10.common.GattListener
import de.ccc.events.badge.card10.filetransfer.FileTransferFragment
import de.ccc.events.badge.card10.hatchery.AppListFragment
import de.ccc.events.badge.card10.mood.MoodFragment
......@@ -41,8 +43,10 @@ import de.ccc.events.badge.card10.scanner.ScannerFragment
import de.ccc.events.badge.card10.sparkle.BeautifulFragment
import de.ccc.events.badge.card10.time.TimeUpdateDialog
import kotlinx.android.synthetic.main.main_fragment.*
import java.lang.IllegalStateException
import java.sql.Connection
class MainFragment : Fragment() {
class MainFragment : Fragment(), GattListener {
private val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?) =
......@@ -62,16 +66,18 @@ class MainFragment : Fragment() {
dialogFragment.dismiss()
}
ConnectionService.addGattListener("main", this)
val bondedCard10s =
bluetoothAdapter.bondedDevices.filter { it.address.startsWith(CARD10_BLUETOOTH_MAC_PREFIX, true) }
if (bondedCard10s.isNotEmpty()) {
val device = bondedCard10s[0]
label_status.text = getString(R.string.main_label_paired, device.name, device.address)
showConnectedView(view)
val ctx = activity ?: throw IllegalStateException()
ConnectionService.connect(ctx)
showConnectingView()
} else {
label_status.text = getString(R.string.main_label_not_connected)
showDisconnectedView(view)
showDisconnectedView()
}
}
......@@ -82,14 +88,37 @@ class MainFragment : Fragment() {
.commit()
}
private fun showConnectedView(view: View) {
view.findViewById<ConstraintLayout>(R.id.container_connected).visibility = View.VISIBLE
view.findViewById<ConstraintLayout>(R.id.container_disconnected).visibility = View.GONE
view.findViewById<Button>(R.id.button_pair).text = getString(R.string.main_button_manage_pairings)
private fun showConnectedView() {
activity?.runOnUiThread {
container_connected.visibility = View.VISIBLE
container_disconnected.visibility = View.GONE
button_pair.text = getString(R.string.main_button_manage_pairings)
button_hatchery.isEnabled = true
button_send.isEnabled = true
button_mood.isEnabled = true
button_beautiful.isEnabled = true
button_set_time.isEnabled = true
val device = ConnectionService.device
label_status.text = getString(R.string.main_label_connected, device?.name, device?.address)
}
}
private fun showConnectingView() {
val device = ConnectionService.device
label_status.text = getString(R.string.main_label_connecting, device?.name, device?.address)
button_pair.text = getString(R.string.main_button_manage_pairings)
}
private fun showDisconnectedView() {
container_connected.visibility = View.GONE
container_disconnected.visibility = View.VISIBLE
button_pair.text = getString(R.string.main_button_pair)
}
private fun showDisconnectedView(view: View) {
view.findViewById<ConstraintLayout>(R.id.container_connected).visibility = View.GONE
view.findViewById<ConstraintLayout>(R.id.container_disconnected).visibility = View.VISIBLE
override fun onConnectionReady() {
showConnectedView()
}
}
......@@ -22,60 +22,23 @@
package de.ccc.events.badge.card10.mood
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback
import android.bluetooth.BluetoothGattCharacteristic
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import de.ccc.events.badge.card10.CARD10_BLUETOOTH_MAC_PREFIX
import de.ccc.events.badge.card10.CARD10_SERVICE_UUID
import de.ccc.events.badge.card10.R
import de.ccc.events.badge.card10.ROCKETS_CHARACTERISTIC_UUID
import de.ccc.events.badge.card10.common.ConnectionService
import de.ccc.events.badge.card10.time.Card10Service
import kotlinx.android.synthetic.main.mood_fragment.*
import java.util.concurrent.CountDownLatch
class MoodFragment : Fragment() {
private val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
private lateinit var gatt: BluetoothGatt
private var rocketsCharacteristic: BluetoothGattCharacteristic? = null
private var writeLatch: CountDownLatch? = null
private var card10Service: Card10Service? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val callback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
System.out.println("===== onConnectionStateChange " + gatt + " / " + status + " / " + newState)
if (newState == BluetoothGatt.STATE_CONNECTED)
gatt.discoverServices()
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
System.out.println("===== onServicesDiscovered " + gatt + " / " + status)
val card10Service = gatt.getService(CARD10_SERVICE_UUID)
rocketsCharacteristic = card10Service.getCharacteristic(ROCKETS_CHARACTERISTIC_UUID)
}
override fun onCharacteristicWrite(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int
) {
System.out.println("===== onCharacteristicWrite " + characteristic.uuid.toString() + " / " + characteristic.value + " / " + status)
writeLatch?.countDown();
}
}
val remoteDevices =
bluetoothAdapter.bondedDevices.filter { it.address.startsWith(CARD10_BLUETOOTH_MAC_PREFIX, true) }
if (remoteDevices.isEmpty())
activity!!.finish()
gatt = remoteDevices.get(0).connectGatt(activity, false, callback)
card10Service = ConnectionService.card10Service
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
......@@ -83,28 +46,14 @@ class MoodFragment : Fragment() {
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
mood_good.setOnClickListener({
writeGatt(rocketsCharacteristic!!, ubyteArrayOf(0xffu, 0x00u, 0x00u).toByteArray())
})
mood_neutral.setOnClickListener({
writeGatt(rocketsCharacteristic!!, ubyteArrayOf(0x00u, 0xffu, 0x00u).toByteArray())
})
mood_bad.setOnClickListener({
writeGatt(rocketsCharacteristic!!, ubyteArrayOf(0x00u, 0x00u, 0xffu).toByteArray())
})
}
fun writeGatt(characteristic: BluetoothGattCharacteristic, values: ByteArray) {
characteristic.value = values
val init = gatt.writeCharacteristic(rocketsCharacteristic)
if (!init)
System.out.println("Failed to initiate writing GATT attribute")
writeLatch = CountDownLatch(1)
writeLatch!!.await();
}
override fun onDestroy() {
gatt.close();
super.onDestroy()
mood_good.setOnClickListener{
card10Service?.setRocketValue(ubyteArrayOf(0xffu, 0x00u, 0x00u).toByteArray())
}
mood_neutral.setOnClickListener{
card10Service?.setRocketValue(ubyteArrayOf(0x00u, 0xffu, 0x00u).toByteArray())
}
mood_bad.setOnClickListener{
card10Service?.setRocketValue(ubyteArrayOf(0x00u, 0x00u, 0xffu).toByteArray())
}
}
}
......@@ -22,65 +22,27 @@
package de.ccc.events.badge.card10.sparkle
import android.bluetooth.*
import android.os.Bundle
import android.os.Handler
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import de.ccc.events.badge.card10.CARD10_BLUETOOTH_MAC_PREFIX
import de.ccc.events.badge.card10.CARD10_SERVICE_UUID
import de.ccc.events.badge.card10.LEDS_ABOVE_CHARACTERISTIC_UUID
import de.ccc.events.badge.card10.R
import java.util.concurrent.CountDownLatch
import de.ccc.events.badge.card10.common.ConnectionService
import de.ccc.events.badge.card10.time.Card10Service
import kotlin.random.Random
class BeautifulFragment : Fragment(), Runnable {
private val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
private lateinit var gatt: BluetoothGatt
private var ledsAboveCharacteristic: BluetoothGattCharacteristic? = null
private @Volatile var writeLatch: CountDownLatch? = null
private val handler = Handler()
private var card10Service: Card10Service? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val callback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
println("===== onConnectionStateChange ${gatt} ${if (status == BluetoothGatt.GATT_SUCCESS) "SUCCESS" else status} ${if (newState == BluetoothProfile.STATE_CONNECTED) "CONNECTED" else if (newState == BluetoothProfile.STATE_DISCONNECTED) "DISCONNECTED" else newState}")
if (newState == BluetoothGatt.STATE_CONNECTED)
gatt.requestMtu(64)
}
override fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {
println("===== onMtuChanged ${gatt} ${if (status == BluetoothGatt.GATT_SUCCESS) "SUCCESS" else status} ${mtu}")
gatt.discoverServices()
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
println("===== onServicesDiscovered ${gatt} ${if (status == BluetoothGatt.GATT_SUCCESS) "SUCCESS" else status}")
val card10Service = gatt.getService(CARD10_SERVICE_UUID)
ledsAboveCharacteristic = card10Service.getCharacteristic(LEDS_ABOVE_CHARACTERISTIC_UUID)
handler.post(this@BeautifulFragment)
}
override fun onCharacteristicWrite(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int
) {
println("=== onCharacteristicWrite ${gatt} ${if (status == BluetoothGatt.GATT_SUCCESS) "SUCCESS" else status} ${characteristic.uuid} ${characteristic.value}")
writeLatch?.countDown();
}
}
val remoteDevices =
bluetoothAdapter.bondedDevices.filter { it.address.startsWith(CARD10_BLUETOOTH_MAC_PREFIX, true) }
if (remoteDevices.isEmpty())
activity!!.finish()
gatt = remoteDevices.get(0).connectGatt(activity, false, callback)
card10Service = ConnectionService.card10Service
handler.post(this@BeautifulFragment)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
......@@ -88,18 +50,12 @@ class BeautifulFragment : Fragment(), Runnable {
}
override fun run() {
ledsAboveCharacteristic!!.value = Random.nextBytes(33);
writeLatch = CountDownLatch(1)
val init = gatt.writeCharacteristic(ledsAboveCharacteristic)
if (!init)
println("=== Failed to initiate writing GATT attribute")
writeLatch!!.await();
card10Service?.setLeds(Random.nextBytes(33))
handler.postDelayed(this, 100)
}
override fun onDestroy() {
handler.removeCallbacksAndMessages(null)
gatt.close();
super.onDestroy()
}
}
......@@ -24,6 +24,8 @@ package de.ccc.events.badge.card10.time
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattService
import de.ccc.events.badge.card10.LEDS_ABOVE_CHARACTERISTIC_UUID
import de.ccc.events.badge.card10.ROCKETS_CHARACTERISTIC_UUID
import de.ccc.events.badge.card10.TIME_CHARACTERISTIC_UUID
import de.ccc.events.badge.card10.common.ConnectionService
import java.nio.ByteBuffer
......@@ -31,11 +33,14 @@ import java.nio.ByteBuffer
class Card10Service(
service: BluetoothGattService
) {
private val timeCharacteristic: BluetoothGattCharacteristic
private val timeCharacteristic = service.getCharacteristic(TIME_CHARACTERISTIC_UUID)
private val rocketsCharacteristic = service.getCharacteristic(ROCKETS_CHARACTERISTIC_UUID)
private var ledsAboveCharacteristic = service.getCharacteristic(LEDS_ABOVE_CHARACTERISTIC_UUID)
init {
timeCharacteristic = service.getCharacteristic(TIME_CHARACTERISTIC_UUID)
timeCharacteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
rocketsCharacteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
ledsAboveCharacteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
}
fun setTime() {
......@@ -44,4 +49,14 @@ class Card10Service(
timeCharacteristic.value = buffer.array()
ConnectionService.writeCharacteristic(timeCharacteristic)
}
fun setRocketValue(value: ByteArray) {
rocketsCharacteristic.value = value
ConnectionService.writeCharacteristic(rocketsCharacteristic)
}
fun setLeds(value: ByteArray) {
ledsAboveCharacteristic.value = value
ConnectionService.writeCharacteristic(ledsAboveCharacteristic)
}
}
\ No newline at end of file
......@@ -52,7 +52,8 @@
android:text="@string/main_button_browse_apps"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
app:layout_constraintTop_toTopOf="parent"
android:enabled="false"/>
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
......@@ -62,7 +63,8 @@
android:text="@string/main_button_send_file"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button_hatchery"/>
app:layout_constraintTop_toBottomOf="@+id/button_hatchery"
android:enabled="false"/>
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
......@@ -72,7 +74,8 @@
android:text="@string/main_button_mood"
app:layout_constraintTop_toBottomOf="@+id/button_send"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
app:layout_constraintRight_toRightOf="parent"
android:enabled="false"/>
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
......@@ -82,7 +85,8 @@
android:text="@string/main_button_beautiful"
app:layout_constraintTop_toBottomOf="@+id/button_mood"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
app:layout_constraintRight_toRightOf="parent"
android:enabled="false"/>
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
......@@ -92,7 +96,8 @@
android:text="@string/main_button_set_time"
app:layout_constraintTop_toBottomOf="@+id/button_beautiful"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
app:layout_constraintRight_toRightOf="parent"
android:enabled="false"/>
</androidx.constraintlayout.widget.ConstraintLayout>
......
......@@ -3,7 +3,8 @@
<string name="main_label_paired">You are paired to %1$s (%2$s)</string>
<string name="main_label_not_connected">You are currently not connected to your card10.</string>
<string name="main_label_status">You are connected to %1$s (%2$s)</string>
<string name="main_label_connected">You are connected to %1$s (%2$s)</string>
<string name="main_label_connecting">Connecting to %1$s (%2$s)</string>
<string name="main_button_pair">Pair</string>
<string name="main_button_manage_pairings">Manage Paired Devices</string>
<string name="main_button_browse_apps">Browse Apps</string>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment