├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md ├── pull_request_template.md └── workflows │ └── build.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── README.md ├── app ├── build.gradle ├── cpd.gradle ├── detekt.gradle ├── jacoco.gradle └── src │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── java │ │ └── com │ │ │ └── smlnskgmail │ │ │ └── jaman │ │ │ └── remotetemperaturecontrol │ │ │ ├── MainActivity.kt │ │ │ ├── components │ │ │ ├── AppDialog.kt │ │ │ ├── BaseBottomSheet.kt │ │ │ └── BaseFragment.kt │ │ │ └── logic │ │ │ ├── deviceselector │ │ │ ├── BtDevicesBottomSheet.kt │ │ │ └── recycler │ │ │ │ ├── BtDeviceHolder.kt │ │ │ │ └── BtDevicesAdapter.kt │ │ │ ├── monitor │ │ │ ├── MonitorFragment.kt │ │ │ ├── api │ │ │ │ ├── connection │ │ │ │ │ └── BtConnection.kt │ │ │ │ ├── entities │ │ │ │ │ ├── BtDevice.kt │ │ │ │ │ └── targets │ │ │ │ │ │ ├── BtConnectTarget.kt │ │ │ │ │ │ ├── BtDisconnectTarget.kt │ │ │ │ │ │ └── BtPauseTarget.kt │ │ │ │ └── monitor │ │ │ │ │ ├── BtMonitor.kt │ │ │ │ │ ├── BtMonitorSignalType.kt │ │ │ │ │ └── BtMonitorTarget.kt │ │ │ └── impl │ │ │ │ ├── debugbt │ │ │ │ ├── DebugBtConnection.kt │ │ │ │ └── DebugBtMonitor.kt │ │ │ │ └── devicebt │ │ │ │ ├── DeviceBtConnection.kt │ │ │ │ ├── DeviceBtDataExtractor.kt │ │ │ │ └── DeviceBtMonitor.kt │ │ │ └── settings │ │ │ └── SettingsBottomSheet.kt │ └── res │ │ ├── drawable │ │ ├── bg_bottom_sheet.xml │ │ ├── ic_item_selected.xml │ │ └── ic_launcher_foreground.xml │ │ ├── layout │ │ ├── bottom_sheet_bt_devices.xml │ │ ├── bottom_sheet_settings.xml │ │ ├── fragment_monitor.xml │ │ └── item_bt_device.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings-non-translatable.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── smlnskgmail │ └── jaman │ └── remotetemperaturecontrol │ ├── TestSuite.kt │ └── api │ ├── MonitorDataExtractorTest.kt │ ├── MonitorDataSendTest.kt │ ├── entities │ ├── BaseEntityTest.kt │ └── BtDeviceTest.kt │ └── fakemonitor │ ├── FakeMonitor.kt │ └── FakeMonitorHandleTarget.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── media ├── logo │ └── ic_app.png └── screenshots │ ├── screenshot_01.png │ └── screenshot_02.png └── settings.gradle /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...'; 16 | 2. Click on '....'; 17 | 3. Scroll down to '....'; 18 | 4. See error. 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Device (please complete the following information):** 27 | - Device: [e.g. Xiaomi Mi A1] 28 | - OS: [e.g. Android 8.1] 29 | - Version [e.g. 2.9.9.3] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Checklist 2 | 3 | __Common__ 4 | 5 | - [ ] I am ran the app before creating PR 6 | - [ ] I am ran all tests before creating PR 7 | 8 | __UI__ 9 | 10 | - [ ] I am ran the app for visual checks. 11 | 12 | __Logic__ 13 | 14 | - [ ] I am tested basic app functionality. 15 | 16 | ## Changes 17 | 18 | Describe all changes here: 19 | 20 | - First; 21 | - Second; 22 | - Third; 23 | - ... 24 | 25 | ## Comments 26 | 27 | Describe all additional information here. 28 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Set up JDK 1.8 15 | uses: actions/setup-java@v1 16 | with: 17 | java-version: 1.8 18 | - name: Build with Gradle 19 | run: ./gradlew build 20 | - name: Generage JaCoCo report 21 | run: ./gradlew jacocoTestDebugbtDebugUnitTestReport 22 | - name: Send JaCoCo coverage report to Codecov 23 | uses: codecov/codecov-action@v1 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | *.aab 5 | 6 | # Files for the ART/Dalvik VM 7 | *.dex 8 | 9 | # Java class files 10 | *.class 11 | 12 | # Generated files 13 | bin/ 14 | gen/ 15 | out/ 16 | 17 | # Gradle files 18 | .gradle/ 19 | build/ 20 | 21 | # Local configuration file (sdk path, etc) 22 | local.properties 23 | 24 | # Proguard folder generated by Eclipse 25 | proguard/ 26 | 27 | # Log Files 28 | *.log 29 | 30 | # Android Studio Navigation editor temp files 31 | .navigation/ 32 | 33 | # Android Studio captures folder 34 | captures/ 35 | 36 | # IntelliJ 37 | /.idea/ 38 | *.iml 39 | 40 | # Keystore files 41 | *.jks 42 | *.keystore 43 | 44 | # External native build folder generated in Android Studio 2.2 and later 45 | .externalNativeBuild 46 | 47 | # Google Services (e.g. APIs or Firebase) 48 | google-services.json 49 | 50 | # Freeline 51 | freeline.py 52 | freeline/ 53 | freeline_project_description.json 54 | 55 | # fastlane 56 | fastlane/report.xml 57 | fastlane/Preview.html 58 | fastlane/screenshots 59 | fastlane/test_output 60 | fastlane/readme.md 61 | 62 | # OSX files 63 | .DS_Store 64 | 65 | # Outputs 66 | app/release/ 67 | app/debug/ 68 | 69 | # Checkstyle 70 | checkstyle-result.xml 71 | 72 | # detekt 73 | detekt-config.yml 74 | 75 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at jaman.smlnsk@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Android Remote Temperature Control Client 4 | 5 | [![GitHubActions](https://github.com/fartem/android-remote-temperature-control-client/workflows/Build/badge.svg)](https://github.com/fartem/android-remote-temperature-control-client/actions?query=workflow%3ABuild) 6 | [![Codebeat](https://codebeat.co/badges/18d9fcff-7f58-4b78-943e-47bc4d091238)](https://codebeat.co/projects/github-com-fartem-android-remote-temperature-control-client-master) 7 | [![Codecov](https://codecov.io/gh/fartem/android-remote-temperature-control-client/branch/master/graph/badge.svg)](https://codecov.io/gh/fartem/android-remote-temperature-control-client) 8 | [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Android%20Home%20Control%20Client-brightgreen.svg?style=flat)](https://android-arsenal.com/details/3/7943) 9 | 10 | ## About 11 | 12 | Remote client for [Arduino temperature project](https://github.com/fartem/arduino-temperature-control). 13 | 14 | ## How to run 15 | 16 | __With Arduino module__ 17 | 18 | 1. run [Arduino module](https://github.com/fartem/arduino-temperature-control); 19 | 2. pair your Android device and Arduino Bluetooth module from a device settings; 20 | 3. install `devicebt` app flavor; 21 | 4. run app and select Arduino Bluetooth module from devices list. 22 | 23 | __Without Arduino module__ 24 | 25 | Install `debugbt` app flavor. 26 | 27 | ## Screenshots 28 | 29 |
30 |

