practise BLE yes
This commit is contained in:
12
app/src/main/kotlin/ru/myitschool/work/ActivityViewModel.kt
Normal file
12
app/src/main/kotlin/ru/myitschool/work/ActivityViewModel.kt
Normal file
@ -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<BluetoothDevice>();
|
||||
val statemotors = MutableLiveData<String>("OFF");
|
||||
}
|
126
app/src/main/kotlin/ru/myitschool/work/BluetoothScanner.kt
Normal file
126
app/src/main/kotlin/ru/myitschool/work/BluetoothScanner.kt
Normal file
@ -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<ScanFilter>) {
|
||||
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<ScanResult>) {
|
||||
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
|
||||
}
|
||||
}
|
389
app/src/main/kotlin/ru/myitschool/work/MainActivity.kt
Normal file
389
app/src/main/kotlin/ru/myitschool/work/MainActivity.kt
Normal file
@ -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<String>? = arrayOf("RDB-1")
|
||||
private val mac:Array<String>? = 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<ScanFilter> = 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<ArrayList<Float>>):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<kotlin.collections.ArrayList<Float>>(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<kotlin.collections.ArrayList<Float>>(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<Array<String>, Map<String, Boolean>>(
|
||||
ActivityResultContracts.RequestMultiplePermissions()
|
||||
) { permissions: Map<String, Boolean> ->
|
||||
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()
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user