├── mhflasher
├── .gitignore
├── src
│ ├── main
│ │ ├── MHFlasher2.png
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── themes.xml
│ │ │ │ └── colors.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ ├── ic_launcher_round.webp
│ │ │ │ └── ic_launcher_foreground.webp
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ ├── ic_launcher_round.webp
│ │ │ │ └── ic_launcher_foreground.webp
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ ├── ic_launcher_round.webp
│ │ │ │ └── ic_launcher_foreground.webp
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ ├── ic_launcher_round.webp
│ │ │ │ └── ic_launcher_foreground.webp
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ ├── ic_launcher_round.webp
│ │ │ │ └── ic_launcher_foreground.webp
│ │ │ ├── raw
│ │ │ │ └── openbl602_1_17_452_xz_ota.bin
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ └── drawable
│ │ │ │ ├── ic_launcher_foreground.xml
│ │ │ │ └── ic_launcher_background.xml
│ │ ├── ic_launcher-playstore.png
│ │ ├── assets
│ │ │ ├── OpenBL602_1.17.551_OTA.bin.xz.ota
│ │ │ └── OpenBL602_1.17.551_OTA.bin.xz.ota_cutted
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── lance3
│ │ │ │ └── mhflasher
│ │ │ │ ├── ui
│ │ │ │ └── theme
│ │ │ │ │ ├── Color.kt
│ │ │ │ │ ├── Type.kt
│ │ │ │ │ └── Theme.kt
│ │ │ │ ├── LogViewModel.kt
│ │ │ │ ├── ApViewModel.kt
│ │ │ │ └── MainActivity.kt
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── lance3
│ │ │ └── mhflasher
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── lance3
│ │ └── mhflasher
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle.kts
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── img
└── Screenshot_20240422-001651_mhflasher.png
├── reverse_engineer
├── README.md
├── at_commands.txt
└── ota_cutted.log
├── settings.gradle.kts
├── gradle.properties
├── .gitignore
├── gradlew.bat
├── README.md
└── gradlew
/mhflasher/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kruzer/mhflasher/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/mhflasher/src/main/MHFlasher2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kruzer/mhflasher/HEAD/mhflasher/src/main/MHFlasher2.png
--------------------------------------------------------------------------------
/mhflasher/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | mhflasher
3 |
--------------------------------------------------------------------------------
/img/Screenshot_20240422-001651_mhflasher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kruzer/mhflasher/HEAD/img/Screenshot_20240422-001651_mhflasher.png
--------------------------------------------------------------------------------
/mhflasher/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kruzer/mhflasher/HEAD/mhflasher/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/mhflasher/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kruzer/mhflasher/HEAD/mhflasher/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/mhflasher/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kruzer/mhflasher/HEAD/mhflasher/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/mhflasher/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kruzer/mhflasher/HEAD/mhflasher/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/mhflasher/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kruzer/mhflasher/HEAD/mhflasher/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/mhflasher/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kruzer/mhflasher/HEAD/mhflasher/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/mhflasher/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kruzer/mhflasher/HEAD/mhflasher/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/mhflasher/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kruzer/mhflasher/HEAD/mhflasher/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/mhflasher/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kruzer/mhflasher/HEAD/mhflasher/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/mhflasher/src/main/res/raw/openbl602_1_17_452_xz_ota.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kruzer/mhflasher/HEAD/mhflasher/src/main/res/raw/openbl602_1_17_452_xz_ota.bin
--------------------------------------------------------------------------------
/mhflasher/src/main/assets/OpenBL602_1.17.551_OTA.bin.xz.ota:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kruzer/mhflasher/HEAD/mhflasher/src/main/assets/OpenBL602_1.17.551_OTA.bin.xz.ota
--------------------------------------------------------------------------------
/mhflasher/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kruzer/mhflasher/HEAD/mhflasher/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/mhflasher/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kruzer/mhflasher/HEAD/mhflasher/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/mhflasher/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kruzer/mhflasher/HEAD/mhflasher/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/mhflasher/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kruzer/mhflasher/HEAD/mhflasher/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/mhflasher/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kruzer/mhflasher/HEAD/mhflasher/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/reverse_engineer/README.md:
--------------------------------------------------------------------------------
1 | ### Device logs
2 |
3 | #### Succesfull flash log
4 |
5 | #### Rejected flash log (wrong checksum)
6 |
7 | ### Open ports
8 |
9 | ### AT Commands
--------------------------------------------------------------------------------
/mhflasher/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kruzer/mhflasher/HEAD/mhflasher/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/mhflasher/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kruzer/mhflasher/HEAD/mhflasher/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/mhflasher/src/main/assets/OpenBL602_1.17.551_OTA.bin.xz.ota_cutted:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kruzer/mhflasher/HEAD/mhflasher/src/main/assets/OpenBL602_1.17.551_OTA.bin.xz.ota_cutted
--------------------------------------------------------------------------------
/mhflasher/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Jan 28 23:39:36 CET 2024
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/mhflasher/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/mhflasher/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/mhflasher/src/main/java/com/lance3/mhflasher/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package com.lance3.mhflasher.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val Purple80 = Color(0xFFD0BCFF)
6 | val PurpleGrey80 = Color(0xFFCCC2DC)
7 | val Pink80 = Color(0xFFEFB8C8)
8 |
9 | val Purple40 = Color(0xFF6650a4)
10 | val PurpleGrey40 = Color(0xFF625b71)
11 | val Pink40 = Color(0xFF7D5260)
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | gradlePluginPortal()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 |
16 | rootProject.name = "MagicHomeFlasher"
17 | include(":mhflasher")
18 |
--------------------------------------------------------------------------------
/mhflasher/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/mhflasher/src/test/java/com/lance3/mhflasher/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.lance3.mhflasher
2 |
3 | import org.junit.Assert.assertEquals
4 | import org.junit.Test
5 |
6 | /**
7 | * Example local unit test, which will execute on the development machine (host).
8 | *
9 | * See [testing documentation](http://d.android.com/tools/testing).
10 | */
11 | class ExampleUnitTest {
12 | @Test
13 | fun addition_isCorrect() {
14 | assertEquals(4, 2 + 2)
15 | }
16 | }
--------------------------------------------------------------------------------
/reverse_engineer/at_commands.txt:
--------------------------------------------------------------------------------
1 | AT+RELD\r
2 | AT+LVER
3 | HF-A11ASSISTHREAD
4 | AT+MACID\r
5 | AT+TCPLKB\r
6 | AT+KVER\r
7 | AT+RSCOUNT\r
8 | AT+SOCKB\r
9 | AT+SOCKB=NONE\r
10 | AT+TCPLKB\r
11 | AT+ENTRY_TABLE=
12 | AT+FRTIME
13 | AT+FUNCTEST
14 | AT+KVER
15 | AT+LVER
16 | AT+MACID
17 | AT+PUP=
18 | AT+PUP:wifi_password:%s(%d)
19 | AT+PUP:wifi_ssid:%s(%d)
20 | AT+RELD
21 | AT+RSCOUNT
22 | AT+RSSI
23 | AT+SOCKB
24 | AT+SOCKB=NONE
25 | AT+SOCKB=TCP
26 | AT+TCPERROR
27 | AT+TCPLKB
28 | AT+UPURL=
29 | AT+WAKEY
30 | AT+WAKEY=
31 | AT+WAP
32 | AT+WAP=
33 | AT+WMODE
34 | AT+WMODE=AP
35 | AT+WMODE=STA
36 | AT+WSCAN
37 | AT+WSKEY
38 | AT+WSKEY=
39 | AT+WSLK
40 | AT+WSSSID
41 | AT+WSSSID=
42 | AT+Z
--------------------------------------------------------------------------------
/mhflasher/src/main/java/com/lance3/mhflasher/LogViewModel.kt:
--------------------------------------------------------------------------------
1 | import android.util.Log
2 | import androidx.compose.runtime.mutableStateOf
3 | import androidx.lifecycle.ViewModel
4 | import java.time.LocalTime
5 | import java.time.format.DateTimeFormatter
6 |
7 | class LogViewModel : ViewModel() {
8 | var logText = mutableStateOf("")
9 | fun addLog(message: String) {
10 | val currentTime = LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))
11 | val newMessage =
12 | if (logText.value.isEmpty()) "[$currentTime] $message" else "${logText.value}\n[$currentTime] $message"
13 | logText.value = newMessage
14 | }
15 |
16 | fun clearLog() {
17 | logText.value = ""
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/mhflasher/src/androidTest/java/com/lance3/mhflasher/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.lance3.mhflasher
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import org.junit.Assert.*
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | /**
10 | * Instrumented test, which will execute on an Android device.
11 | *
12 | * See [testing documentation](http://d.android.com/tools/testing).
13 | */
14 | @RunWith(AndroidJUnit4::class)
15 | class ExampleInstrumentedTest {
16 | @Test
17 | fun useAppContext() {
18 | // Context of the app under test.
19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
20 | assertEquals("com.lance3.mhflasher", appContext.packageName)
21 | }
22 | }
--------------------------------------------------------------------------------
/mhflasher/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/mhflasher/src/main/java/com/lance3/mhflasher/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package com.lance3.mhflasher.ui.theme
2 |
3 | import androidx.compose.material3.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 |
9 | // Set of Material typography styles to start with
10 | val Typography = Typography(
11 | bodyLarge = TextStyle(
12 | fontFamily = FontFamily.Default,
13 | fontWeight = FontWeight.Normal,
14 | fontSize = 16.sp,
15 | lineHeight = 24.sp,
16 | letterSpacing = 0.5.sp
17 | )
18 | /* Other default text styles to override
19 | titleLarge = TextStyle(
20 | fontFamily = FontFamily.Default,
21 | fontWeight = FontWeight.Normal,
22 | fontSize = 22.sp,
23 | lineHeight = 28.sp,
24 | letterSpacing = 0.sp
25 | ),
26 | labelSmall = TextStyle(
27 | fontFamily = FontFamily.Default,
28 | fontWeight = FontWeight.Medium,
29 | fontSize = 11.sp,
30 | lineHeight = 16.sp,
31 | letterSpacing = 0.5.sp
32 | )
33 | */
34 | )
--------------------------------------------------------------------------------
/mhflasher/src/main/java/com/lance3/mhflasher/ApViewModel.kt:
--------------------------------------------------------------------------------
1 | import android.Manifest
2 | import android.content.BroadcastReceiver
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.content.IntentFilter
6 | import android.content.pm.PackageManager
7 | import android.net.wifi.WifiManager
8 | import android.os.Build
9 | import android.widget.Toast
10 | import androidx.annotation.RequiresApi
11 | import androidx.compose.runtime.mutableStateListOf
12 | import androidx.compose.runtime.mutableStateOf
13 | import androidx.core.app.ActivityCompat
14 | import androidx.lifecycle.ViewModel
15 |
16 | class ApViewModel : ViewModel() {
17 | val accessPoints = mutableStateListOf()
18 | var isConnected = mutableStateOf(false)
19 | var connectedAP = mutableStateOf("")
20 | var macAddress = mutableStateOf("")
21 | var deviceId = mutableStateOf("")
22 | var firmwareVersion = mutableStateOf("")
23 | var localIP = mutableStateOf("")
24 | var remoteIP = mutableStateOf("")
25 | var flashProgress = mutableStateOf(0.0f)
26 | fun addAccessPoint(ap: WifiAP) {
27 | accessPoints.add(ap)
28 | }
29 | fun clearAccessPoints() {
30 | accessPoints.clear()
31 | }
32 |
33 | }
34 |
35 | data class WifiAP(val name: String, val rssi: Int)
36 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/mhflasher/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
19 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/mhflasher/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.aar
4 | *.ap_
5 | *.aab
6 | # Files for the ART/Dalvik VM
7 | *.dex
8 | # Java class files
9 | *.class
10 | # Generated files
11 | bin/
12 | gen/
13 | out/
14 | # App Release Files
15 | app/release/*
16 | mhflasher/release/*
17 |
18 | # Uncomment the following line in case you need and you don't have the release build type files in your app
19 | # release/
20 | # Gradle files
21 | .gradle/
22 | build/
23 | # Local configuration file (sdk path, etc)
24 | local.properties
25 | # Proguard folder generated by Eclipse
26 | proguard/
27 | # Log Files
28 | *.log
29 | # Android Studio Navigation editor temp files
30 | .navigation/
31 | # Android Studio captures folder
32 | captures/
33 | # IntelliJ
34 | *.iml
35 | .idea/
36 | # .idea/workspace.xml
37 | # .idea/tasks.xml
38 | # .idea/gradle.xml
39 | # .idea/assetWizardSettings.xml
40 | # .idea/dictionaries
41 | .idea/libraries
42 | # Android Studio 3 in .gitignore file.
43 | .idea/caches
44 | .idea/modules.xml
45 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you
46 | .idea/navEditor.xml
47 | # Keystore files
48 | # Uncomment the following lines if you do not want to check your keystore files in.
49 | *.jks
50 | *.keystore
51 | # External native build folder generated in Android Studio 2.2 and later
52 | .externalNativeBuild
53 | .cxx/
54 | # Google Services (e.g. APIs or Firebase)
55 | google-services.json
56 | # Freeline
57 | freeline.py
58 | freeline/
59 | freeline_project_description.json
60 | # fastlane
61 | fastlane/report.xml
62 | fastlane/Preview.html
63 | fastlane/screenshots
64 | fastlane/test_output
65 | fastlane/readme.md
66 | # Version control
67 | vcs.xml
68 | # lint
69 | lint/intermediates/
70 | lint/generated/
71 | lint/outputs/
72 | lint/tmp/
73 | # lint/reports/
74 | # MacOS
75 | .DS_Store
76 | # App Specific cases
77 | app/release/output.json
78 | .idea/codeStyles/
79 |
--------------------------------------------------------------------------------
/mhflasher/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.application")
3 | id("org.jetbrains.kotlin.android")
4 | }
5 |
6 | android {
7 | namespace = "com.lance3.mhflasher"
8 | compileSdk = 34
9 |
10 | defaultConfig {
11 | applicationId = "com.lance3.mhflasher"
12 | minSdk = 29
13 | targetSdk = 34
14 | versionCode = 2
15 | versionName = "0.2"
16 |
17 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
18 | vectorDrawables {
19 | useSupportLibrary = true
20 | }
21 | }
22 |
23 | buildTypes {
24 | release {
25 | isMinifyEnabled = false
26 | proguardFiles(
27 | getDefaultProguardFile("proguard-android-optimize.txt"),
28 | "proguard-rules.pro"
29 | )
30 | }
31 | }
32 | compileOptions {
33 | sourceCompatibility = JavaVersion.VERSION_1_8
34 | targetCompatibility = JavaVersion.VERSION_1_8
35 | }
36 | kotlinOptions {
37 | jvmTarget = "1.8"
38 | }
39 | buildFeatures {
40 | compose = true
41 | }
42 | composeOptions {
43 | kotlinCompilerExtensionVersion = "1.5.8"
44 | }
45 | packaging {
46 | resources {
47 | excludes += "/META-INF/{AL2.0,LGPL2.1}"
48 | }
49 | }
50 | }
51 |
52 | dependencies {
53 |
54 | implementation("androidx.core:core-ktx:1.12.0")
55 | implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
56 | implementation("androidx.activity:activity-compose:1.8.2")
57 | implementation(platform("androidx.compose:compose-bom:2024.01.00"))
58 | implementation("androidx.compose.ui:ui")
59 | implementation("androidx.compose.ui:ui-graphics")
60 | implementation("androidx.compose.ui:ui-tooling-preview")
61 | implementation("androidx.compose.material3:material3")
62 | testImplementation("junit:junit:4.13.2")
63 | androidTestImplementation("androidx.test.ext:junit:1.1.5")
64 | androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
65 | androidTestImplementation(platform("androidx.compose:compose-bom:2024.01.00"))
66 | androidTestImplementation("androidx.compose.ui:ui-test-junit4")
67 | debugImplementation("androidx.compose.ui:ui-tooling")
68 | debugImplementation("androidx.compose.ui:ui-test-manifest")
69 | }
--------------------------------------------------------------------------------
/mhflasher/src/main/java/com/lance3/mhflasher/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package com.lance3.mhflasher.ui.theme
2 |
3 | import android.app.Activity
4 | import android.os.Build
5 | import androidx.compose.foundation.isSystemInDarkTheme
6 | import androidx.compose.material3.MaterialTheme
7 | import androidx.compose.material3.darkColorScheme
8 | import androidx.compose.material3.dynamicDarkColorScheme
9 | import androidx.compose.material3.dynamicLightColorScheme
10 | import androidx.compose.material3.lightColorScheme
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.SideEffect
13 | import androidx.compose.ui.graphics.toArgb
14 | import androidx.compose.ui.platform.LocalContext
15 | import androidx.compose.ui.platform.LocalView
16 | import androidx.core.view.WindowCompat
17 |
18 | private val DarkColorScheme = darkColorScheme(
19 | primary = Purple80,
20 | secondary = PurpleGrey80,
21 | tertiary = Pink80
22 | )
23 |
24 | private val LightColorScheme = lightColorScheme(
25 | primary = Purple40,
26 | secondary = PurpleGrey40,
27 | tertiary = Pink40
28 |
29 | /* Other default colors to override
30 | background = Color(0xFFFFFBFE),
31 | surface = Color(0xFFFFFBFE),
32 | onPrimary = Color.White,
33 | onSecondary = Color.White,
34 | onTertiary = Color.White,
35 | onBackground = Color(0xFF1C1B1F),
36 | onSurface = Color(0xFF1C1B1F),
37 | */
38 | )
39 |
40 | @Composable
41 | fun MagicHomeFlasherTheme(
42 | darkTheme: Boolean = isSystemInDarkTheme(),
43 | // Dynamic color is available on Android 12+
44 | dynamicColor: Boolean = true,
45 | content: @Composable () -> Unit
46 | ) {
47 | val colorScheme = when {
48 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
49 | val context = LocalContext.current
50 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
51 | }
52 |
53 | darkTheme -> DarkColorScheme
54 | else -> LightColorScheme
55 | }
56 | val view = LocalView.current
57 | if (!view.isInEditMode) {
58 | SideEffect {
59 | val window = (view.context as Activity).window
60 | window.statusBarColor = colorScheme.primary.toArgb()
61 | WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
62 | }
63 | }
64 |
65 | MaterialTheme(
66 | colorScheme = colorScheme,
67 | typography = Typography,
68 | content = content
69 | )
70 | }
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/reverse_engineer/ota_cutted.log:
--------------------------------------------------------------------------------
1 | UDP recv IP:10.10.123.4
2 | UDP recv Port:56639
3 | HF-A11ASSISTHREAD
4 |
5 | fetch mac [B4E842290716]
6 | UDP(sk 1) send status 37 bytes
7 | UDP recv IP:10.10.123.4
8 | UDP recv Port:41741
9 | AT+LVER
10 |
11 | UDP(sk 1) send status 25 bytes
12 | UDP recv IP:10.10.123.4
13 | UDP recv Port:57714
14 | AT+UPURL=http://10.10.123.4:1111/update?version=33_48_20240418_OpenBeken&beta,pierogi
15 | UDP(sk 1) send status 4 bytes
16 | OTA fech code :pierogi(7)
17 | fetch host : (11) 10.10.123.4 1111
18 | feth path : (45) /update?version=33_48_20240418_OpenBeken&beta
19 |
20 | device .......... ota_device_beta : |1|
21 | device .......... ota_device_type : |33|
22 | device .......... flash_device_type : |33|
23 | device .......... ota_manufacturer_id : |OpenBeken|
24 | device .......... flash_manufacturer_id : |ZG-BL|
25 |
26 | ota: g_timeout_timer = start
27 | wait for watchdog stop success
28 |
29 | fetch mac [B4E842290716]
30 | origin_code: pierogi
31 | encry_code: 82CC963255
32 | fota url:http://10.10.123.4:1111/update?version=33_48_20240418_OpenBeken&beta&filename=FW_OTA.bin.ota&checkcode={82CC963255}&mac=B4E842290716(132)
33 | ota host name:10.10.123.4(11):1111 path:/update?version=33_48_20240418_OpenBeken&beta&filename=FW_OTA.bin.ota&checkcode={82CC963255}&mac=B4E842290716
34 | zg_ota_task
35 | [HTTPC] hdr_len is 43, content_len is 409600
36 | [MTD] >>>>>> Hanlde info Dump >>>>>>
37 | name FW
38 | id 0
39 | offset 0x000d8000(884736)
40 | size 0x000b7000(732Kbytes)
41 | xip_addr 0x00000000
42 | [MTD] <<<<<< Hanlde info End <<<<<<
43 | otaTotalLength : 409600, bin_size : 749568
44 | [HTTPC] Received 645 Bytes
45 | otaoffset : 645
46 | [OTA] [HEADER] ota header is BL60X_OTA_Ver1.0
47 | [OTA] [HEADER] file type is XZ
48 | [OTA] [HEADER] file length (exclude ota header) is 426776
49 | [OTA] [HEADER] ver_hardware is BFL_Module_v1.1
50 | [OTA] [HEADER] ver_software is EVENT_V1.1.1
51 | [OTA] [HEADER] sha256 is 12B2B062A3861B058A112B4440F3C00C199A4A4C1ACB28C0DC3A6DC6117F0312
52 | [HTTPC] Received 688 Bytes
53 | otaoffset : 821
54 | Write 821[409600]
55 | [HTTPC] Received 688 Bytes
56 | otaoffset : 688
57 | Write 1509[409600]
58 | [HTTPC] Received 688 Bytes
59 | otaoffset : 688
60 | Write 2197[409600]
61 | ota process begin, exit tcp server task.
62 |
63 | [HTTPC] Received 688 Bytes
64 | otaoffset : 688
65 | Write 2885[409600]
66 | [HTTPC] Received 688 Bytes
67 | otaoffset : 688
68 | Write 3573[409600]
69 | [HTTPC] Received 688 Bytes
70 | otaoffset : 688
71 | Write 4261[409600]
72 | [HTTPC] Received 688 Bytes
73 | otaoffset : 688
74 | Write 4949[409600]
75 | UDP(sk 1) send status 7 bytes
76 | event:ota process
77 | UDP(sk 1) send status 7 bytes
78 | [HTTPC] Received 688 Bytes
79 | otaoffset : 688
80 | Write 5637[409600]
81 | [HTTPC] Received 688 Bytes
82 | otaoffset : 688
83 | -----------------> AABA Request:
84 | A-MSDU: Not Permitted
85 | Block Ack Policy: Immediate Block Ack
86 | TID: 7
87 | Number of Buffers: 64
88 | -----------------> AABA Response:
89 | A-MSDU: Not Permitted
90 | Block Ack PolicWrite 6325[409600]
91 | y: Immediate Block Ack
92 | TID: 7
93 | Number of Buffers: 8
94 | event:ota process
95 | Light:LED mode -> 61
96 |
97 | record dynamic br 100
98 | light_set_color (255->0) (0->0) (0->0)
99 | [HTTPC] Received 688 Bytes
100 | otaoffset : 688
101 | Write 7013[409600]
102 | green take time 10 ms 0
103 | blue take time 10 ms 0
104 | [HTTPC] Received 688 Bytes
105 | otaoffset : 688
106 | Write 7701[409600]
107 | [HTTPC] Received 688 Bytes
108 | otaoffset : 688
109 | Write 8389[409600]
110 | [HTTPC] Received 688 Bytes
111 | otaoffset : 688
112 | Write 9077[409600]
113 |
114 | ...
115 |
116 | Write 408805[409600]
117 | [HTTPC] Received 283 Bytes
118 | otaoffset : 283
119 | Write 409088[409600]
120 |
121 | Calculated SHA256 Checksum:F1E973BF45CF1D2F59D71FC05BAC66EA9641A308A096CC894B8E8B1CE2D1D07B
122 | Header SET SHA256 Checksum:12B2B062A3861B058A112B4440F3C00C199A4A4C1ACB28C0DC3A6DC6117F0312
123 | [OTA] [TCP] SHA256 NOT Correct
124 | [OTA] [TCP] prepare OTA partition info
125 | [OTA] [TCP] Update PARTITION, partition len is 409088
126 | [OTA] [TCP] Rebooting
127 | [HTTPC] Transfer finished. rx_content_len is 409600
128 | ota download is done!
129 | ota:push success event
130 | event:ota success
131 | UDP(sk 1) send status 15 bytes
132 | Light:LED mode -> 61
133 |
134 | light_set_color (213->0) (213->0) (213->255)
135 | light record color (0 0 255)
136 | red take time 500 ms 0
137 | green take time 500 ms 0
138 | blue take time 500 ms 255
139 | light save : R 0 G 0 B 255
140 | zj_userdata_write key=KEY_COLOR(3)
141 | (report):red [56] -> [0]
142 | (report):green [30] -> [0]
143 | (report):blue [31] -> [255]
144 | ___________local report__________
145 | Starting bl602 now....
146 | Booting BL602 Chip...
147 |
--------------------------------------------------------------------------------
/mhflasher/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
10 |
12 |
14 |
16 |
18 |
20 |
22 |
24 |
26 |
28 |
30 |
32 |
34 |
36 |
38 |
40 |
42 |
44 |
46 |
48 |
50 |
52 |
54 |
56 |
58 |
60 |
62 |
64 |
66 |
68 |
70 |
72 |
74 |
75 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Magic Home Flasher (BL602)
2 |
3 | ## Introduction
4 | Magic Home Flasher is an Android application designed to flash Magic Home devices equipped with a RISC-V BL602 chip using the OpenBeken firmware available from the [OpenBeken_App project on GitHub](https://github.com/openshwprojects/OpenBK7231T_App). This tool simplifies the process of updating devices to use open-source firmware, enhancing their functionality and customizability.
5 |
6 | ## Features
7 | - **Simple Device Updating**: Select and flash devices with new firmware directly over the air. No soldering required
8 | - **Open Source**: Leverage and contribute to the open-source community.
9 |
10 | ## Getting Started
11 | These instructions will guide you through the setup and operation of Magic Home Flasher.
12 |
13 | ### Prerequisites
14 | - An Android device running Android 29 or higher.
15 | - A Magic Home device with a BL602 chip within range.
16 |
17 | ### Installation
18 | 1. Download the latest release of Magic Home Flasher from the [Releases](https://github.com/kruzer/mhflasher/releases) section on GitHub.
19 | 2. Install the APK on your Android device. You may need to enable installation from unknown sources in your device settings.
20 |
21 | ### Usage
22 | 1. Open the Magic Home Flasher app.
23 | 2. Tap on the **Scan Wifi Devices** button to discover available Wi-Fi networks.
24 | 3. Look for networks named like `LEDnetXXXXXX`. These are typically broadcast by Magic Home devices.
25 | 4. Select the network corresponding to the device you wish to flash.
26 | 5. The app will automatically handle the OTA update process by serving the firmware over an internal HTTP server.
27 | 6. Once the device downloads and applies the firmware, it will reboot automatically, running the new OpenBeken system.
28 |
29 |
30 |
31 | ## Firmware
32 | The firmware used by Magic Home Flasher is provided by the OpenBK7231T_App project. You can find more information and contribute to the firmware development at [OpenBeken_App GitHub Repository](https://github.com/openshwprojects/OpenBK7231T_App).
33 |
34 | ## Reverse engineer risc-v chip
35 | All info is based on the process of a reverse engineering the risc-v Bouffalo Labs chip bl602 described [here](reverse_engineer/README.md)
36 | You can use a shell commands to achieve similar result:
37 | serve http file on port 1111:
38 | ```shell
39 | {
40 | echo -ne "HTTP/1.0 200 OK\r\nContent-Length: "$(wc -c < OpenBL602_1.17.551_OTA.bin.xz.ota)"\r\n\r\n"
41 | cat OpenBL602_1.17.551_OTA.bin.xz.ota
42 | } | nc -l 1111
43 | ```
44 | invoke flashing process (from another terminal):
45 | ```shell
46 | echo -e "AT+UPURL=http://10.10.123.4:1111/update?version=33_00_20240418_OpenBeken&beta,pierogi" | nc -u 10.10.123.3 48899
47 | ```
48 | List of all AT commands recognized by firmware is available [here](reverse_engineer/at_commands.txt)
49 | For example to check version before flashing use:
50 | ```shell
51 | echo -e "AT+LVER\r" | nc -u 10.10.123.3 48899
52 | ```
53 | ## TODO
54 |
55 | The following features are planned for future releases of Magic Home Flasher to enhance its functionality and user experience:
56 |
57 | - **Wi-Fi Settings Configuration**: Add the capability to change the SSID and password of the Magic Home device directly from the app. This feature will allow users to manage their device's network settings easily without needing additional tools.
58 |
59 | - **Automatic Firmware Updates**: Integrate functionality to automatically fetch the latest version of OpenBeken firmware from the GitHub repository. This will ensure that users always have access to the latest features and security updates without manually checking for new releases.
60 |
61 | These enhancements aim to streamline the user experience and expand the functionality of Magic Home Flasher, making it a more robust and convenient tool for updating Magic Home devices.
62 |
63 | ## Contributing
64 | Contributions to Magic Home Flasher are welcome! Whether it's reporting issues, submitting fixes, or proposing new features, your help is appreciated.
65 |
66 | ### How to Contribute
67 | 1. Fork the repository.
68 | 2. Create your feature branch (`git checkout -b feature/AmazingFeature`).
69 | 3. Commit your changes (`git commit -m 'Add some AmazingFeature'`).
70 | 4. Push to the branch (`git push origin feature/AmazingFeature`).
71 | 5. Open a new Pull Request.
72 |
73 | ## License
74 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details.
75 |
76 | ## Acknowledgments
77 | - Thanks to the OpenBK7231T_App project for providing the firmware.
78 | - Community contributions that help improve this tool.
79 |
80 | ## Support
81 | For support, open an issue in the GitHub repository.
82 |
83 | >[!Warning]
84 | >Flashing non-original firmware on your device carries inherent risks. By proceeding with the installation of any non-original firmware, you acknowledge and accept that such actions may potentially brick or permanently damage your device. Please be aware that installing firmware that has not been officially released or endorsed by the manufacturer voids any warranties and releases the manufacturer from any liabilities related to device performance or failure.
85 | >
86 | >**Proceed at your own risk**. It is highly recommended that you thoroughly review the firmware documentation and understand the flashing process before attempting any modifications. The authors of this tool or firmware are not responsible for any damages or losses that may occur from the use of this software.
87 | ---
88 | For more information on how to use Magic Home Flasher, please refer to the wiki or contact support.
89 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/mhflasher/src/main/java/com/lance3/mhflasher/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.lance3.mhflasher
2 |
3 | import ApViewModel
4 | import LogViewModel
5 | import WifiAP
6 | import android.Manifest
7 | import android.content.BroadcastReceiver
8 | import android.content.Context
9 | import android.content.Intent
10 | import android.content.IntentFilter
11 | import android.content.pm.PackageManager
12 | import android.net.ConnectivityManager
13 | import android.net.LinkProperties
14 | import android.net.Network
15 | import android.net.NetworkCapabilities
16 | import android.net.NetworkRequest
17 | import android.net.wifi.ScanResult
18 | import android.net.wifi.WifiManager
19 | import android.net.wifi.WifiNetworkSpecifier
20 | import android.os.Build
21 | import android.os.Bundle
22 | import android.util.Log
23 | import android.widget.Toast
24 | import androidx.activity.ComponentActivity
25 | import androidx.activity.compose.setContent
26 | import androidx.annotation.RequiresApi
27 | import androidx.compose.foundation.clickable
28 | import androidx.compose.foundation.layout.Arrangement
29 | import androidx.compose.foundation.layout.Column
30 | import androidx.compose.foundation.layout.Row
31 | import androidx.compose.foundation.layout.Spacer
32 | import androidx.compose.foundation.layout.fillMaxSize
33 | import androidx.compose.foundation.layout.fillMaxWidth
34 | import androidx.compose.foundation.layout.height
35 | import androidx.compose.foundation.layout.padding
36 | import androidx.compose.foundation.layout.width
37 | import androidx.compose.foundation.lazy.LazyColumn
38 | import androidx.compose.foundation.lazy.items
39 | import androidx.compose.foundation.text.BasicTextField
40 | import androidx.compose.foundation.text.KeyboardOptions
41 | import androidx.compose.material3.Button
42 | import androidx.compose.material3.LinearProgressIndicator
43 | import androidx.compose.material3.MaterialTheme
44 | import androidx.compose.material3.OutlinedTextField
45 | import androidx.compose.material3.Tab
46 | import androidx.compose.material3.TabRow
47 | import androidx.compose.material3.Text
48 | import androidx.compose.material3.TextFieldDefaults
49 | import androidx.compose.runtime.Composable
50 | import androidx.compose.runtime.getValue
51 | import androidx.compose.runtime.mutableIntStateOf
52 | import androidx.compose.runtime.mutableStateOf
53 | import androidx.compose.runtime.remember
54 | import androidx.compose.runtime.setValue
55 | import androidx.compose.ui.Alignment
56 | import androidx.compose.ui.Modifier
57 | import androidx.compose.ui.graphics.Color
58 | import androidx.compose.ui.text.TextStyle
59 | import androidx.compose.ui.text.font.FontFamily
60 | import androidx.compose.ui.text.font.FontWeight
61 | import androidx.compose.ui.text.input.KeyboardType
62 | import androidx.compose.ui.unit.dp
63 | import androidx.core.app.ActivityCompat
64 | import androidx.core.content.ContextCompat
65 | import com.lance3.mhflasher.ui.theme.MagicHomeFlasherTheme
66 | import java.io.BufferedOutputStream
67 | import java.io.BufferedReader
68 | import java.io.IOException
69 | import java.io.InputStreamReader
70 | import java.net.DatagramPacket
71 | import java.net.DatagramSocket
72 | import java.net.Inet4Address
73 | import java.net.InetAddress
74 | import java.net.ServerSocket
75 | import java.net.SocketTimeoutException
76 |
77 | object Constants {
78 | const val OTA_PORT = 1111
79 | const val CMD_PORT = 48899
80 | const val CODE_PLACEHOLDER = "pierogi"
81 | }
82 |
83 | enum class CMD(val str: String){
84 | INFO("HF-A11ASSISTHREAD"),
85 | VER("AT+LVER\n"),
86 | OTA("AT+UPURL=http://10.10.123.4:${Constants.OTA_PORT}/update?version=33_00_20240418_OpenBeken&beta,${Constants.CODE_PLACEHOLDER}")
87 | }
88 | class MainActivity : ComponentActivity() {
89 | private val logViewModel = LogViewModel()
90 | private val apViewModel = ApViewModel()
91 | companion object {
92 | const val MY_PERMISSIONS_REQUEST_LOCATION = 1
93 | }
94 | override fun onCreate(savedInstanceState: Bundle?) {
95 | super.onCreate(savedInstanceState)
96 | Log.d("LOG","On create")
97 | setContent {
98 | MagicHomeFlasherTheme {
99 | MyApp(logViewModel, apViewModel, {wifiCheckAndScan()}, {ap -> wifiConnect(ap)},{ip -> checkDeviceByIP(ip)}, {flashDevice()})
100 | }
101 | }
102 | startHttpServer(applicationContext)
103 | }
104 |
105 | private fun scanSuccess(results: List?, wifiscanReceiver: BroadcastReceiver) {
106 | unregisterReceiver(wifiscanReceiver)
107 | myLog("Success scan")
108 | apViewModel.clearAccessPoints()
109 | results?.forEach { scanResult ->
110 | myLog("AP found: ${scanResult.SSID}")
111 | apViewModel.addAccessPoint(
112 | WifiAP(
113 | scanResult.SSID,
114 | scanResult.level
115 | )
116 | )
117 | }
118 | }
119 |
120 | override fun onRequestPermissionsResult(
121 | requestCode: Int,
122 | permissions: Array,
123 | grantResults: IntArray
124 | ) {
125 | super.onRequestPermissionsResult(requestCode, permissions, grantResults)
126 | when (requestCode) {
127 | MY_PERMISSIONS_REQUEST_LOCATION -> {
128 | if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
129 | myLog("Permission granted")
130 | wifiScan()
131 | } else {
132 | myLog("Permission not granted")
133 | Toast.makeText(
134 | this,
135 | "Unfortunately without this permission i was unable to scan WiFi (Android requirement)",
136 | Toast.LENGTH_LONG
137 | ).show()
138 | }
139 | return
140 | }
141 | }
142 | }
143 |
144 | private fun wifiCheckAndScan(){
145 | if (ContextCompat.checkSelfPermission(
146 | this,
147 | Manifest.permission.ACCESS_FINE_LOCATION
148 | ) != PackageManager.PERMISSION_GRANTED
149 | ) {
150 | myLog("No permission yet")
151 | ActivityCompat.requestPermissions(
152 | this,
153 | arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
154 | MY_PERMISSIONS_REQUEST_LOCATION
155 | )
156 | } else {
157 | myLog("Permission granted - skanujemy")
158 | wifiScan()
159 | }
160 | }
161 | private fun wifiScan() {
162 |
163 | myLog("Starting scan")
164 | apViewModel.clearAccessPoints()
165 | val wifiManager = getSystemService(Context.WIFI_SERVICE) as WifiManager?
166 |
167 | val wifiScanReceiver = object : BroadcastReceiver() {
168 | @RequiresApi(Build.VERSION_CODES.TIRAMISU)
169 | override fun onReceive(context: Context, intent: Intent) {
170 | val success = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false)
171 | if (success) {
172 | if (ActivityCompat.checkSelfPermission(
173 | context,
174 | Manifest.permission.ACCESS_FINE_LOCATION
175 | ) != PackageManager.PERMISSION_GRANTED
176 | ) {
177 | return
178 | }
179 | scanSuccess(wifiManager?.scanResults, this)
180 | } else {
181 | Toast.makeText(
182 | context,
183 | "Unfortunately without this permission i was unable to scan WiFi (Android requirement)",
184 | Toast.LENGTH_LONG
185 | ).show()
186 | }
187 | }
188 | }
189 |
190 | val intentFilter = IntentFilter()
191 | intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)
192 | registerReceiver(wifiScanReceiver, intentFilter)
193 |
194 | val success = wifiManager?.startScan()
195 | if (!success!!) {
196 | Toast.makeText(
197 | this,
198 | "Couldn't start a scan.",
199 | Toast.LENGTH_LONG
200 | ).show()
201 | }
202 | }
203 |
204 |
205 | private fun wifiConnect(ap: WifiAP) {
206 | val specifier = WifiNetworkSpecifier.Builder()
207 | .setSsid(ap.name)
208 | .build()
209 |
210 | val request = NetworkRequest.Builder()
211 | .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
212 | .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
213 | .setNetworkSpecifier(specifier)
214 | .build()
215 |
216 | val connectivityManager = applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
217 |
218 | val callback = object : ConnectivityManager.NetworkCallback() {
219 | override fun onAvailable(network: Network) {
220 | super.onAvailable(network)
221 | logViewModel.addLog("Connected to ${ap.name}")
222 | apViewModel.connectedAP.value = ap.name
223 | apViewModel.clearAccessPoints()
224 | connectivityManager.bindProcessToNetwork(network)
225 | }
226 |
227 | override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) {
228 | super.onLinkPropertiesChanged(network, linkProperties)
229 | val addresses = linkProperties.linkAddresses
230 | addresses.forEach {
231 | if (it.address is Inet4Address) { // Filter for IPv4 address
232 | logViewModel.addLog("got dhcp IPv4: ${it.address.hostAddress}")
233 | apViewModel.isConnected.value = true
234 | apViewModel.localIP.value = it.address.hostAddress
235 | }
236 | }
237 | val dnsServers = linkProperties.dnsServers
238 | if (dnsServers.isNotEmpty()) {
239 | val firstDnsServer = dnsServers[0].hostAddress
240 | logViewModel.addLog("First DNS Server: $firstDnsServer")
241 | apViewModel.remoteIP.value = firstDnsServer
242 | sendUdpPacket(dnsServers[0],Constants.CMD_PORT, CMD.INFO)
243 | } else {
244 | logViewModel.addLog("No DNS servers available")
245 | }
246 | }
247 | override fun onUnavailable() {
248 | super.onUnavailable()
249 | logViewModel.addLog("Failed to connect to ${ap.name}")
250 | apViewModel.isConnected.value = false
251 | }
252 |
253 | override fun onLost(network: Network) {
254 | super.onLost(network)
255 | logViewModel.addLog("Lost connection to ${ap.name}")
256 | apViewModel.isConnected.value = false
257 | }
258 | }
259 |
260 | connectivityManager.requestNetwork(request, callback)
261 | }
262 |
263 | private fun checkDeviceByIP(ipString: String){
264 | val ip = convertToInetAddress(ipString)
265 | if(ip == null){
266 | Toast.makeText(
267 | applicationContext,
268 | "Wrong ip: $ipString",
269 | Toast.LENGTH_LONG
270 | ).show()
271 | } else {
272 | sendUdpPacket(ip,Constants.CMD_PORT,CMD.INFO)
273 | }
274 | }
275 |
276 | fun convertToInetAddress(ipInput: String): InetAddress? {
277 | return try {
278 | InetAddress.getByName(ipInput)
279 | } catch (e: Exception) {
280 | null
281 | }
282 | }
283 | private fun flashDevice() {
284 | val inetAddress = convertToInetAddress(apViewModel.remoteIP.value)
285 | if (inetAddress != null) {
286 | sendUdpPacket(inetAddress, Constants.CMD_PORT, CMD.OTA)
287 | } else {
288 | logViewModel.addLog("Invalid IP address: ${apViewModel.remoteIP.value}")
289 | }
290 | }
291 | private fun sendUdpPacket(ip: InetAddress, port: Int, cmd: CMD) {
292 | val thread = Thread {
293 | try {
294 | val buffer = cmd.str.toByteArray()
295 |
296 | val packet = DatagramPacket(buffer, buffer.size, ip, port)
297 | DatagramSocket().use { socket ->
298 | socket.send(packet)
299 | val receiveBuffer = ByteArray(1024)
300 | val receivePacket = DatagramPacket(receiveBuffer, receiveBuffer.size)
301 | socket.soTimeout = 2000
302 |
303 | try {
304 | socket.receive(receivePacket)
305 | val receivedText = String(receivePacket.data, 0, receivePacket.length)
306 | when (cmd){
307 | CMD.INFO -> {
308 | checkUDP1_a11(receivedText)
309 | sendUdpPacket(ip, port, CMD.VER)
310 | }
311 | CMD.VER -> {
312 | logViewModel.addLog("ver: $receivedText")
313 | apViewModel.firmwareVersion.value= receivedText.split("=")[1]
314 | // sendUdpPacket(ip,port, CMD.OTA)
315 | }
316 | else -> {
317 | logViewModel.addLog("unk: $receivedText")
318 | }
319 | }
320 |
321 | } catch (e: SocketTimeoutException) {
322 | val info="Timeout: No response received within 2 seconds"
323 | logViewModel.addLog(info)
324 | }
325 | }
326 | } catch (e: Exception) {
327 | val info="Error sending or receiving packet: ${e.message}"
328 | logViewModel.addLog(info)
329 | }
330 | }
331 | thread.start()
332 | }
333 |
334 | private fun checkUDP1_a11(str: String){
335 | logViewModel.addLog("Received: $str")
336 | val parts=str.split(",")
337 | if (parts.count() > 2){
338 | logViewModel.addLog("\tmac:${parts[1]}")
339 | apViewModel.macAddress.value=parts[1]
340 | logViewModel.addLog("\tdev id:${parts[2]}")
341 | apViewModel.deviceId.value=parts[2]
342 | }
343 | }
344 |
345 | private fun startHttpServer(context: Context) {
346 | Thread {
347 | try {
348 | val serverSocket = ServerSocket(Constants.OTA_PORT)
349 | logViewModel.addLog("OTA server is running on port ${Constants.OTA_PORT}")
350 |
351 | while (!Thread.currentThread().isInterrupted) {
352 | val socket = serverSocket.accept()
353 | val input = BufferedReader(InputStreamReader(socket.getInputStream()))
354 | val output = BufferedOutputStream(socket.getOutputStream())
355 |
356 | val requestLines = mutableListOf()
357 | var line: String?
358 | while (input.readLine().also { line = it } != null && line != "") {
359 | requestLines.add(line!!)
360 | }
361 | logViewModel.addLog("Received HTTP Request:")
362 | apViewModel.flashProgress.value=0f
363 | requestLines.forEach { logViewModel.addLog(it) }
364 |
365 | //val fileInput = context.assets.open("OpenBL602_1.17.551_OTA.bin.xz.ota_cutted") //cutted=checksum error, test transfer only, no final flash
366 | val fileInput = context.assets.open("OpenBL602_1.17.551_OTA.bin.xz.ota")
367 | val fileSize = fileInput.available()
368 | val httpResponse = "HTTP/1.0 200 OK\r\nContent-Length: $fileSize\r\n\r\n"
369 |
370 | output.write(httpResponse.toByteArray())
371 |
372 | val buffer = ByteArray(1024*10)
373 | var totalSentBytes = 0
374 | var bytesRead: Int
375 | while (fileInput.read(buffer).also { bytesRead = it } != -1) {
376 | output.write(buffer, 0, bytesRead)
377 | totalSentBytes += bytesRead
378 | logViewModel.addLog("Sent $totalSentBytes of $fileSize bytes")
379 | apViewModel.flashProgress.value = totalSentBytes.toFloat()/fileSize
380 | }
381 | logViewModel.addLog("Finished uploading $totalSentBytes bytes")
382 | output.flush()
383 | output.close()
384 | fileInput.close()
385 | input.close()
386 | socket.close()
387 | }
388 |
389 | serverSocket.close()
390 | } catch (e: IOException) {
391 | e.printStackTrace()
392 | }
393 | }.start()
394 | }
395 | private fun myLog(message: String){
396 | logViewModel.addLog(message)
397 | }
398 | }
399 |
400 | @Composable
401 | fun MyApp(
402 | logViewModel: LogViewModel,
403 | apViewModel: ApViewModel,
404 | onScanClick: () -> Unit,
405 | onConnectClick: (WifiAP) -> kotlin.Unit,
406 | onCheckDeviceClick: (String) -> Unit,
407 | onFlashClick: () -> Unit,
408 | selTab: Int = 0
409 | ) {
410 | var tabIndex by remember { mutableIntStateOf(selTab) }
411 | val tabTitles = listOf("flash", "log", "about")
412 | Column(modifier = Modifier.fillMaxSize()) {
413 | TabRow(selectedTabIndex = tabIndex) {
414 | tabTitles.forEachIndexed { index, title ->
415 | Tab(
416 | selected = tabIndex == index,
417 | onClick = { tabIndex = index },
418 | text = { Text(title) })
419 | }
420 | }
421 | when (tabIndex) {
422 | 0 -> MainTabContent(logViewModel,apViewModel,onScanClick,onConnectClick, onCheckDeviceClick, onFlashClick)
423 | 1 -> LogTabContent(logViewModel)
424 | }
425 | }
426 | }
427 |
428 | @Composable
429 | fun MainTabContent(
430 | logViewModel: LogViewModel,
431 | apViewModel: ApViewModel,
432 | onScanClick: () -> Unit,
433 | onConnectClick: (WifiAP) -> Unit,
434 | onCheckDeviceClick: (String) -> Unit,
435 | onFlashClick: () -> Unit
436 | ) {
437 | Column(
438 | modifier = Modifier.fillMaxSize(),
439 | horizontalAlignment = Alignment.CenterHorizontally
440 | ) {
441 | LazyColumn {
442 | items(apViewModel.accessPoints) { ap ->
443 | Text(
444 | text = "SSID: ${ap.name}, RSSI: ${ap.rssi}",
445 | modifier = Modifier
446 | .fillMaxWidth()
447 | .padding(0.dp, 10.dp)
448 | .clickable {
449 | onConnectClick(ap)
450 | }
451 | )
452 | }
453 | }
454 |
455 | Button(onClick = {
456 | logViewModel.addLog("clicked scan")
457 | onScanClick()
458 | }) {
459 | Text(text = "Scan for WIFI device")
460 | }
461 | DeviceDetailsTable(apViewModel)
462 |
463 | if (apViewModel.firmwareVersion.value.isNotEmpty()) {
464 | Button(onClick = { onFlashClick() }) {
465 | Text(text = "Flash")
466 | }
467 | }
468 |
469 | if (apViewModel.flashProgress.value > 0) {
470 | LinearProgressIndicator(
471 | progress = apViewModel.flashProgress.value,
472 | modifier = Modifier
473 | .fillMaxWidth()
474 | .padding(16.dp),
475 | color = MaterialTheme.colorScheme.primary,
476 | trackColor = MaterialTheme.colorScheme.background
477 | )
478 | }
479 |
480 | }
481 | }
482 | /*
483 | Text("or", style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding(vertical = 8.dp))
484 | OutlinedTextField(
485 | value = apViewModel.remoteIP.value,
486 | onValueChange = { newValue ->
487 | apViewModel.remoteIP.value = newValue
488 | },
489 | label = { Text("Enter device IP") },
490 | singleLine = true,
491 | textStyle = TextStyle(color = Color.Black),
492 | keyboardOptions = KeyboardOptions.Default.copy(
493 | keyboardType = KeyboardType.Decimal,
494 | autoCorrect = false
495 | )
496 | )
497 | Button(onClick = { onCheckDeviceClick(apViewModel.remoteIP.value) }) {
498 | Text(text = "Check device")
499 | }
500 | */
501 |
502 | @Composable
503 | fun LogTabContent(logViewModel: LogViewModel) {
504 |
505 | Column {
506 | BasicTextField(
507 | value = logViewModel.logText.value,
508 | onValueChange = {},
509 | modifier = Modifier
510 | .fillMaxWidth()
511 | .weight(1f),
512 | textStyle = TextStyle(fontFamily = FontFamily.Monospace),
513 | readOnly = true
514 | )
515 | Spacer(modifier = Modifier.height(8.dp))
516 | Button(onClick = { logViewModel.clearLog() }) {
517 | Text("clear")
518 | }
519 | }
520 | }
521 |
522 | @Composable
523 | fun AboutTabContent(){
524 |
525 | }
526 | @Composable
527 | fun DeviceDetailsTable(apViewModel: ApViewModel) {
528 | if (apViewModel.isConnected.value) {
529 | Column(
530 | modifier = Modifier
531 | .fillMaxWidth()
532 | .padding(16.dp)
533 | ) {
534 | Text("Connected Device Details")
535 |
536 | Row(modifier = Modifier.padding(top = 8.dp)) {
537 | Text("AP:", fontWeight = FontWeight.Bold)
538 | Spacer(Modifier.width(8.dp))
539 | Text(apViewModel.connectedAP.value)
540 | }
541 |
542 | Row(modifier = Modifier.padding(top = 8.dp)) {
543 | Text("MAC Address:", fontWeight = FontWeight.Bold)
544 | Spacer(Modifier.width(8.dp))
545 | Text(apViewModel.macAddress.value)
546 | }
547 |
548 | Row(modifier = Modifier.padding(top = 8.dp)) {
549 | Text("Device ID:", fontWeight = FontWeight.Bold)
550 | Spacer(Modifier.width(8.dp))
551 | Text(apViewModel.deviceId.value)
552 | }
553 |
554 | Row(modifier = Modifier.padding(top = 8.dp)) {
555 | Text("Firmware Version:", fontWeight = FontWeight.Bold)
556 | Spacer(Modifier.width(8.dp))
557 | Text(apViewModel.firmwareVersion.value)
558 | }
559 |
560 | Row(modifier = Modifier.padding(top = 8.dp)) {
561 | Text("Device IP:", fontWeight = FontWeight.Bold)
562 | Spacer(Modifier.width(8.dp))
563 | Text(apViewModel.remoteIP.value)
564 | }
565 |
566 | Row(modifier = Modifier.padding(top = 8.dp)) {
567 | Text("My IP:", fontWeight = FontWeight.Bold)
568 | Spacer(Modifier.width(8.dp))
569 | Text(apViewModel.localIP.value)
570 | }
571 | }
572 | }
573 | }
574 |
575 | //@Preview(showBackground = true)
576 | //@Composable
577 | //fun DefaultPreview(onScanClick: () -> Unit) {
578 | // val logViewModel = LogViewModel()
579 | // logViewModel.addLog("First line")
580 | // logViewModel.addLog("Second line")
581 | // val apViewModel = ApViewModel()
582 | // MagicHomeFlasherTheme {
583 | // MyApp(logViewModel, apViewModel, onScanClick, onConnectClick ,0)
584 | // }
585 | //}
586 |
--------------------------------------------------------------------------------