31 | 32 | 33 |

34 | 35 | ## How to contribute 36 | 37 | Read [Commit Convention](https://github.com/fartem/repository-rules/blob/master/commit-convention/COMMIT_CONVENTION.md). Make sure your build is green before you contribute your pull request. Then: 38 | 39 | ```shell 40 | $ ./gradlew clean 41 | $ ./gradlew build 42 | ``` 43 | 44 | If you don't see any error messages, submit your pull request. 45 | 46 | ## Contributors 47 | 48 | * [@fartem](https://github.com/fartem) as Artem Fomchenkov 49 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply from: 'detekt.gradle' 5 | apply from: 'cpd.gradle' 6 | apply from: 'jacoco.gradle' 7 | 8 | android { 9 | compileSdkVersion 29 10 | buildToolsVersion'29.0.2' 11 | defaultConfig { 12 | applicationId 'com.smlnskgmail.jaman.remotetemperaturecontrol' 13 | minSdkVersion 21 14 | targetSdkVersion 29 15 | versionCode 1 16 | versionName '1.3.7' 17 | 18 | buildConfigField 'String', 'API_IMPL', "\"\"" 19 | } 20 | productFlavors { 21 | devicebt { 22 | flavorDimensions 'build' 23 | 24 | buildConfigField 'String', 'API_IMPL', "\"DEVICE_BT\"" 25 | } 26 | debugbt { 27 | flavorDimensions "build" 28 | 29 | buildConfigField 'String', 'API_IMPL', "\"DEBUG_BT\"" 30 | } 31 | } 32 | 33 | applicationVariants.all { variant -> 34 | variant.outputs.each { output -> 35 | def versionName = android.defaultConfig.versionName 36 | def productFlavorName = variant.flavorName 37 | def buildTypeName = variant.buildType.name 38 | output.outputFileName = "remote-monitor-${productFlavorName}-${buildTypeName}_${versionName}.apk" 39 | } 40 | } 41 | } 42 | 43 | dependencies { 44 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 45 | implementation 'androidx.appcompat:appcompat:1.1.0' 46 | implementation 'com.google.android.material:material:1.1.0' 47 | implementation 'androidx.core:core-ktx:1.2.0' 48 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 49 | 50 | // Unit tests 51 | testImplementation 'junit:junit:4.13' 52 | } 53 | -------------------------------------------------------------------------------- /app/cpd.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'cpd' 2 | 3 | cpd { 4 | language = 'kotlin' 5 | toolVersion = '6.21.0' 6 | } 7 | 8 | cpdCheck { 9 | source = files('src/main/java/') 10 | } 11 | -------------------------------------------------------------------------------- /app/detekt.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'io.gitlab.arturbosch.detekt' 2 | 3 | detekt { 4 | failFast = true 5 | 6 | def detektConfig = new File("$rootDir/detekt-config.yml") 7 | if (!detektConfig.exists()) { 8 | new URL( 9 | 'https://raw.githubusercontent.com/fartem/repository-rules/master/rules/kotlin/detekt/detekt.yml' 10 | ).withInputStream { 11 | i -> detektConfig.withOutputStream { 12 | it << i 13 | } 14 | } 15 | } 16 | 17 | config = files(detektConfig) 18 | } 19 | -------------------------------------------------------------------------------- /app/jacoco.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.hiya.jacoco-android' 2 | 3 | jacoco { 4 | toolVersion = '0.8.4' 5 | } 6 | 7 | jacocoAndroidUnitTestReport { 8 | csv.enabled false 9 | html.enabled true 10 | xml.enabled true 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fartem/android-remote-temperature-control-client/1c54b71b65cdbcc3fce09eb496de2fee0865a5a3/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/com/smlnskgmail/jaman/remotetemperaturecontrol/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.smlnskgmail.jaman.remotetemperaturecontrol 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.MonitorFragment 6 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.entities.targets.BtPauseTarget 7 | 8 | class MainActivity : AppCompatActivity() { 9 | 10 | companion object { 11 | 12 | private const val currentFragmentTag = "current_fragment" 13 | 14 | } 15 | 16 | override fun onCreate(savedInstanceState: Bundle?) { 17 | super.onCreate(savedInstanceState) 18 | showMonitorFragment() 19 | } 20 | 21 | private fun showMonitorFragment() { 22 | supportFragmentManager.beginTransaction() 23 | .add( 24 | android.R.id.content, 25 | MonitorFragment(), 26 | currentFragmentTag 27 | ) 28 | .addToBackStack(null) 29 | .commit() 30 | } 31 | 32 | override fun onPause() { 33 | super.onPause() 34 | handleOnPause() 35 | } 36 | 37 | private fun handleOnPause() { 38 | val currentFragment = supportFragmentManager.findFragmentByTag( 39 | currentFragmentTag 40 | ) 41 | if (currentFragment != null && currentFragment is BtPauseTarget) { 42 | currentFragment.btOnPause() 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/smlnskgmail/jaman/remotetemperaturecontrol/components/AppDialog.kt: -------------------------------------------------------------------------------- 1 | package com.smlnskgmail.jaman.remotetemperaturecontrol.components 2 | 3 | import android.app.AlertDialog 4 | import android.content.Context 5 | import android.content.DialogInterface 6 | 7 | object AppDialog { 8 | 9 | fun show( 10 | context: Context, 11 | titleResId: Int, 12 | message: Int, 13 | buttonText: Int, 14 | buttonClick: DialogInterface.OnClickListener 15 | ) { 16 | AlertDialog.Builder(context) 17 | .setTitle(titleResId) 18 | .setMessage(message) 19 | .setNegativeButton(buttonText, buttonClick) 20 | .setCancelable(false) 21 | .show() 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/smlnskgmail/jaman/remotetemperaturecontrol/components/BaseBottomSheet.kt: -------------------------------------------------------------------------------- 1 | package com.smlnskgmail.jaman.remotetemperaturecontrol.components 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment 8 | import com.smlnskgmail.jaman.remotetemperaturecontrol.R 9 | 10 | abstract class BaseBottomSheet : BottomSheetDialogFragment() { 11 | 12 | override fun onViewCreated( 13 | view: View, 14 | savedInstanceState: Bundle? 15 | ) { 16 | initialize() 17 | } 18 | 19 | abstract fun initialize() 20 | 21 | override fun onCreateView( 22 | inflater: LayoutInflater, 23 | container: ViewGroup?, 24 | savedInstanceState: Bundle? 25 | ) : View? = inflater.inflate( 26 | getLayoutResId(), 27 | container, 28 | false 29 | ) 30 | 31 | abstract fun getLayoutResId(): Int 32 | 33 | override fun getTheme(): Int { 34 | return R.style.AppBottomSheetStyle 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/smlnskgmail/jaman/remotetemperaturecontrol/components/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package com.smlnskgmail.jaman.remotetemperaturecontrol.components 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.entities.targets.BtPauseTarget 9 | 10 | abstract class BaseFragment : Fragment(), BtPauseTarget { 11 | 12 | override fun onCreateView( 13 | inflater: LayoutInflater, 14 | container: ViewGroup?, 15 | savedInstanceState: Bundle? 16 | ) : View = inflater.inflate( 17 | getLayoutResId(), 18 | container, 19 | false 20 | ) 21 | 22 | abstract fun getLayoutResId(): Int 23 | 24 | fun showBottomSheet(bottomSheet: BaseBottomSheet) { 25 | bottomSheet.show( 26 | activity!!.supportFragmentManager, 27 | bottomSheet::class.java.name 28 | ) 29 | } 30 | 31 | override fun btOnPause() {} 32 | 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/smlnskgmail/jaman/remotetemperaturecontrol/logic/deviceselector/BtDevicesBottomSheet.kt: -------------------------------------------------------------------------------- 1 | package com.smlnskgmail.jaman.remotetemperaturecontrol.logic.deviceselector 2 | 3 | import com.smlnskgmail.jaman.remotetemperaturecontrol.R 4 | import com.smlnskgmail.jaman.remotetemperaturecontrol.components.BaseBottomSheet 5 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.deviceselector.recycler.BtDevicesAdapter 6 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.entities.BtDevice 7 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.entities.targets.BtConnectTarget 8 | import kotlinx.android.synthetic.main.bottom_sheet_bt_devices.* 9 | 10 | class BtDevicesBottomSheet : BaseBottomSheet() { 11 | 12 | private var btConnectTarget: BtConnectTarget? = null 13 | private val btDevices = mutableListOf() 14 | 15 | override fun initialize() { 16 | devices.adapter = BtDevicesAdapter( 17 | btDevices 18 | ) 19 | select_device.setOnClickListener { 20 | val adapter = devices.adapter as BtDevicesAdapter 21 | btConnectTarget?.onBtDeviceSelected( 22 | adapter.getSelectedDeviceName(), 23 | adapter.getSelectedDeviceMacAddress() 24 | ) 25 | dismiss() 26 | } 27 | } 28 | 29 | fun setBtDevices(btDevices: List) { 30 | this.btDevices.clear() 31 | this.btDevices.addAll(btDevices) 32 | } 33 | 34 | fun setBtDeviceSelectCallback( 35 | btConnectTarget: BtConnectTarget 36 | ) { 37 | this.btConnectTarget = btConnectTarget 38 | } 39 | 40 | override fun getLayoutResId() = R.layout.bottom_sheet_bt_devices 41 | 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/smlnskgmail/jaman/remotetemperaturecontrol/logic/deviceselector/recycler/BtDeviceHolder.kt: -------------------------------------------------------------------------------- 1 | package com.smlnskgmail.jaman.remotetemperaturecontrol.logic.deviceselector.recycler 2 | 3 | import android.view.View 4 | import androidx.recyclerview.widget.RecyclerView 5 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.entities.BtDevice 6 | import kotlinx.android.synthetic.main.item_bt_device.view.* 7 | 8 | class BtDeviceHolder( 9 | itemView: View, 10 | private val recyclerAdapterBt: BtDevicesAdapter 11 | ) : RecyclerView.ViewHolder(itemView) { 12 | 13 | fun bind(btDevice: BtDevice) { 14 | itemView.bt_device_name.text = btDevice.name 15 | validateSelectedStatus(btDevice.isSelected) 16 | itemView.setOnClickListener { 17 | recyclerAdapterBt.selectDevice( 18 | btDevice 19 | ) 20 | } 21 | } 22 | 23 | private fun validateSelectedStatus( 24 | isSelected: Boolean 25 | ) { 26 | itemView.bt_device_status.visibility = if (isSelected) { 27 | View.VISIBLE 28 | } else { 29 | View.INVISIBLE 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/smlnskgmail/jaman/remotetemperaturecontrol/logic/deviceselector/recycler/BtDevicesAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.smlnskgmail.jaman.remotetemperaturecontrol.logic.deviceselector.recycler 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.RecyclerView 6 | import com.smlnskgmail.jaman.remotetemperaturecontrol.R 7 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.entities.BtDevice 8 | 9 | class BtDevicesAdapter( 10 | private val btDevices: List 11 | ) : RecyclerView.Adapter() { 12 | 13 | private var selectedDeviceMacAddress: String = "" 14 | private var selectedDeviceName: String = "" 15 | 16 | override fun onBindViewHolder( 17 | holderBt: BtDeviceHolder, 18 | position: Int 19 | ) { 20 | holderBt.bind(btDevices[position]) 21 | } 22 | 23 | fun selectDevice(btDevice: BtDevice) { 24 | btDevices.forEach { 25 | it.isSelected = btDevice == it 26 | if (btDevice.isSelected) { 27 | selectedDeviceName = btDevice.name 28 | selectedDeviceMacAddress = btDevice.address 29 | } 30 | } 31 | notifyDataSetChanged() 32 | } 33 | 34 | fun getSelectedDeviceName() = selectedDeviceName 35 | 36 | fun getSelectedDeviceMacAddress() = selectedDeviceMacAddress 37 | 38 | override fun onCreateViewHolder( 39 | parent: ViewGroup, 40 | viewType: Int 41 | ) = BtDeviceHolder( 42 | LayoutInflater.from(parent.context) 43 | .inflate(R.layout.item_bt_device, parent, false), 44 | this 45 | ) 46 | 47 | override fun getItemCount() = btDevices.size 48 | 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/smlnskgmail/jaman/remotetemperaturecontrol/logic/monitor/MonitorFragment.kt: -------------------------------------------------------------------------------- 1 | package com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor 2 | 3 | import android.annotation.SuppressLint 4 | import android.bluetooth.BluetoothAdapter 5 | import android.content.DialogInterface 6 | import android.os.Bundle 7 | import android.view.View 8 | import android.widget.TextView 9 | import com.smlnskgmail.jaman.remotetemperaturecontrol.BuildConfig 10 | import com.smlnskgmail.jaman.remotetemperaturecontrol.R 11 | import com.smlnskgmail.jaman.remotetemperaturecontrol.components.AppDialog 12 | import com.smlnskgmail.jaman.remotetemperaturecontrol.components.BaseFragment 13 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.deviceselector.BtDevicesBottomSheet 14 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.connection.BtConnection 15 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.entities.BtDevice 16 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.entities.targets.BtConnectTarget 17 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.entities.targets.BtDisconnectTarget 18 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.monitor.BtMonitor 19 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.monitor.BtMonitorSignalType 20 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.monitor.BtMonitorTarget 21 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.impl.debugbt.DebugBtConnection 22 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.impl.debugbt.DebugBtMonitor 23 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.impl.devicebt.DeviceBtConnection 24 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.impl.devicebt.DeviceBtMonitor 25 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.settings.SettingsBottomSheet 26 | import kotlinx.android.synthetic.main.fragment_monitor.* 27 | 28 | @SuppressWarnings("TooManyFunctions") 29 | class MonitorFragment : BaseFragment(), 30 | BtMonitorTarget, BtDisconnectTarget { 31 | 32 | private var monitorBtConnection: BtConnection? = null 33 | private var btAdapter: BluetoothAdapter? = null 34 | 35 | private var btMonitor: BtMonitor? = null 36 | 37 | override fun onViewCreated( 38 | view: View, 39 | savedInstanceState: Bundle? 40 | ) { 41 | btAdapter = BluetoothAdapter.getDefaultAdapter() 42 | 43 | @Suppress("ConstantConditionIf") 44 | if (BuildConfig.API_IMPL == "DEVICE_BT") { 45 | if (bluetoothIsEnabled()) { 46 | val btDevices = getBtDevices() 47 | if (btDevices.isNotEmpty()) { 48 | btMonitor = DeviceBtMonitor(this) 49 | showDevicesList(btDevices) 50 | } else { 51 | showBtDevicesNotFoundWarning() 52 | } 53 | } else { 54 | showBluetoothErrorDialog() 55 | } 56 | } else { 57 | startInDebugMode() 58 | initializeButtons() 59 | } 60 | } 61 | 62 | private fun bluetoothIsEnabled(): Boolean { 63 | return if (btAdapter != null) { 64 | btAdapter!!.isEnabled 65 | } else { 66 | false 67 | } 68 | } 69 | 70 | private fun showBluetoothErrorDialog() { 71 | AppDialog.show( 72 | context!!, 73 | R.string.bluetooth_error_dialog_title, 74 | R.string.bluetooth_error_dialog_message, 75 | R.string.bluetooth_error_dialog_button_text, 76 | DialogInterface.OnClickListener { dialog, _ -> 77 | dialog.cancel() 78 | activity!!.finish() 79 | } 80 | ) 81 | } 82 | 83 | @SuppressLint("SetTextI18n") 84 | private fun startInDebugMode() { 85 | btMonitor = DebugBtMonitor(this) 86 | monitorBtConnection = DebugBtConnection(btMonitor!!) 87 | startMonitorThread() 88 | 89 | tv_connected_device_info.text = "DEBUG" 90 | } 91 | 92 | private fun startMonitorThread() { 93 | object : Thread() { 94 | override fun run() { 95 | super.run() 96 | monitorBtConnection!!.connect() 97 | } 98 | }.start() 99 | } 100 | 101 | private fun showDevicesList(btDevices: List) { 102 | val devicesBottomSheet = BtDevicesBottomSheet() 103 | devicesBottomSheet.setBtDevices(btDevices) 104 | devicesBottomSheet.setBtDeviceSelectCallback(object : BtConnectTarget { 105 | override fun onBtDeviceSelected( 106 | name: String, 107 | address: String 108 | ) { 109 | monitorBtConnection = DeviceBtConnection( 110 | btAdapter!!, 111 | address, 112 | btMonitor!! 113 | ) 114 | startMonitorThread() 115 | setDeviceName(name) 116 | initializeButtons() 117 | } 118 | }) 119 | devicesBottomSheet.isCancelable = false 120 | showBottomSheet(devicesBottomSheet) 121 | } 122 | 123 | private fun getBtDevices(): List { 124 | val bondedDevices= btAdapter!!.bondedDevices 125 | return bondedDevices.mapTo(ArrayList(bondedDevices.size)) { 126 | BtDevice( 127 | it.name, 128 | it.address 129 | ) 130 | } 131 | } 132 | 133 | override fun temperatureAvailable(data: String) { 134 | setTextOnUIThread(tv_temperature_value, data) 135 | } 136 | 137 | override fun temperatureMaximumAvailable(data: String) { 138 | setTextOnUIThread(tv_temperature_max_value, data) 139 | } 140 | 141 | override fun temperatureMinimumAvailable(data: String) { 142 | setTextOnUIThread(tv_temperature_min_value, data) 143 | } 144 | 145 | override fun humidityAvailable(data: String) { 146 | setTextOnUIThread(tv_humidity_value, data) 147 | } 148 | 149 | override fun humidityMaximumAvailable(data: String) { 150 | setTextOnUIThread(tv_humidity_max_value, data) 151 | } 152 | 153 | override fun humidityMinimumAvailable(data: String) { 154 | setTextOnUIThread(tv_humidity_min_value, data) 155 | } 156 | 157 | override fun resetRequired() { 158 | activity!!.runOnUiThread { 159 | val valueViews = listOf( 160 | tv_temperature_value, 161 | tv_temperature_max_value, 162 | tv_temperature_min_value, 163 | tv_humidity_value, 164 | tv_humidity_max_value, 165 | tv_humidity_min_value 166 | ) 167 | val defaultText = getString(R.string.default_parameter_value) 168 | for (textView in valueViews) { 169 | textView.text = defaultText 170 | } 171 | } 172 | } 173 | 174 | private fun setTextOnUIThread( 175 | textView: TextView, 176 | text: String 177 | ) { 178 | activity!!.runOnUiThread { 179 | textView.text = text 180 | } 181 | } 182 | 183 | private fun setDeviceName(name: String) { 184 | tv_connected_device_info.text = name 185 | } 186 | 187 | private fun showBtDevicesNotFoundWarning() { 188 | AppDialog.show( 189 | context!!, 190 | R.string.title_warning, 191 | R.string.message_no_available_devices, 192 | R.string.action_exit, 193 | DialogInterface.OnClickListener { _, _ -> 194 | activity!!.finish() 195 | } 196 | ) 197 | } 198 | 199 | private fun initializeButtons() { 200 | btn_reset_monitor.setOnClickListener { 201 | monitorBtConnection!!.send(BtMonitorSignalType.Reset) 202 | } 203 | btn_main_options.setOnClickListener { 204 | showSettings() 205 | } 206 | } 207 | 208 | private fun showSettings() { 209 | val settingsBottomSheet = SettingsBottomSheet() 210 | showBottomSheet(settingsBottomSheet) 211 | } 212 | override fun btDisconnect() { 213 | monitorBtConnection!!.disconnect() 214 | resetRequired() 215 | } 216 | 217 | override fun btOnPause() { 218 | monitorBtConnection!!.handleOnResume() 219 | } 220 | 221 | override fun getLayoutResId() = R.layout.fragment_monitor 222 | 223 | } 224 | -------------------------------------------------------------------------------- /app/src/main/java/com/smlnskgmail/jaman/remotetemperaturecontrol/logic/monitor/api/connection/BtConnection.kt: -------------------------------------------------------------------------------- 1 | package com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.connection 2 | 3 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.monitor.BtMonitorSignalType 4 | 5 | interface BtConnection { 6 | 7 | fun connect() 8 | fun disconnect() 9 | fun handleOnResume() 10 | 11 | fun send(btMonitorSignalType: BtMonitorSignalType) 12 | 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/smlnskgmail/jaman/remotetemperaturecontrol/logic/monitor/api/entities/BtDevice.kt: -------------------------------------------------------------------------------- 1 | package com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.entities 2 | 3 | data class BtDevice( 4 | val name: String, 5 | val address: String, 6 | var isSelected: Boolean = false 7 | ) 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/smlnskgmail/jaman/remotetemperaturecontrol/logic/monitor/api/entities/targets/BtConnectTarget.kt: -------------------------------------------------------------------------------- 1 | package com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.entities.targets 2 | 3 | interface BtConnectTarget { 4 | 5 | fun onBtDeviceSelected( 6 | name: String, 7 | address: String 8 | ) 9 | 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/smlnskgmail/jaman/remotetemperaturecontrol/logic/monitor/api/entities/targets/BtDisconnectTarget.kt: -------------------------------------------------------------------------------- 1 | package com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.entities.targets 2 | 3 | interface BtDisconnectTarget { 4 | 5 | fun btDisconnect() 6 | 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/smlnskgmail/jaman/remotetemperaturecontrol/logic/monitor/api/entities/targets/BtPauseTarget.kt: -------------------------------------------------------------------------------- 1 | package com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.entities.targets 2 | 3 | interface BtPauseTarget { 4 | 5 | fun btOnPause() 6 | 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/smlnskgmail/jaman/remotetemperaturecontrol/logic/monitor/api/monitor/BtMonitor.kt: -------------------------------------------------------------------------------- 1 | package com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.monitor 2 | 3 | interface BtMonitor { 4 | 5 | fun onNewDataAvailable( 6 | signalType: BtMonitorSignalType, 7 | rawData: String 8 | ) 9 | 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/smlnskgmail/jaman/remotetemperaturecontrol/logic/monitor/api/monitor/BtMonitorSignalType.kt: -------------------------------------------------------------------------------- 1 | package com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.monitor 2 | 3 | enum class BtMonitorSignalType { 4 | 5 | Temperature, 6 | TemperatureMinimum, 7 | TemperatureMaximum, 8 | Humidity, 9 | HumidityMinimum, 10 | HumidityMaximum, 11 | Reset, 12 | Other 13 | 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/smlnskgmail/jaman/remotetemperaturecontrol/logic/monitor/api/monitor/BtMonitorTarget.kt: -------------------------------------------------------------------------------- 1 | package com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.monitor 2 | 3 | interface BtMonitorTarget { 4 | 5 | fun temperatureAvailable(data: String) 6 | fun temperatureMaximumAvailable(data: String) 7 | fun temperatureMinimumAvailable(data: String) 8 | fun humidityAvailable(data: String) 9 | fun humidityMaximumAvailable(data: String) 10 | fun humidityMinimumAvailable(data: String) 11 | fun resetRequired() 12 | 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/smlnskgmail/jaman/remotetemperaturecontrol/logic/monitor/impl/debugbt/DebugBtConnection.kt: -------------------------------------------------------------------------------- 1 | package com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.impl.debugbt 2 | 3 | import android.util.Log 4 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.connection.BtConnection 5 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.monitor.BtMonitor 6 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.monitor.BtMonitorSignalType 7 | 8 | class DebugBtConnection( 9 | private val btMonitor: BtMonitor 10 | ) : BtConnection { 11 | 12 | companion object { 13 | 14 | private val temperature = arrayListOf( 15 | "25.3 C", 16 | "24.6 C", 17 | "23.5 C", 18 | "21.2 C", 19 | "22.4 C", 20 | "23.3 C", 21 | "24.7 C", 22 | "25.1 C", 23 | "25.6 C", 24 | "25.7 C" 25 | ) 26 | 27 | private val humidity = arrayListOf( 28 | "78.1 %", 29 | "79.4 %", 30 | "77.3 %", 31 | "77.7 %", 32 | "77.8 %", 33 | "78.4 %", 34 | "79.5 %", 35 | "81.6 %", 36 | "79.4 %", 37 | "77.5 %" 38 | ) 39 | 40 | } 41 | 42 | private var loopCounter = 0 43 | 44 | override fun connect() { 45 | writeLog( 46 | "Debug Monitor: connect()" 47 | ) 48 | while (true) { 49 | @Suppress("MagicNumber") 50 | Thread.sleep(5_000) 51 | btMonitor.onNewDataAvailable( 52 | BtMonitorSignalType.Temperature, 53 | temperature[loopCounter] 54 | ) 55 | btMonitor.onNewDataAvailable( 56 | BtMonitorSignalType.TemperatureMaximum, 57 | "25.7 C" 58 | ) 59 | btMonitor.onNewDataAvailable( 60 | BtMonitorSignalType.TemperatureMinimum, 61 | "21.2 C" 62 | ) 63 | btMonitor.onNewDataAvailable( 64 | BtMonitorSignalType.Humidity, 65 | humidity[loopCounter] 66 | ) 67 | btMonitor.onNewDataAvailable( 68 | BtMonitorSignalType.HumidityMaximum, 69 | "81.6 %" 70 | ) 71 | btMonitor.onNewDataAvailable( 72 | BtMonitorSignalType.HumidityMinimum, 73 | "77.3 %" 74 | ) 75 | if (loopCounter < temperature.size - 1) { 76 | loopCounter++ 77 | } else { 78 | loopCounter = 0 79 | } 80 | } 81 | } 82 | 83 | private fun writeLog(message: String) { 84 | Log.i( 85 | "MONITOR", 86 | message 87 | ) 88 | } 89 | 90 | override fun disconnect() { 91 | writeLog( 92 | "Debug Monitor: disconnect()" 93 | ) 94 | } 95 | 96 | override fun handleOnResume() { 97 | writeLog( 98 | "Debug Monitor: handleOnResume()" 99 | ) 100 | } 101 | 102 | override fun send( 103 | btMonitorSignalType: BtMonitorSignalType 104 | ) { 105 | writeLog( 106 | "Debug Monitor: send(${btMonitorSignalType})" 107 | ) 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /app/src/main/java/com/smlnskgmail/jaman/remotetemperaturecontrol/logic/monitor/impl/debugbt/DebugBtMonitor.kt: -------------------------------------------------------------------------------- 1 | package com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.impl.debugbt 2 | 3 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.monitor.BtMonitor 4 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.monitor.BtMonitorSignalType 5 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.monitor.BtMonitorTarget 6 | 7 | class DebugBtMonitor( 8 | private val btMonitorTarget: BtMonitorTarget 9 | ) : BtMonitor { 10 | 11 | override fun onNewDataAvailable( 12 | signalType: BtMonitorSignalType, 13 | rawData: String 14 | ) { 15 | @Suppress("UseCheckOrError") 16 | when (signalType) { 17 | BtMonitorSignalType.Temperature -> { 18 | btMonitorTarget.temperatureAvailable( 19 | rawData 20 | ) 21 | } 22 | BtMonitorSignalType.TemperatureMaximum -> { 23 | btMonitorTarget.temperatureMaximumAvailable( 24 | rawData 25 | ) 26 | } 27 | BtMonitorSignalType.TemperatureMinimum -> { 28 | btMonitorTarget.temperatureMinimumAvailable( 29 | rawData 30 | ) 31 | } 32 | BtMonitorSignalType.Humidity -> { 33 | btMonitorTarget.humidityAvailable( 34 | rawData 35 | ) 36 | } 37 | BtMonitorSignalType.HumidityMaximum -> { 38 | btMonitorTarget.humidityMaximumAvailable( 39 | rawData 40 | ) 41 | } 42 | BtMonitorSignalType.HumidityMinimum -> { 43 | btMonitorTarget.humidityMinimumAvailable( 44 | rawData 45 | ) 46 | } 47 | else -> throw IllegalStateException( 48 | "DebugMonitor cannot support $signalType!" 49 | ) 50 | } 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/smlnskgmail/jaman/remotetemperaturecontrol/logic/monitor/impl/devicebt/DeviceBtConnection.kt: -------------------------------------------------------------------------------- 1 | package com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.impl.devicebt 2 | 3 | import android.bluetooth.BluetoothAdapter 4 | import android.bluetooth.BluetoothSocket 5 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.connection.BtConnection 6 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.monitor.BtMonitor 7 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.monitor.BtMonitorSignalType 8 | import java.io.InputStream 9 | import java.io.OutputStream 10 | import java.util.* 11 | 12 | class DeviceBtConnection( 13 | btAdapter: BluetoothAdapter, 14 | deviceMacAddress: String, 15 | private val btMonitor: BtMonitor 16 | ) : BtConnection { 17 | 18 | private val btUuid = UUID.fromString( 19 | "00001101-0000-1000-8000-00805F9B34FB" 20 | ) 21 | 22 | private var btSocket: BluetoothSocket? 23 | 24 | private var inputStream: InputStream? = null 25 | private var outputStream: OutputStream? = null 26 | 27 | private var isRunning = false 28 | 29 | init { 30 | val btDevice = btAdapter.getRemoteDevice(deviceMacAddress) 31 | btSocket = btDevice.createRfcommSocketToServiceRecord(btUuid) 32 | } 33 | 34 | private fun inputStreamIsOpen() = inputStream!!.bufferedReader().ready() 35 | 36 | override fun send(btMonitorSignalType: BtMonitorSignalType) { 37 | outputStream!!.write( 38 | DeviceBtDataExtractor.signalOf( 39 | btMonitorSignalType 40 | ).toByteArray() 41 | ) 42 | } 43 | 44 | override fun connect() { 45 | if (!btSocket!!.isConnected) { 46 | btSocket!!.connect() 47 | inputStream = btSocket!!.inputStream 48 | outputStream = btSocket!!.outputStream 49 | } 50 | runRead() 51 | while (canRead()) { 52 | read() 53 | } 54 | } 55 | private fun read() { 56 | if (inputStreamIsOpen()) { 57 | val rawData = inputStream!!.bufferedReader().readLine() 58 | val signalType = DeviceBtDataExtractor.signalType( 59 | rawData 60 | ) 61 | val data = DeviceBtDataExtractor.data( 62 | rawData 63 | ) 64 | btMonitor.onNewDataAvailable(signalType, data) 65 | } 66 | } 67 | 68 | 69 | override fun disconnect() { 70 | if (btSocket!!.isConnected) { 71 | isRunning = false 72 | btSocket!!.close() 73 | inputStream!!.close() 74 | outputStream!!.close() 75 | 76 | btSocket = null 77 | inputStream = null 78 | outputStream = null 79 | } 80 | } 81 | 82 | override fun handleOnResume() { 83 | if (outputStream != null) { 84 | outputStream!!.flush() 85 | } 86 | } 87 | 88 | private fun runRead() { 89 | isRunning = true 90 | } 91 | 92 | private fun canRead() = isRunning 93 | 94 | } 95 | -------------------------------------------------------------------------------- /app/src/main/java/com/smlnskgmail/jaman/remotetemperaturecontrol/logic/monitor/impl/devicebt/DeviceBtDataExtractor.kt: -------------------------------------------------------------------------------- 1 | package com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.impl.devicebt 2 | 3 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.monitor.BtMonitorSignalType 4 | 5 | object DeviceBtDataExtractor { 6 | 7 | fun signalType(rawData: String): BtMonitorSignalType { 8 | if (rawData.isNotEmpty()) { 9 | return fromRawData(rawData) 10 | } 11 | return BtMonitorSignalType.Other 12 | } 13 | 14 | fun data(rawData: String): String { 15 | if (rawData.isEmpty()) { 16 | return "" 17 | } 18 | return rawData.substring(1) 19 | } 20 | 21 | private fun fromRawData(rawData: String): BtMonitorSignalType { 22 | return when (rawData[0].toString()) { 23 | "t" -> BtMonitorSignalType.Temperature 24 | "i" -> BtMonitorSignalType.TemperatureMinimum 25 | "m" -> BtMonitorSignalType.TemperatureMaximum 26 | "h" -> BtMonitorSignalType.Humidity 27 | "q" -> BtMonitorSignalType.HumidityMinimum 28 | "w" -> BtMonitorSignalType.HumidityMaximum 29 | "r" -> BtMonitorSignalType.Reset 30 | else -> BtMonitorSignalType.Other 31 | } 32 | } 33 | 34 | fun signalOf( 35 | btMonitorSignalType: BtMonitorSignalType 36 | ): String { 37 | return when (btMonitorSignalType) { 38 | BtMonitorSignalType.Temperature -> "t" 39 | BtMonitorSignalType.TemperatureMinimum -> "i" 40 | BtMonitorSignalType.TemperatureMaximum -> "m" 41 | BtMonitorSignalType.Humidity -> "h" 42 | BtMonitorSignalType.HumidityMinimum -> "q" 43 | BtMonitorSignalType.HumidityMaximum -> "w" 44 | BtMonitorSignalType.Reset -> "r" 45 | else -> "" 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/smlnskgmail/jaman/remotetemperaturecontrol/logic/monitor/impl/devicebt/DeviceBtMonitor.kt: -------------------------------------------------------------------------------- 1 | package com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.impl.devicebt 2 | 3 | import android.annotation.SuppressLint 4 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.monitor.BtMonitor 5 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.monitor.BtMonitorSignalType 6 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.monitor.BtMonitorTarget 7 | 8 | class DeviceBtMonitor( 9 | private val btMonitorTarget: BtMonitorTarget 10 | ) : BtMonitor { 11 | 12 | @Suppress("NON_EXHAUSTIVE_WHEN") 13 | override fun onNewDataAvailable( 14 | signalType: BtMonitorSignalType, 15 | rawData: String 16 | ) { 17 | when(signalType) { 18 | BtMonitorSignalType.Temperature -> { 19 | btMonitorTarget.temperatureAvailable( 20 | temperatureResult(rawData) 21 | ) 22 | } 23 | BtMonitorSignalType.TemperatureMaximum -> { 24 | btMonitorTarget.temperatureMaximumAvailable( 25 | temperatureResult(rawData) 26 | ) 27 | } 28 | BtMonitorSignalType.TemperatureMinimum -> { 29 | btMonitorTarget.temperatureMinimumAvailable( 30 | temperatureResult(rawData) 31 | ) 32 | } 33 | BtMonitorSignalType.Humidity -> { 34 | btMonitorTarget.humidityAvailable( 35 | humidityResult(rawData) 36 | ) 37 | } 38 | BtMonitorSignalType.HumidityMaximum -> { 39 | btMonitorTarget.humidityMaximumAvailable( 40 | humidityResult(rawData) 41 | ) 42 | } 43 | BtMonitorSignalType.HumidityMinimum -> { 44 | btMonitorTarget.humidityMinimumAvailable( 45 | humidityResult(rawData) 46 | ) 47 | } 48 | BtMonitorSignalType.Reset -> { 49 | btMonitorTarget.resetRequired() 50 | } 51 | } 52 | } 53 | 54 | private fun temperatureResult(rawData: String) 55 | = formattedResult(rawData, "C") 56 | 57 | private fun humidityResult(rawData: String) 58 | = formattedResult(rawData, "%") 59 | 60 | @SuppressLint("SetTextI18n") 61 | private fun formattedResult( 62 | rawData: String, 63 | measure: String 64 | ) = "$rawData $measure" 65 | 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/smlnskgmail/jaman/remotetemperaturecontrol/logic/settings/SettingsBottomSheet.kt: -------------------------------------------------------------------------------- 1 | package com.smlnskgmail.jaman.remotetemperaturecontrol.logic.settings 2 | 3 | import com.smlnskgmail.jaman.remotetemperaturecontrol.R 4 | import com.smlnskgmail.jaman.remotetemperaturecontrol.components.BaseBottomSheet 5 | import com.smlnskgmail.jaman.remotetemperaturecontrol.logic.monitor.api.entities.targets.BtDisconnectTarget 6 | import kotlinx.android.synthetic.main.bottom_sheet_settings.* 7 | 8 | class SettingsBottomSheet : BaseBottomSheet() { 9 | 10 | override fun initialize() { 11 | settings_disconnect.setOnClickListener { 12 | actionAndDismiss { 13 | (activity as BtDisconnectTarget).btDisconnect() 14 | } 15 | } 16 | } 17 | 18 | private fun actionAndDismiss(action: () -> Unit) { 19 | dismiss() 20 | action() 21 | } 22 | 23 | override fun getLayoutResId() = R.layout.bottom_sheet_settings 24 | 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_bottom_sheet.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_item_selected.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 13 | 15 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/bottom_sheet_bt_devices.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 |