diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 0ab9e22..906a5f7 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,5 +1,6 @@
plugins {
androidApplication
+ kotlinAndroid
}
val packageName = "ru.myitschool.work"
@@ -18,14 +19,18 @@ android {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
- buildFeatures.viewBinding = true
+ buildFeatures.dataBinding = true
compileOptions {
sourceCompatibility = Version.Kotlin.javaSource
targetCompatibility = Version.Kotlin.javaSource
}
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
}
dependencies {
+ implementation("androidx.core:core-ktx:1.7.0")
defaultLibrary()
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 9ee5c40..c09c2d5 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,6 +2,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ tools:targetApi="31" >
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/kotlin/ru/myitschool/work/ActivityViewModel.kt b/app/src/main/kotlin/ru/myitschool/work/ActivityViewModel.kt
new file mode 100644
index 0000000..0ac40d0
--- /dev/null
+++ b/app/src/main/kotlin/ru/myitschool/work/ActivityViewModel.kt
@@ -0,0 +1,12 @@
+package ru.myitschool.work
+
+import android.bluetooth.BluetoothDevice
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+
+class ActivityViewModel(
+
+): ViewModel() {
+ val foundDevice = MutableLiveData();
+ val statemotors = MutableLiveData("OFF");
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/ru/myitschool/work/BluetoothScanner.kt b/app/src/main/kotlin/ru/myitschool/work/BluetoothScanner.kt
new file mode 100644
index 0000000..4ac2637
--- /dev/null
+++ b/app/src/main/kotlin/ru/myitschool/work/BluetoothScanner.kt
@@ -0,0 +1,126 @@
+package ru.myitschool.work
+
+import android.Manifest
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothManager
+import android.bluetooth.le.BluetoothLeScanner
+import android.bluetooth.le.ScanCallback
+import android.bluetooth.le.ScanFilter
+import android.bluetooth.le.ScanResult
+import android.bluetooth.le.ScanSettings
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Handler
+import android.os.Looper
+import android.util.Log
+import android.widget.Toast
+import androidx.core.app.ActivityCompat
+
+class BluetoothScanner(activityViewModel: ActivityViewModel, private val context: Context) {
+ private val mBluetoothAdapter: BluetoothAdapter
+ private val mBluetoothLeScanner: BluetoothLeScanner
+ private var mScanCallback: ScanCallback? = null
+ private val mHandler: Handler
+ private var mScanning = false
+ private var bluetoothDevice: BluetoothDevice? = null
+ private val activityViewModel: ActivityViewModel
+
+ init {
+ mHandler = Handler(Looper.getMainLooper())
+ val bluetoothManager =
+ context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && ActivityCompat.checkSelfPermission(
+ context, Manifest.permission.BLUETOOTH_SCAN
+ ) != PackageManager.PERMISSION_GRANTED
+ ) {
+
+ }
+ mBluetoothAdapter = bluetoothManager.adapter
+ mBluetoothAdapter.startDiscovery()
+ mBluetoothLeScanner = mBluetoothAdapter.bluetoothLeScanner
+ this.activityViewModel = activityViewModel
+ }
+
+ fun createScanSettings():ScanSettings{
+ TODO()//Реализовать метод создания ScanSetting с настройками Балансированного режима сканирования и немедленной показе результатов сканирования
+ return ScanSettings.Builder()
+ .setScanMode(ScanSettings.SCAN_MODE_BALANCED)
+ .setReportDelay(0)
+ .build()
+ }
+ fun startScan(filters: ArrayList) {
+ if (!mScanning) {
+ Log.i(TAG, "Starting scan...")
+ val settings = createScanSettings()
+ mScanCallback = object : ScanCallback() {
+ override fun onScanResult(callbackType: Int, result: ScanResult) {
+ super.onScanResult(callbackType, result)
+ val device = result.device
+ println("RESULT")
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && ActivityCompat.checkSelfPermission(
+ context, Manifest.permission.BLUETOOTH_CONNECT
+ ) != PackageManager.PERMISSION_GRANTED
+ ) {
+ return
+ }
+ Log.i(TAG, "Found device: " + device.name + " (" + device.address + ")")
+ bluetoothDevice = device
+ activityViewModel.foundDevice.value = device
+ }
+
+ override fun onBatchScanResults(results: List) {
+ super.onBatchScanResults(results)
+ for (result in results) {
+ val device = result.device
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && ActivityCompat.checkSelfPermission(
+ context, Manifest.permission.BLUETOOTH_CONNECT
+ ) != PackageManager.PERMISSION_GRANTED
+ ) {
+ return
+ }
+ Log.i(TAG, "Found device: " + device.name + " (" + device.address + ")")
+ }
+ }
+
+ override fun onScanFailed(errorCode: Int) {
+ super.onScanFailed(errorCode)
+ Log.e(
+ TAG,
+ "Scan failed with error code: $errorCode"
+ )
+ Toast.makeText(context, "Scan failed with error code: ", Toast.LENGTH_SHORT)
+ .show()
+ }
+ }
+ mBluetoothLeScanner.startScan(filters, settings, mScanCallback)
+ mHandler.postDelayed({ stopScan() }, SCAN_PERIOD)
+ mScanning = true
+ } else {
+ Log.i(TAG, "Scan already in progress")
+ Toast.makeText(context, "Scan already in progress", Toast.LENGTH_SHORT).show()
+ }
+ }
+
+ fun stopScan() {
+ if (mScanning) {
+ Log.i(TAG, "Stopping scan...")
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && ActivityCompat.checkSelfPermission(
+ context, Manifest.permission.BLUETOOTH_SCAN
+ ) != PackageManager.PERMISSION_GRANTED
+ ) {
+ return
+ }
+ mBluetoothLeScanner.stopScan(mScanCallback)
+ mScanning = false
+ } else {
+ Log.i(TAG, "No scan in progress")
+ }
+ }
+
+ companion object {
+ private val TAG = BluetoothScanner::class.java.simpleName
+ private const val SCAN_PERIOD: Long = 4000 // 4 seconds
+ }
+}
diff --git a/app/src/main/kotlin/ru/myitschool/work/MainActivity.kt b/app/src/main/kotlin/ru/myitschool/work/MainActivity.kt
new file mode 100644
index 0000000..48e172e
--- /dev/null
+++ b/app/src/main/kotlin/ru/myitschool/work/MainActivity.kt
@@ -0,0 +1,389 @@
+package ru.myitschool.work
+
+import android.Manifest
+import android.bluetooth.*
+import android.bluetooth.BluetoothDevice.*
+import android.bluetooth.le.ScanFilter
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Bundle
+import android.provider.Settings
+import android.util.Log
+import android.view.View
+import android.widget.Toast
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
+import androidx.databinding.DataBindingUtil
+import androidx.lifecycle.ViewModelProvider
+
+import kotlinx.coroutines.*
+import ru.myitschool.work.databinding.ActivityMainBinding
+import java.nio.ByteBuffer
+import java.util.*
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.function.Consumer
+import kotlin.collections.ArrayList
+
+
+class MainActivity : AppCompatActivity() {
+ private val BLP_SERVICE_UUID: UUID? = UUID.fromString("e7112e6c-c396-11ed-afa1-0242ac120002");//e7112e6c-c396-11ed-afa1-0242ac120002
+ private val names:Array? = arrayOf("RDB-1")
+ private val mac:Array? = arrayOf("0C:B8:15:F6:0D:52")
+
+
+ val TAG ="CONTROLLER"
+
+
+ private lateinit var bluetoothGattCallback: BluetoothGattCallback
+ private lateinit var bluetoothScanner: BluetoothScanner
+ private lateinit var activityViewModel: ActivityViewModel
+
+ private lateinit var service:BluetoothGattService;
+
+
+ companion object{
+ private var status:Int=-1;
+ private var newState:Int=-1;
+ private lateinit var gatt:BluetoothGatt;
+ private var isOffMotors=false;
+
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ activityViewModel = ViewModelProvider(this).get(
+ ActivityViewModel::class.java
+ )
+ val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
+
+ binding.vm =activityViewModel;
+ binding.setLifecycleOwner(this);
+ setContentView(binding.root)
+
+ if (allPermissionGranted()) {
+
+ } else {
+ askPermissions()
+ }
+
+ bluetoothScanner = BluetoothScanner(activityViewModel,this);
+
+ val scanningButton = binding.Scan
+ scanningButton.setOnClickListener(View.OnClickListener {
+ Scanning()
+ })
+ val connectButton = binding.Connect
+ connectButton.setOnClickListener(View.OnClickListener {
+ Connect()
+ })
+ val OnOffMotorsButton = binding.OnOffMotors
+ OnOffMotorsButton.setOnClickListener(View.OnClickListener {
+ OnOffMotors()
+ })
+ val position1Button = binding.Position1
+ position1Button.setOnClickListener(View.OnClickListener {
+ Position1()
+ })
+ val position2Button = binding.Position2
+ position2Button.setOnClickListener(View.OnClickListener {
+ Position2()
+ })
+
+
+ bluetoothGattCallback = object : BluetoothGattCallback(){
+ override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
+ super.onConnectionStateChange(gatt, status, newState)
+ Companion.newState = newState
+ Companion.status = status
+ if(newState==BluetoothProfile.STATE_CONNECTED)
+ Companion.gatt = gatt!!
+
+ }
+ override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {
+ super.onServicesDiscovered(gatt, status)
+
+ for(ser in gatt!!.services){
+ println(" "+ ser.uuid+" "+ ser.characteristics.toString()+" "+ser.type+" "+ser.includedServices)
+ }
+
+ service = gatt!!.getService(BLP_SERVICE_UUID)
+ for(chara in service.characteristics) {
+ println(TAG + " " + chara.uuid + " " + chara.properties + " " + chara.permissions + " ")
+ }
+ val characteristic0 = service.getCharacteristic(service.characteristics[0].uuid)
+ Log.d(TAG,characteristic0.toString())
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && ActivityCompat.checkSelfPermission(this@MainActivity, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
+ return
+ }
+
+ CoroutineScope(Dispatchers.Main).launch {
+ activityViewModel.statemotors.value = "Calibration"
+ }
+ gatt.readCharacteristic(service.characteristics[0])
+
+ }
+
+ override fun onCharacteristicRead(
+ gatt: BluetoothGatt?,
+ characteristic: BluetoothGattCharacteristic?,
+ status: Int
+ ) {
+ super.onCharacteristicRead(gatt, characteristic, status)
+
+ if (status === BluetoothGatt.GATT_SUCCESS) {
+ // Получаем данные характеристики
+ var value = characteristic!!.value.clone()
+
+ if(value[0]==(0).toByte() && value[1]==(0).toByte()) {//Если все двигатели выключены
+ isOffMotors = true;
+ //и включаем двигатели
+ value = byteArrayOf(0b11111111.toByte(), 0b00001111.toByte())
+ val characteristic0 = service.characteristics[0];
+ characteristic0.value = value!!
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && ActivityCompat.checkSelfPermission(
+ this@MainActivity,
+ Manifest.permission.BLUETOOTH_CONNECT
+ ) != PackageManager.PERMISSION_GRANTED
+ ) {
+ return
+ }
+ gatt!!.writeCharacteristic(characteristic0) // перезаписываем состояния двигателей чтобы их включить
+
+
+ }else{
+ CoroutineScope(Dispatchers.Main).launch {
+ Toast.makeText(this@MainActivity, "Двигатели уже включены!", Toast.LENGTH_SHORT).show()
+ activityViewModel.statemotors.value = "ON"
+ }
+ }
+ // Делаем что-то с полученными данными
+ } else {
+ // Чтение характеристики не удалось
+ }
+ }
+
+ override fun onCharacteristicWrite(
+ gatt: BluetoothGatt?,
+ characteristic: BluetoothGattCharacteristic?,
+ status: Int
+ ) {
+ super.onCharacteristicWrite(gatt, characteristic, status)
+
+ if (status === BluetoothGatt.GATT_SUCCESS) {
+ if (characteristic == service.characteristics[0]){// данная характиристика отвечает за состояния вкл/выкл двигателя
+
+ CoroutineScope(Dispatchers.Main).launch {
+ activityViewModel.statemotors.value = "ON"
+ }
+ } else if (characteristic == service.characteristics[2]) { // данная характиристика отвечает за положение
+
+ println("VALUE "+Arrays.toString(characteristic!!.value))
+ val floats = FloatArray(characteristic.value.size/4);
+ var i = 0
+ while (i*4 < characteristic.value.size) {
+ val myFloat = ByteBuffer.wrap(characteristic.value).getFloat(i*4)
+ floats.set(i,myFloat)
+ i += 1
+ }
+ println("FLOAT "+Arrays.toString(floats))
+ }
+ }
+
+
+ }
+ }
+
+ }
+
+ override fun onStart() {
+ super.onStart()
+ if (allPermissionGranted()) {
+ bluetoothScanner = BluetoothScanner(activityViewModel,this);
+ } else {
+ askPermissions()
+ }
+ }
+
+ fun createScanFilter(name:String):ScanFilter{
+ TODO()//Реализовать createScanFilter - Создание scan filter по имени
+ return ScanFilter.Builder()
+ .setDeviceName(name)
+ .build()
+ }
+ fun Scanning(){
+ println("Scanning")
+ var filters: ArrayList = ArrayList();
+
+ if (names != null) {
+ for (name in names) {
+ println(name)
+ val filter = createScanFilter(name)
+ filters.add(filter)
+ }
+ }
+ bluetoothScanner.startScan(filters)
+
+ }
+
+
+
+
+ fun Connect(){
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED
+ ) {
+ return
+ }
+ if(Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
+ pairDevice(activityViewModel.foundDevice.value)
+ }
+
+ if(activityViewModel.foundDevice.value!=null) {
+ val gatt: BluetoothGatt = activityViewModel.foundDevice.value!!
+ .connectGatt(this, false, bluetoothGattCallback, TRANSPORT_AUTO)
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ gatt.readPhy()
+ }
+ }
+
+ }
+ fun OnOffMotors(){
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && ActivityCompat.checkSelfPermission(this@MainActivity,Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
+ }
+ if(status == BluetoothGatt.GATT_SUCCESS) {
+ if (newState == BluetoothProfile.STATE_CONNECTED) {
+ // Мы подключились, можно запускать обнаружение сервисов
+ gatt?.discoverServices();
+
+ } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
+ // Мы успешно отключились (контролируемое отключение)
+ gatt?.close();
+ } else {
+ // мы или подключаемся или отключаемся, просто игнорируем эти статусы
+ }
+ } else {
+ // Произошла ошибка... разбираемся, что случилось!
+ gatt?.close();
+ }
+ }
+
+ fun floatAAToByteArray(floatAA: ArrayList>):ByteArray{
+ TODO()//Реализовать метод перевода из массива вещественных чисел к массиву байтов
+ var value :ByteArray? = null
+ value = ByteArray(floatAA.size*floatAA[0].size*Float.SIZE_BYTES)
+ val buffer = ByteBuffer.wrap(value)
+ for (i in floatAA.indices){
+ for(j in floatAA[0].indices){
+ buffer.putFloat(floatAA[i][j])
+ }
+ }
+ return value
+ }
+
+ fun Position1(){
+ val pos1 = arrayListOf>(arrayListOf(0.8f, 12f, 0f, 10f, 0f ))
+ val characteristic = service.getCharacteristic(service.characteristics[2].uuid)
+ Log.d(TAG,characteristic.uuid.toString())
+ var value :ByteArray? = floatAAToByteArray(pos1)
+
+ characteristic.value = value!!
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && ActivityCompat.checkSelfPermission(
+ this@MainActivity,
+ Manifest.permission.BLUETOOTH_CONNECT
+ ) != PackageManager.PERMISSION_GRANTED
+ ) {
+ return
+ }
+ gatt!!.writeCharacteristic(characteristic)
+
+ }
+ fun Position2(){
+ TODO()//Реализать метод Position2 для передачи массива вещественных чисел на (третью характеристику) [0.3, 2, 4.5, 2, 0.4]
+ val pos1 = arrayListOf>(arrayListOf(0.3f, 2f, 4.5f, 2f, 0.4f ))
+ val characteristic = service.getCharacteristic(service.characteristics[2].uuid)
+ Log.d(TAG,characteristic.uuid.toString())
+ var value :ByteArray? = floatAAToByteArray(pos1)
+
+ characteristic.value = value!!
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && ActivityCompat.checkSelfPermission(
+ this@MainActivity,
+ Manifest.permission.BLUETOOTH_CONNECT
+ ) != PackageManager.PERMISSION_GRANTED
+ ) {
+ return
+ }
+ gatt!!.writeCharacteristic(characteristic)
+ }
+
+ fun createPairingIntent(device:BluetoothDevice?):Intent{
+ TODO()//метод должен отправить intent для запроса присоединения к устройству с вводом пин кода
+ val intent = Intent(BluetoothDevice.ACTION_PAIRING_REQUEST)
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device)
+ intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.PAIRING_VARIANT_PIN)
+ intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ return intent
+ }
+
+
+ fun pairDevice(device: BluetoothDevice?) {
+ val intent = createPairingIntent(device)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && ActivityCompat.checkSelfPermission(
+ this,
+ Manifest.permission.BLUETOOTH_CONNECT
+ ) != PackageManager.PERMISSION_GRANTED
+ ) {
+
+ return
+ }
+
+ this.startActivity(intent)
+ }
+
+
+ private val PERMISSIONS = arrayOf(Manifest.permission.BLUETOOTH_SCAN,Manifest.permission.BLUETOOTH_CONNECT)
+
+ private val requestLauncher = registerForActivityResult, Map>(
+ ActivityResultContracts.RequestMultiplePermissions()
+ ) { permissions: Map ->
+ val permissionGranted =
+ AtomicBoolean(false)
+ permissions.forEach { (key: String, value: Boolean) ->
+ if (Arrays.asList(*PERMISSIONS).contains(key) && !value) {
+ Toast.makeText(this, "$key $value", Toast.LENGTH_SHORT).show()
+ permissionGranted.set(false)
+ }
+ }
+ if (!permissionGranted.get()) {
+ Toast.makeText(
+ this,
+ "Permission request denied",
+ Toast.LENGTH_SHORT
+ ).show()
+ } else {
+ //do something
+ }
+ }
+
+ private fun askPermissions() {
+ requestLauncher.launch(PERMISSIONS)
+ }
+
+ fun allPermissionGranted(): Boolean {
+ val granted = AtomicBoolean(true)
+ Arrays.asList(*PERMISSIONS).forEach(Consumer { it: String? ->
+ if (ContextCompat.checkSelfPermission(
+ this,
+ it!!
+ ) == PackageManager.PERMISSION_DENIED
+ ) {
+ granted.set(false)
+ }
+ })
+ return granted.get()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..03f0040
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build.gradle.kts b/build.gradle.kts
index 860f2c8..abbfbf5 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -2,4 +2,5 @@
plugins {
androidApplication version Version.gradle apply false
kotlinJvm version Version.Kotlin.language apply false
+ kotlinAndroid version Version.Kotlin.language apply false
}
\ No newline at end of file