diff --git a/app/build.gradle b/app/build.gradle index def0375..2e9068d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,13 +14,13 @@ android { compileSdk 34 namespace 'in.sminnovations.hpostesting' - // dev - development, quality - qc, uat - User Acceptance Test, preprod - preproduction, prod - production + // dev -> development, quality -> qc, uat -> User Acceptance Testing, preprod -> preproduction, prod -> production, iocl -> iocl-iisc production defaultConfig { applicationId "in.sminnovations.hpostesting.dev" minSdk 21 targetSdk 34 - versionCode 101 - versionName "2.1.101" + versionCode 114 + versionName "2.1.114" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -157,7 +157,7 @@ dependencies { implementation("com.squareup.okhttp3:okhttp:4.9.3") implementation "androidx.preference:preference-ktx:1.2.1" - implementation 'io.nats:jnats:2.11.2' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1' implementation("androidx.work:work-runtime-ktx:2.9.0") @@ -165,4 +165,6 @@ dependencies { implementation 'com.google.android.play:core:1.10.3' implementation fileTree(dir: 'libs', include: ['*.aar']) + implementation 'io.nats:jnats:2.11.4' + } \ No newline at end of file diff --git a/app/release/hpos-app.apk b/app/release/hpos-app.apk deleted file mode 100644 index b9d6528..0000000 Binary files a/app/release/hpos-app.apk and /dev/null differ diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json index acfe21e..14fde68 100644 --- a/app/release/output-metadata.json +++ b/app/release/output-metadata.json @@ -4,15 +4,15 @@ "type": "APK", "kind": "Directory" }, - "applicationId": "com.example.hpos", + "applicationId": "in.sminnovations.hpostesting.quality", "variantName": "release", "elements": [ { "type": "SINGLE", "filters": [], "attributes": [], - "versionCode": 1, - "versionName": "1.0", + "versionCode": 101, + "versionName": "2.1.101", "outputFile": "app-release.apk" } ], diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a5b66b6..6a31cea 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -48,6 +48,10 @@ android:name="com.example.hpostesting.presentation.autodac.AutoDacActivity" android:exported="false" android:theme="@style/Theme.HPOS.NoActionBar" /> + @@ -118,9 +123,8 @@ android:noHistory="true" android:theme="@style/AppTheme.NoActionBar"> - - - + + >> = mapOf( "HC-V1-005" to listOf( listOf(1.5,0.0), diff --git a/app/src/main/java/com/example/hpostesting/data/dao/MyDataBase.kt b/app/src/main/java/com/example/hpostesting/data/dao/MyDataBase.kt index 7cb221a..fcfc0cc 100644 --- a/app/src/main/java/com/example/hpostesting/data/dao/MyDataBase.kt +++ b/app/src/main/java/com/example/hpostesting/data/dao/MyDataBase.kt @@ -8,7 +8,7 @@ import com.example.hpostesting.data.model.patient.DeviceData import com.example.hpostesting.data.model.patient.HemoCubeTestData import com.example.hpostesting.data.model.patient.UserData -@Database(entities = [UserData::class, HemoCubeTestData::class, DeviceData::class, BufferCheckData::class], version = 24, exportSchema = false) +@Database(entities = [UserData::class, HemoCubeTestData::class, DeviceData::class, BufferCheckData::class], version = 26, exportSchema = false) @TypeConverters(Converters::class) abstract class MyDatabase : RoomDatabase() { abstract fun userDao(): UserDao diff --git a/app/src/main/java/com/example/hpostesting/data/model/deviceprovision/ProvisionData.kt b/app/src/main/java/com/example/hpostesting/data/model/deviceprovision/ProvisionData.kt new file mode 100644 index 0000000..179ea33 --- /dev/null +++ b/app/src/main/java/com/example/hpostesting/data/model/deviceprovision/ProvisionData.kt @@ -0,0 +1,8 @@ +package com.example.hpostesting.data.model.deviceprovision + +data class ProvisionData( +val username: String?, +val password: String?, +val natsToken: String? +) + diff --git a/app/src/main/java/com/example/hpostesting/data/model/diagnostics/DiagnosticsData.kt b/app/src/main/java/com/example/hpostesting/data/model/diagnostics/DiagnosticsData.kt index c84cdc4..d482597 100644 --- a/app/src/main/java/com/example/hpostesting/data/model/diagnostics/DiagnosticsData.kt +++ b/app/src/main/java/com/example/hpostesting/data/model/diagnostics/DiagnosticsData.kt @@ -5,5 +5,8 @@ data class DiagnosticsData ( var appVersion: String? = "", var deviceType: String = "HEMOCUBE", var deviceData: String = "", + var devicePassword: String = "", + var deviceNatsToken: String = "", + var accessToken: String = "", var runTime: String = "" ) \ No newline at end of file diff --git a/app/src/main/java/com/example/hpostesting/data/model/jig/JigData.kt b/app/src/main/java/com/example/hpostesting/data/model/jig/JigData.kt new file mode 100644 index 0000000..91426b2 --- /dev/null +++ b/app/src/main/java/com/example/hpostesting/data/model/jig/JigData.kt @@ -0,0 +1,9 @@ +package com.example.hpostesting.data.model.jig + +data class JigData ( + var deviceId: String = "", + var appVersion: String? = "", + var deviceType: String = "JIG", + var scanData: String = "", + var createdAt: String = "" +) \ No newline at end of file diff --git a/app/src/main/java/com/example/hpostesting/data/model/molbioresult/MolbioV2Result.kt b/app/src/main/java/com/example/hpostesting/data/model/molbioresult/MolbioV2Result.kt index 3e3c698..ab01bec 100644 --- a/app/src/main/java/com/example/hpostesting/data/model/molbioresult/MolbioV2Result.kt +++ b/app/src/main/java/com/example/hpostesting/data/model/molbioresult/MolbioV2Result.kt @@ -4,7 +4,7 @@ import com.example.hpostesting.data.model.patient.HemoCubeTestData data class MolbioV2Result( val age: Int? = 31, - val analysisDate: String? = "", + val analysisDate: String? = "2024-02-08 16:33:56", val analysisId: String? = "", val analysisStatus: String? = "", val analysisType: String? = "HPOS", @@ -12,11 +12,11 @@ data class MolbioV2Result( val bloodGroup: String? = "", val coefficients: List? = listOf(22, 22), val collectionLocation: List? = listOf(), - val collectionTime: String? = "", + val collectionTime: String? = "2024-02-08 16:33:56", val collector: String? = "", val curveFitting: String? = "Linear", val deviceName: String? = "HPOS", - val expiryTime: String? = "", + val expiryTime: String? = "2024-02-08 16:33:56", val gender: String? = "", val interpretation: String? = "", val `operator`: String? = "", @@ -30,7 +30,7 @@ data class MolbioV2Result( val testId: String? = "", val testResult: String? = "", val testStatus: String? = "", - val testTime: String? = "", + val testTime: String? = "2024-02-08 16:33:56", val testType: String? = "", val thresholds: String? = "", val underMedication: Boolean? = false, diff --git a/app/src/main/java/com/example/hpostesting/data/model/patient/DeviceData.kt b/app/src/main/java/com/example/hpostesting/data/model/patient/DeviceData.kt index cefc5b9..79b3834 100644 --- a/app/src/main/java/com/example/hpostesting/data/model/patient/DeviceData.kt +++ b/app/src/main/java/com/example/hpostesting/data/model/patient/DeviceData.kt @@ -15,5 +15,15 @@ data class DeviceData( @get:PropertyName("coefficients") @set:PropertyName("coefficients") var coefficients: List = emptyList(), @get:PropertyName("calibratedAt") @set:PropertyName("calibratedAt") - var calibratedAt: String = "" + var calibratedAt: String = "" , + @get:PropertyName("deviceProvisionResponse") @set:PropertyName("deviceProvisionResponse") + var deviceProvisionResponse: String = "" , + @get:PropertyName("username") @set:PropertyName("username") + var username: String = "", + @get:PropertyName("password") @set:PropertyName("password") + var password: String = "", + @get:PropertyName("natsToken") @set:PropertyName("natsToken") + var natsToken: String = "", + @get:PropertyName("natsTokenExpiry") @set:PropertyName("natsTokenExpiry") + var natsTokenExpiry: String = "" ) \ No newline at end of file diff --git a/app/src/main/java/com/example/hpostesting/data/repository/DatabaseRepository.kt b/app/src/main/java/com/example/hpostesting/data/repository/DatabaseRepository.kt index cb279fd..acb2755 100644 --- a/app/src/main/java/com/example/hpostesting/data/repository/DatabaseRepository.kt +++ b/app/src/main/java/com/example/hpostesting/data/repository/DatabaseRepository.kt @@ -11,6 +11,7 @@ import com.example.hpostesting.data.model.devicediagnostics.DeviceDiagnosticsRes import com.example.hpostesting.data.model.deviceprovision.DeviceProvisionRequest import com.example.hpostesting.data.model.deviceprovision.DeviceProvisionResponse import com.example.hpostesting.data.model.diagnostics.DiagnosticsData +import com.example.hpostesting.data.model.jig.JigData import com.example.hpostesting.data.model.log.UploadLogsResponse import com.example.hpostesting.data.model.login.LoginRequest import com.example.hpostesting.data.model.login.LoginResponse @@ -140,6 +141,16 @@ class DatabaseRepository @Inject constructor( } } + override suspend fun addTestJigData(data: JigData?): Response { + return try { + db.collection("jigs").add(data!!).await() + Response.Success(data.deviceId) + } catch (e: Exception) { + Firebase.crashlytics.recordException(e) + Response.Error(e) + } + } + override suspend fun uploadFileToStorage( patientID: String, filePath: String ): Response { @@ -209,7 +220,28 @@ class DatabaseRepository @Inject constructor( return allDeviceDataList.find { it.deviceId == deviceId } } + override suspend fun getDeviceResponse(data: DeviceData?): Response { + return try { + db.collection("devices").add(data!!).await() + Response.Success(data.deviceProvisionResponse) + } catch (e: Exception) { + Firebase.crashlytics.recordException(e) + Response.Error(e) + } + } + + override suspend fun uploadDeviceId(data: DeviceData): Response? { + return try { + db.collection("devices").add(data!!).await() + Response.Success(data.deviceId) + } catch (e: Exception) { + Firebase.crashlytics.recordException(e) + Response.Error(e) + } + } + override fun addTestToDatabase(testDetails: UserData): Any { TODO("Not yet implemented") } + } \ No newline at end of file diff --git a/app/src/main/java/com/example/hpostesting/data/repository/Repository.kt b/app/src/main/java/com/example/hpostesting/data/repository/Repository.kt index dfb5d33..243c494 100644 --- a/app/src/main/java/com/example/hpostesting/data/repository/Repository.kt +++ b/app/src/main/java/com/example/hpostesting/data/repository/Repository.kt @@ -7,6 +7,7 @@ import com.example.hpostesting.data.model.devicediagnostics.DeviceDiagnosticsRes import com.example.hpostesting.data.model.deviceprovision.DeviceProvisionRequest import com.example.hpostesting.data.model.deviceprovision.DeviceProvisionResponse import com.example.hpostesting.data.model.diagnostics.DiagnosticsData +import com.example.hpostesting.data.model.jig.JigData import com.example.hpostesting.data.model.log.UploadLogsResponse import com.example.hpostesting.data.model.login.LoginRequest import com.example.hpostesting.data.model.login.LoginResponse @@ -31,9 +32,13 @@ interface Repository { suspend fun addDiagnostics(data: DiagnosticsData?): Response + suspend fun addTestJigData(data: JigData?): Response + suspend fun uploadFileToStorage(patientID: String, filePath: String): Response suspend fun getDeviceDataById(deviceId: String): DeviceData? + suspend fun getDeviceResponse(data: DeviceData?): Response + suspend fun uploadDeviceId(data: DeviceData): Response? abstract fun addTestToDatabase(testDetails: UserData): Any // suspend fun addToDatabase(data: PatientDetails) diff --git a/app/src/main/java/com/example/hpostesting/presentation/KitScanActivity.kt b/app/src/main/java/com/example/hpostesting/presentation/KitScanActivity.kt index 4aafcc7..f5b87e4 100644 --- a/app/src/main/java/com/example/hpostesting/presentation/KitScanActivity.kt +++ b/app/src/main/java/com/example/hpostesting/presentation/KitScanActivity.kt @@ -229,7 +229,6 @@ class KitScanActivity : AppCompatActivity(), IDcsSdkApiDelegate { } else { Toast.makeText(this,"Error", Toast.LENGTH_LONG).show() } - } private fun pullTrigger() { @@ -271,7 +270,7 @@ class KitScanActivity : AppCompatActivity(), IDcsSdkApiDelegate { return sharedPreference.getString(Constants.KIT_NUMBER, "") ?.isNotBlank() == true && sharedPreference.getInt( Constants.KIT_COUNT, 0 - ) > 0 && sharedPreference.getInt(Constants.KIT_COUNT, 0) < 35 + ) > 0 && sharedPreference.getInt(Constants.KIT_COUNT, 0) < Constants.KIT_CAPACITY } private fun isSerialValid(s: String): Boolean { diff --git a/app/src/main/java/com/example/hpostesting/presentation/MainActivity.kt b/app/src/main/java/com/example/hpostesting/presentation/MainActivity.kt index dfd8997..f1d0992 100644 --- a/app/src/main/java/com/example/hpostesting/presentation/MainActivity.kt +++ b/app/src/main/java/com/example/hpostesting/presentation/MainActivity.kt @@ -149,6 +149,15 @@ class MainActivity : AppCompatActivity() { Constants.DEVICE_TYPE_HEMOCUBE } + device.productId == 4614 && device.vendorId == 7111 -> { + binding.cvItem1.visibility = View.VISIBLE + binding.cvItem3.visibility = View.VISIBLE + binding.cvItem2.visibility = View.GONE + binding.cvItem4.visibility = View.GONE + DataHolder.deviceType.postValue(Constants.DEVICE_TYPE_HEMOCUBE) + Constants.DEVICE_TYPE_HEMOCUBE + } + device.productId == Constants.DEVICE_PRODUCT_ID && device.vendorId == Constants.DEVICE_VENDOR_ID -> { Log.d( TAG, diff --git a/app/src/main/java/com/example/hpostesting/presentation/NatsManager.kt b/app/src/main/java/com/example/hpostesting/presentation/NatsManager.kt index 4c8f3dc..36cd967 100644 --- a/app/src/main/java/com/example/hpostesting/presentation/NatsManager.kt +++ b/app/src/main/java/com/example/hpostesting/presentation/NatsManager.kt @@ -1,6 +1,10 @@ package com.example.hpostesting.presentation +import android.content.Context +import android.os.Build import android.util.Log +import androidx.annotation.RequiresApi +import com.example.hpostesting.data.constant.Constants import com.example.hpostesting.presentation.dashboard.DashboardActivity import io.nats.client.AuthHandler import io.nats.client.Connection @@ -8,9 +12,17 @@ import io.nats.client.Message import io.nats.client.NKey import io.nats.client.Nats import io.nats.client.Options +import io.nats.client.support.SSLUtils +import java.io.FileInputStream import java.io.IOException import java.nio.charset.StandardCharsets import java.security.GeneralSecurityException +import java.security.KeyStore +import java.security.SecureRandom +import java.security.cert.CertificateFactory +import javax.net.ssl.KeyManagerFactory +import javax.net.ssl.SSLContext +import javax.net.ssl.TrustManagerFactory class NatsManager(datacollector: DashboardActivity) { @@ -19,103 +31,160 @@ class NatsManager(datacollector: DashboardActivity) { var nc: Connection? = null val datacollector = datacollector var connect = false + var sharedPreferences = datacollector.getSharedPreferences("HEMOCUBE", Context.MODE_PRIVATE) + private fun createSSLContext(): SSLContext { + val keyStorePassword = "prime24".toCharArray() // Change as necessary + val clientCertPath = "/storage/sdcard0/Download/client.p12" + + // Load client certificate and key + val keyStore = KeyStore.getInstance("PKCS12") + FileInputStream(clientCertPath).use { keyStoreInputStream -> + keyStore.load(keyStoreInputStream, keyStorePassword) + } + val caCertPath = + "/storage/sdcard0/Android/data/in.sminnovations.hpostesting.quality/files/NATS/clientCertificate/client-cert.pem" + val caCert = FileInputStream(caCertPath).use { inputStream -> + val certificateFactory = CertificateFactory.getInstance("X.509") + certificateFactory.generateCertificate(inputStream) + } + + val trustStore = KeyStore.getInstance(KeyStore.getDefaultType()).apply { + load(null, null) // Initialize the keystore + setCertificateEntry("caCert", caCert) // Add the CA certificate + } + + // Initialize key manager factory + val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()) + kmf.init(keyStore, keyStorePassword) + + // Initialize trust manager factory + val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) + tmf.init(trustStore) + + // Initialize SSLContext + val sslContext = SSLContext.getInstance("TLS") + sslContext.init(kmf.keyManagers, tmf.trustManagers, SecureRandom()) + + return sslContext + } + + @RequiresApi(Build.VERSION_CODES.O) fun connect() { Log.d(TAG, "TRY TO CONNECT") Thread { - val seedString = "SUAEB5PUWNS6C2HUV3MFI6HUDEAPGPFPBHMI73NHIQATCDD2BWALVEZXZ4" - val seedBytes = seedString.toCharArray() - - val theNKey = NKey.fromSeed(seedBytes) // really should load from somewhere - - val options = Options.Builder() - .server("nats://192.168.10.117:4222") - .authHandler(object : AuthHandler { - override fun getID(): CharArray? { - return try { - theNKey?.publicKey - } catch (ex: GeneralSecurityException) { - null - } catch (ex: IOException) { - null - } catch (ex: NullPointerException) { - null - } - } - - override fun sign(nonce: ByteArray): ByteArray? { - return try { - theNKey?.sign(nonce) - } catch (ex: GeneralSecurityException) { - null - } catch (ex: IOException) { - null - } catch (ex: NullPointerException) { - null - } - } - - override fun getJWT(): CharArray? { - return null - } - }) - .build() - try { + + val seedString = sharedPreferences.getString(Constants.NATS_TOKEN, "") + val deviceId = sharedPreferences.getString(Constants.DEVICE_ID, "") + Log.e("seedString", seedString.toString()) + Log.d("nats deviceId", deviceId.toString()) + val seedBytes = seedString?.toCharArray() + + val theNKey = NKey.fromSeed(seedBytes) // really should load from somewhere + + val options = Options.Builder() + .server("nats://nanodgx.in:4222") + .sslContext(SSLUtils.createOpenTLSContext()) + .authHandler(object : AuthHandler { + override fun getID(): CharArray? { + return try { + theNKey?.publicKey + } catch (ex: GeneralSecurityException) { + null + } catch (ex: IOException) { + null + } catch (ex: NullPointerException) { + null + } + } + + override fun sign(nonce: ByteArray): ByteArray? { + return try { + theNKey?.sign(nonce) + } catch (ex: GeneralSecurityException) { + null + } catch (ex: IOException) { + null + } catch (ex: NullPointerException) { + null + } + } + + override fun getJWT(): CharArray? { + return null + } + }) + .build() + nc = Nats.connect(options) Log.d(TAG, "Connected to Nats server ${options.servers.first()}") connect = true datacollector.setConnect(true) + if (nc?.status == Connection.Status.CONNECTED) { + Log.d("NATSCONNECTION", "NATS is successfully connected.") - nc?.publish( - "server.hpos.HCV-000-3001.ping", - "ALIVE".toByteArray(StandardCharsets.UTF_8) - ) - nc?.publish( - "server.hpos.HCV-000-3001.health", - "ALIVE".toByteArray(StandardCharsets.UTF_8) - ) - Log.d(TAG, "Published msg server.hpos.HCV-000-3001.ping on topic Testing") - val d = nc?.createDispatcher { msg: Message? -> - println("PRITIMOI SARKAR $msg") - } + val d = nc?.createDispatcher { msg: Message? -> + println("Nats dispatcher $msg") + } - d?.subscribe("device.hpos.HCV-000-3001.update") { msg -> - val response = String(msg.data, StandardCharsets.UTF_8) - datacollector. setResponse(response) - println("Message received (up to 100 times): $response") - } + nc?.subscribe("device.hpos.${deviceId}.ping") - d?.subscribe("device.hpos.HCV-000-3001.uploadlogs") { msg -> - val response = String(msg.data, StandardCharsets.UTF_8) - datacollector.setResponse(response + "uPLOAD") - println("Message received (up to 100 times): $response") - } + nc?.publish( + "server.hpos.${deviceId}.ping", + "ALIVE".toByteArray(StandardCharsets.UTF_8) + ) + nc?.publish( + "server.hpos.${deviceId}.health", + "ALIVE".toByteArray(StandardCharsets.UTF_8) + ) - d?.subscribe("device.hpos.HCV-000-3001.disable") { msg -> - val response = String(msg.data, StandardCharsets.UTF_8) - datacollector.setResponse(response) - println("Message received (up to 100 times): $response") - } + d?.subscribe("device.hpos.${deviceId}.ping") { msg -> + val response = String(msg.data, StandardCharsets.UTF_8) + datacollector.setResponse(response) + println("Message received (up to 100 times): $response") + Log.d(TAG, "subscribed msg ${msg} on topic ping") + } - d?.subscribe("device.hpos.HCV-000-3001.updatecustomer") { msg -> - val response = String(msg.data, StandardCharsets.UTF_8) - datacollector.setResponse(response) - println("Message received (up to 100 times): $response") - } + d?.subscribe("device.hpos.${deviceId}.update") { msg -> + val response = String(msg.data, StandardCharsets.UTF_8) + datacollector.setResponse(response) + println("Message received (up to 100 times): $response") + } - d?.subscribe("device.hpos.HCV-000-3001.checkupdate") { msg -> - val response = String(msg.data, StandardCharsets.UTF_8) - datacollector.setResponse(response) - println("Message received (up to 100 times): $response") + d?.subscribe("device.hpos.${deviceId}.uploadlogs") { msg -> + val response = String(msg.data, StandardCharsets.UTF_8) + datacollector.setResponse(response + "UPLOAD") + println("Message received (up to 100 times): $response") + } + + d?.subscribe("device.hpos.${deviceId}.disable") { msg -> + val response = String(msg.data, StandardCharsets.UTF_8) + datacollector.setResponse(response) + println("Message received (up to 100 times): $response") + } + + d?.subscribe("device.hpos.${deviceId}.updatecustomer") { msg -> + val response = String(msg.data, StandardCharsets.UTF_8) + datacollector.setResponse(response) + println("Message received (up to 100 times): $response") + } + + d?.subscribe("device.hpos.${deviceId}.checkupdate") { msg -> + val response = String(msg.data, StandardCharsets.UTF_8) + datacollector.setResponse(response) + println("Message received (up to 100 times): $response") + } + } else { + Log.d("NATSCONNECTION", "NATS is not connected. Current status: ${nc?.status}") } } catch (exp: Exception) { println(exp.printStackTrace()) connect = false - datacollector.setConnect(true) + datacollector.setConnect(false) } }.start() @@ -126,6 +195,16 @@ class NatsManager(datacollector: DashboardActivity) { Log.d(TAG, "Published msg ${msg} on topic ${topic}") } + fun sub(topic: String) { + val d = nc?.createDispatcher { msg: Message? -> + val response = String(msg?.data ?: ByteArray(0), StandardCharsets.UTF_8) + datacollector.onMessageReceived(topic, response) + Log.d(TAG, "Subscribed msg $msg on topic $topic") + } + + d?.subscribe(topic) + } + fun close() { nc?.close() Log.d(TAG, "Nats connection close") diff --git a/app/src/main/java/com/example/hpostesting/presentation/assurance/AssuranceControlsFragment.kt b/app/src/main/java/com/example/hpostesting/presentation/assurance/AssuranceControlsFragment.kt index 4043938..250bea2 100644 --- a/app/src/main/java/com/example/hpostesting/presentation/assurance/AssuranceControlsFragment.kt +++ b/app/src/main/java/com/example/hpostesting/presentation/assurance/AssuranceControlsFragment.kt @@ -12,6 +12,7 @@ import android.view.ViewGroup import android.widget.AdapterView import android.widget.ArrayAdapter import android.widget.Spinner +import android.widget.Toast import androidx.fragment.app.Fragment import com.example.hpostesting.data.DataHolder import com.example.hpostesting.data.constant.Constants @@ -30,6 +31,9 @@ class AssuranceControlsFragment: Fragment() { binding = FragmentAssuranceControlsBinding.inflate(inflater, container, false) sharedPreferences = requireContext().getSharedPreferences("HEMOCUBE", Context.MODE_PRIVATE) + + DataHolder.hemoCubeTestData!!.solution = "" + DataHolder.hemoCubeTestData!!.volume = "" return binding.root } @@ -42,7 +46,7 @@ class AssuranceControlsFragment: Fragment() { // binding.btnSubmit.visibility = View.GONE val solutionSpinner: Spinner = binding.spinnerSolutions - val solutionOptions = arrayOf("Select solution", "Tartrazine", "AR") + val solutionOptions = arrayOf("Select solution", "Tartrazine", "Acid Red") val solutionAdapter = ArrayAdapter(requireContext(), R.layout.simple_spinner_item, solutionOptions) solutionAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) solutionSpinner.adapter = solutionAdapter @@ -121,6 +125,13 @@ class AssuranceControlsFragment: Fragment() { volumeSpinner.setSelection(volumePosition) binding.btnSubmit.setOnClickListener { + val selectedSolution = DataHolder.hemoCubeTestData!!.solution + val selectedVolume = DataHolder.hemoCubeTestData!!.volume + if (selectedSolution == "Select solution" || selectedVolume == "Select volume") { + Toast.makeText(requireContext(), "Please select both solution and volume", Toast.LENGTH_SHORT).show() + return@setOnClickListener + } + DataHolder.hemoCubeTestData!!.quickCapture = true val currentUnixTime = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { Instant.now().epochSecond diff --git a/app/src/main/java/com/example/hpostesting/presentation/autodac/AutoDacFragment.kt b/app/src/main/java/com/example/hpostesting/presentation/autodac/AutoDacFragment.kt index 71f4d2c..2cc3a6a 100644 --- a/app/src/main/java/com/example/hpostesting/presentation/autodac/AutoDacFragment.kt +++ b/app/src/main/java/com/example/hpostesting/presentation/autodac/AutoDacFragment.kt @@ -105,8 +105,6 @@ class AutoDacFragment: Fragment() { HemoCubeCommands.AUTO_DAC_COMMAND, object : UsbServiceListener { override fun onUsbRead(data: ByteArray?) { - - } override fun onUsbError(e: Exception?) { autoDacViewModel.progressBar.postValue(false) @@ -148,6 +146,9 @@ class AutoDacFragment: Fragment() { autoDacViewModel.addAutoDacDataToDb( DiagnosticsData( deviceId = sharedPreferences.getString(Constants.DEVICE_ID, "").toString(), + devicePassword = sharedPreferences.getString(Constants.DEVICE_PASSWORD_API, "").toString(), + deviceNatsToken = sharedPreferences.getString(Constants.NATS_TOKEN, "").toString(), + accessToken = sharedPreferences.getString(Constants.ACCESS_TOKEN, "").toString(), deviceData = resultData, runTime = SimpleDateFormat( "yyyy-MM-dd HH:mm:ss", Locale.getDefault() @@ -169,7 +170,6 @@ class AutoDacFragment: Fragment() { } } - fun parseData(inputData: List): List> { val pattern = Regex("([A-Z]+)\\s(\\d+)") val parsedData = mutableListOf>() diff --git a/app/src/main/java/com/example/hpostesting/presentation/dashboard/DashboardActivity.kt b/app/src/main/java/com/example/hpostesting/presentation/dashboard/DashboardActivity.kt index 63b7cd9..3153f95 100644 --- a/app/src/main/java/com/example/hpostesting/presentation/dashboard/DashboardActivity.kt +++ b/app/src/main/java/com/example/hpostesting/presentation/dashboard/DashboardActivity.kt @@ -4,7 +4,9 @@ import android.app.DownloadManager import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.content.SharedPreferences import android.net.Uri +import android.os.Build import android.os.Bundle import android.util.Log import android.view.Menu @@ -19,9 +21,11 @@ import androidx.navigation.ui.navigateUp import androidx.navigation.ui.setupActionBarWithNavController import androidx.navigation.ui.setupWithNavController import com.example.hpostesting.data.Result +import com.example.hpostesting.data.constant.Constants import com.example.hpostesting.data.constant.LanguageManager import com.example.hpostesting.presentation.NatsManager import com.example.hpostesting.presentation.hemocube.HemoCubeViewModel +import com.example.hpostesting.presentation.jig.JigActivity import com.google.android.material.navigation.NavigationView import com.google.firebase.appdistribution.FirebaseAppDistribution import com.google.firebase.appdistribution.FirebaseAppDistributionException @@ -32,15 +36,22 @@ import `in`.sminnovations.hpostesting.databinding.ActivityDashboardBinding import okhttp3.ResponseBody import java.io.File -open interface IDataCollector { +interface NatsMessageCallback { + fun onMessageReceived(topic: String, message: String) +} + +open interface IDataCollector: NatsMessageCallback { fun setConnect(connect: Boolean) fun setResponse(response: String) } + @AndroidEntryPoint class DashboardActivity : AppCompatActivity(), IDataCollector { + val TAG = "DashboardActivity" private lateinit var appBarConfiguration: AppBarConfiguration private lateinit var binding: ActivityDashboardBinding + lateinit var sharedPreferences: SharedPreferences var responses: String = "" lateinit var nats: NatsManager private var downloadId: Long = 0 @@ -53,15 +64,27 @@ class DashboardActivity : AppCompatActivity(), IDataCollector { super.attachBaseContext(newBase) } + override fun onMessageReceived(topic: String, message: String) { + // Handle incoming messages from NATS + Log.d(TAG, "Received message on topic $topic: $message") + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityDashboardBinding.inflate(layoutInflater) + sharedPreferences = this.getSharedPreferences("HEMOCUBE", Context.MODE_PRIVATE) setContentView(binding.root) setSupportActionBar(binding.appBarDashboard.toolbar) nats = NatsManager(this) - nats.connect() - nats.pub("server.hpos.HCV-000-3001.ping", "THIS IS A TEST MSG") + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + nats.connect() + } + + val deviceId = sharedPreferences.getString(Constants.DEVICE_ID, "") + + nats.sub("server.hpos.${deviceId}.ping") + nats.pub("server.hpos.${deviceId}.ping", "THIS IS A TEST MSG") hemocubeViewModel.deviceUpdate.observe(this) { result -> when (result) { @@ -71,11 +94,11 @@ class DashboardActivity : AppCompatActivity(), IDataCollector { // val apkUrl = "https://dl.dropboxusercontent.com/s/fi/1c3nn7t0co431hicl3hrt/app-debug.apk?rlkey=e4uf13ty1dpcked614vy1aaqp&dl=0" initiateUpdate(apkUrl.toString()) Log.d("ApI", "APK URL: $apkUrl") - Toast.makeText( - this, - "APK UPLOAD ${result.data}", - Toast.LENGTH_SHORT - ).show() +// Toast.makeText( +// this, +// "APK UPLOAD ${result.data}", +// Toast.LENGTH_SHORT +// ).show() } is Result.Error -> { @@ -105,6 +128,11 @@ class DashboardActivity : AppCompatActivity(), IDataCollector { ) setupActionBarWithNavController(navController, appBarConfiguration) navView.setupWithNavController(navController) + + if (Constants.NAVIGATE_TO_TEST_JIG_DIRECTLY) { + startActivity(Intent(this, JigActivity::class.java)) + finish() + } } override fun onCreateOptionsMenu(menu: Menu): Boolean { @@ -156,9 +184,10 @@ class DashboardActivity : AppCompatActivity(), IDataCollector { val file = File(getExternalFilesDir("Updates"), "update.apk") file.setReadable(true, false) // Ensure the file is readable + val pInfo = baseContext.packageManager.getPackageInfo(baseContext.packageName, 0) val uri: Uri = FileProvider.getUriForFile( this, - "com.example.hpostesting.fileprovider", + "${pInfo}.fileprovider", file ) diff --git a/app/src/main/java/com/example/hpostesting/presentation/dashboard/GalleryFragment.kt b/app/src/main/java/com/example/hpostesting/presentation/dashboard/GalleryFragment.kt index 387324c..539b6e8 100644 --- a/app/src/main/java/com/example/hpostesting/presentation/dashboard/GalleryFragment.kt +++ b/app/src/main/java/com/example/hpostesting/presentation/dashboard/GalleryFragment.kt @@ -1,6 +1,7 @@ package com.example.hpostesting.presentation.dashboard import android.annotation.SuppressLint +import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.SharedPreferences @@ -14,12 +15,13 @@ import com.example.hpostesting.data.constant.Constants import com.example.hpostesting.presentation.autodac.AutoDacActivity import com.example.hpostesting.presentation.buffercheck.HemocubeBufferCheckActivity import com.example.hpostesting.presentation.calibration.CalibrationActivity -import com.example.hpostesting.presentation.deviceprovision.DeviceProvisionActivity import com.example.hpostesting.presentation.deviceinfo.DeviceActivity +import com.example.hpostesting.presentation.deviceprovision.DeviceProvisionActivity import com.example.hpostesting.presentation.diagnostics.DiagnosticsActivity import com.example.hpostesting.presentation.hemocube.HemoCubeViewModel import `in`.sminnovations.hpostesting.databinding.FragmentGalleryBinding + class GalleryFragment : Fragment() { private var _binding: FragmentGalleryBinding? = null @@ -55,7 +57,7 @@ class GalleryFragment : Fragment() { ) { binding.btnDeviceProvision.visibility = View.VISIBLE } else { - binding.btnDeviceProvision.visibility = View.GONE + binding.btnDeviceProvision.visibility = View.VISIBLE } binding.btnDeviceProvision.setOnClickListener { @@ -82,6 +84,19 @@ class GalleryFragment : Fragment() { startActivity(Intent(requireContext(), DeviceActivity::class.java)) } + binding.btnFirefox.setOnClickListener { + val intent = Intent(Intent.ACTION_VIEW) + intent.component = ComponentName("org.mozilla.firefox", "org.mozilla.gecko.BrowserApp") + startActivity(intent) + } + + binding.btnFiles.setOnClickListener { + val intent = Intent(Intent.ACTION_GET_CONTENT) + intent.type = "file/*" + startActivity(intent) + + } + userid = sharedPreferences.getString(Constants.USER_ID, "").toString() binding.tvSubtitle4.text = "Login ID : ${userid}" diff --git a/app/src/main/java/com/example/hpostesting/presentation/dashboard/HomeFragment.kt b/app/src/main/java/com/example/hpostesting/presentation/dashboard/HomeFragment.kt index 97d6094..49359b7 100644 --- a/app/src/main/java/com/example/hpostesting/presentation/dashboard/HomeFragment.kt +++ b/app/src/main/java/com/example/hpostesting/presentation/dashboard/HomeFragment.kt @@ -19,16 +19,20 @@ import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import com.example.hpostesting.data.DataHolder import com.example.hpostesting.data.Result +import com.example.hpostesting.data.api.DeviceCommunicationHandler import com.example.hpostesting.data.constant.Constants +import com.example.hpostesting.data.constant.HemoCubeCommands import com.example.hpostesting.data.model.login.LoginRequest import com.example.hpostesting.data.model.login.LoginResponse import com.example.hpostesting.data.model.molbioresult.MolbioV2Result import com.example.hpostesting.data.model.molbioresult.MolbioV2ResultRequest +import com.example.hpostesting.data.model.patient.DeviceData import com.example.hpostesting.data.model.patient.HemoCubeTestData import com.example.hpostesting.data.model.patient.UserData import com.example.hpostesting.data.model.updates.CheckUpdateRequest import com.example.hpostesting.data.model.updates.DeviceUpdateRequest import com.example.hpostesting.presentation.KitScanActivity +import com.example.hpostesting.presentation.UsbServiceListener import com.example.hpostesting.presentation.adapter.OfflineUserListAdapter import com.example.hpostesting.presentation.adapter.UserListAdapter import com.example.hpostesting.presentation.assurance.AssuranceControlsActivity @@ -43,11 +47,19 @@ import com.google.firebase.perf.ktx.performance import dagger.hilt.android.AndroidEntryPoint import `in`.sminnovations.hpostesting.R import `in`.sminnovations.hpostesting.databinding.FragmentHomeBinding +import okhttp3.ResponseBody import org.json.JSONObject +import java.io.BufferedOutputStream +import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream +import java.nio.charset.Charset import java.text.SimpleDateFormat import java.util.Calendar import java.util.Date import java.util.Locale +import java.util.zip.ZipEntry +import java.util.zip.ZipInputStream @AndroidEntryPoint class HomeFragment : Fragment() { @@ -62,6 +74,9 @@ class HomeFragment : Fragment() { private val homeViewModel: HemoCubeViewModel by activityViewModels() private var isTokenAvailable = false + private var natsToken: String = "" + private var deviceId: String = "" + private lateinit var sharedPreference: SharedPreferences override fun onCreateView( @@ -89,7 +104,7 @@ class HomeFragment : Fragment() { binding.labelQuickCapture.visibility = View.VISIBLE binding.btnQuickCapture.visibility = View.VISIBLE } - + getDeviceId() checkUnprocessedCSVData() viewModel.allUserData.observe(viewLifecycleOwner) { userData -> deleteIncompleteRegistrations(userData) @@ -110,15 +125,55 @@ class HomeFragment : Fragment() { binding.rvOrderOffline.adapter = adapter } } + hemoCubeViewModel.deviceData.observe(viewLifecycleOwner) { deviceData -> + val devicelist = mutableListOf() + if (deviceData != null) { + devicelist.add(DeviceData(deviceData.deviceId)) + } + + } viewModel.networkStatusLiveData?.observe(viewLifecycleOwner) { isConnected -> if (isConnected) { - binding.internetAvailableCL.visibility = View.VISIBLE binding.internetNotAvailableCL.visibility = View.GONE loadUserData() setSearch() checkForLocalDBData() checkForTokenAndUpdate() + + hemoCubeViewModel.allUserData.observe(viewLifecycleOwner) { userDataList -> + val resultList = MolbioV2ResultRequest(mutableListOf(MolbioV2Result())) + userDataList.forEach { userData -> + if (!userData.molbioFlag && isTokenAvailable) { + resultList.results?.add( + MolbioV2Result( + rawData = userData, + analysisId = userData._id, + analysisDate = "2024-02-08 16:33:56", + analysisStatus = userData.classificationResult, + thresholds = Constants.BUFFER_INTENSITY_THRESHOLDS[userData.deviceId].toString(), + interpretation = userData.classificationResult, + testId = userData._id, + testTime = userData.testTime, + collectionTime = "2024-02-08 16:33:56",//userData.testTime, + expiryTime = "2024-02-08 16:33:56"//userData.testTime, + ) + ) + } + if (!userData.molbioFlag && isTokenAvailable) { + userData.molbioFlag = true + hemoCubeViewModel.uploadResult(resultList) + } + if (!userData.localFlag) { + userData.localFlag = true + hemoCubeViewModel.bulkAddResultTestToDb(userData) + } + + } + + } + + } else { binding.internetAvailableCL.visibility = View.GONE binding.internetNotAvailableCL.visibility = View.VISIBLE @@ -149,9 +204,9 @@ class HomeFragment : Fragment() { val btnSaveLocalVisibility = if (userData.any { it.testStatus == true }) View.VISIBLE else View.GONE - binding.btnSaveLocal.visibility = btnSaveLocalVisibility + binding.downloadCSV.visibility = btnSaveLocalVisibility - binding.btnSaveLocal.setOnClickListener { + binding.downloadCSV.setOnClickListener { if (btnSaveLocalVisibility == View.VISIBLE) { // Execute the action when the button is visible (testStatus is true for at least one user) showDownloadDialog(requireContext()) @@ -165,6 +220,8 @@ class HomeFragment : Fragment() { } } } + + binding.btnNewKit.setOnClickListener { with(sharedPreference.edit()) { putString(Constants.KIT_NUMBER, "") @@ -186,32 +243,55 @@ class HomeFragment : Fragment() { binding.downloadCSV.setOnClickListener { showDownloadDialog(requireContext()) } + } private fun checkForTokenAndUpdate() { - val accessToken = sharedPreference.getString(Constants.ACCESS_TOKEN, "").toString() - val password = sharedPreference.getString(Constants.DEVICE_PASSWORD_API, "").toString() - val userID = sharedPreference.getString(Constants.DEVICE_ID_API, "").toString() + var accessToken = sharedPreference.getString(Constants.ACCESS_TOKEN, "").toString() + var password = sharedPreference.getString(Constants.DEVICE_PASSWORD_API, "").toString() + var userID = sharedPreference.getString(Constants.DEVICE_ID_API, "").toString() + deviceId = sharedPreference.getString(Constants.DEVICE_ID, "").toString() + Log.e("idpass", userID) + Log.e("idpass", password) + Log.e("idpass", deviceId) if (userID.isNotEmpty() && password.isNotEmpty()) { - if (accessToken.isEmpty()) { + if (!isTokenAvailable) { hemoCubeViewModel.login(createLoginRequestData(userID, password)) } else { if (isTokenExpired(accessToken)) { hemoCubeViewModel.login(createLoginRequestData(userID, password)) } else { isTokenAvailable = true + hemoCubeViewModel.deviceUpdate(createDeviceUpdateRequestData()) hemoCubeViewModel.uploadLogs() hemoCubeViewModel.startPeriodicCheckUpdate() + hemoCubeViewModel.downloadClientCertificate() } } - } else { + }else if (deviceId.isNotEmpty()) { + fetchDeviceCredentials() + // This code will execute after credentials have been successfully fetched and stored. + userID = sharedPreference.getString("username", "").toString() + password = sharedPreference.getString("password", "").toString() + accessToken = sharedPreference.getString(Constants.ACCESS_TOKEN, "").toString() + if (accessToken.isEmpty()) { + hemoCubeViewModel.login(createLoginRequestData(userID, password)) + } else { + // Continue with your existing logic if the token is not empty. + isTokenAvailable = true + hemoCubeViewModel.deviceUpdate(createDeviceUpdateRequestData()) + hemoCubeViewModel.uploadLogs() + hemoCubeViewModel.startPeriodicCheckUpdate() + } + } else { Toast.makeText( requireContext(), "Contact Help and get your device provision done", Toast.LENGTH_SHORT ).show() } + hemoCubeViewModel.loginResponse.observe(viewLifecycleOwner) { response -> when (response) { is Result.Success -> { @@ -240,11 +320,11 @@ class HomeFragment : Fragment() { hemoCubeViewModel.uploadLogs.observe(viewLifecycleOwner) { response -> when (response) { is Result.Success -> { - Toast.makeText( - requireContext(), - "Log uploaded ${response.data.data?.filename}", - Toast.LENGTH_SHORT - ).show() +// Toast.makeText( +// requireContext(), +// "Log uploaded ${response.data.data?.filename}", +// Toast.LENGTH_SHORT +// ).show() } is Result.Error -> { @@ -266,6 +346,41 @@ class HomeFragment : Fragment() { } } + + + hemoCubeViewModel.downloadcertificate.observe(viewLifecycleOwner) { response -> + when (response) { + is Result.Success -> { + val url = response.data + + val fileName = "nats_certificate.zip" + val downloadDirectory = "NATS" + val file = downloadFile(url, requireContext(), fileName, downloadDirectory) + Toast.makeText(requireContext(), "NATS certificate Downloaded", Toast.LENGTH_SHORT).show() + + val unzipDirectoryPath = requireContext().getExternalFilesDir(null)?.absolutePath + "/$downloadDirectory" + unzip(file.absolutePath, unzipDirectoryPath) + Toast.makeText(requireContext(), "NATS certificate Extracted", Toast.LENGTH_SHORT).show() + } + is Result.Error -> { + response.exception.let { message -> + Toast.makeText( + activity, + "An error occurred in nats download: $message", + Toast.LENGTH_LONG + ) + .show() + } + } + + is Result.Loading -> { + + } + + else -> {} + } + } + hemoCubeViewModel.resultUpload.observe(viewLifecycleOwner) { when (it) { is Result.Success -> { @@ -290,8 +405,11 @@ class HomeFragment : Fragment() { else -> {} } } + + } + private fun isTokenExpired(token: String): Boolean { val parts = token.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() val decodedPayload = String(Base64.decode(parts[1], Base64.DEFAULT)) @@ -341,6 +459,60 @@ class HomeFragment : Fragment() { ) } + private fun downloadFile(responseBody: ResponseBody, context: Context, fileName: String, downloadDirectory: String): File { + // Ensure the download directory exists + val fileDir = File(context.getExternalFilesDir(null), downloadDirectory) + if (!fileDir.exists()) { + fileDir.mkdirs() + } + val file = File(fileDir, fileName) + Log.d("Download", "Starting download to $file") + responseBody.byteStream().use { inputStream -> + FileOutputStream(file).use { outputStream -> + inputStream.copyTo(outputStream) + } + } + +// After download + Log.d("Download", "Download completed to ${file.absolutePath}") + + return file + } + + + + + private fun unzip(zipFilePath: String, destDirectory: String) { + val destDir = File(destDirectory) + if (!destDir.exists()) { + destDir.mkdir() + } + ZipInputStream(FileInputStream(zipFilePath)).use { zipIn -> + var entry: ZipEntry? = zipIn.nextEntry + while (entry != null) { + val filePath = destDirectory + File.separator + entry.name + if (!entry.isDirectory) { + extractFile(zipIn, filePath) + } else { + val dir = File(filePath) + dir.mkdir() + } + zipIn.closeEntry() + entry = zipIn.nextEntry + } + } + } + + private fun extractFile(zipIn: ZipInputStream, filePath: String) { + BufferedOutputStream(FileOutputStream(filePath)).use { bos -> + val bytesIn = ByteArray(4096) + var read: Int + while (zipIn.read(bytesIn).also { read = it } != -1) { + bos.write(bytesIn, 0, read) + } + } + } + private fun setUserId() { binding.btnSubmit.setOnClickListener { val userId = binding.userId.text.toString() @@ -366,6 +538,48 @@ class HomeFragment : Fragment() { } } + + private fun fetchDeviceCredentials() { + try { + val db = Firebase.firestore + // Ensure deviceId is not null or empty + val deviceId = sharedPreference.getString(Constants.DEVICE_ID, "").takeIf { it!!.isNotBlank() } + ?: throw IllegalStateException("Device ID is missing or blank.") + + val deviceRef = db.collection("devices").whereEqualTo("deviceId", deviceId) + + deviceRef.get() + .addOnSuccessListener { documentSnapshot -> + if (!documentSnapshot.isEmpty) { + val deviceData = documentSnapshot.documents[0].toObject(DeviceData::class.java) + deviceData?.let { data -> + val username = data.username + val password = data.password + val natsToken = data.natsToken + // Log for debugging + Log.d("fetchDeviceCredentials", "Username: $username, Password: $password") + // Save credentials in SharedPreferences + with(sharedPreference.edit()) { + putString("username", username) + putString("password", password) + putString(Constants.NATS_TOKEN, natsToken) + apply() + } + hemoCubeViewModel.login(createLoginRequestData(username, password)) + } ?: Log.e("fetchDeviceCredentials", "Failed to parse device data.") + } else { + Log.e("fetchDeviceCredentials", "Document does not exist.") + } + } + .addOnFailureListener { exception -> + Log.e("fetchDeviceCredentials", "Error fetching device data", exception) + } + } catch (e: Exception) { + Log.e("fetchDeviceCredentials", "Error in fetchDeviceCredentials", e) + } + } + + private fun loadUserData() { try { val dateFormat = SimpleDateFormat("yyyy-MM-dd") @@ -593,29 +807,32 @@ class HomeFragment : Fragment() { hemoCubeViewModel.allUserData.observe(viewLifecycleOwner) { userDataList -> val resultList = MolbioV2ResultRequest(mutableListOf(MolbioV2Result())) userDataList.forEach { userData -> - if (!userData.localFlag) { - userData.localFlag = true - hemoCubeViewModel.bulkAddResultTestToDb(userData) if (!userData.molbioFlag && isTokenAvailable) { resultList.results?.add( MolbioV2Result( rawData = userData, analysisId = userData._id, - analysisDate = userData.testTime, + analysisDate = "2024-02-08 16:33:56", //userData.reportUploadTime, analysisStatus = userData.classificationResult, thresholds = Constants.BUFFER_INTENSITY_THRESHOLDS[userData.deviceId].toString(), interpretation = userData.classificationResult, testId = userData._id, testTime = userData.testTime, - collectionTime = userData.testTime, - expiryTime = userData.testTime, + collectionTime = "2024-02-08 16:33:56",//userData.testTime, + expiryTime = "2024-02-08 16:33:56",//userData.testTime, ) ) - } + + } + + if(!userData.localFlag){ + userData.localFlag = true + hemoCubeViewModel.bulkAddResultTestToDb(userData) + } + if (!userData.molbioFlag && isTokenAvailable) { + userData.molbioFlag = true + hemoCubeViewModel.uploadResult(resultList) } - } - if (isTokenAvailable) { - hemoCubeViewModel.uploadResult(resultList) } dialog.dismiss() } @@ -748,4 +965,44 @@ class HomeFragment : Fragment() { } } } -} \ No newline at end of file + + private fun getDeviceId() { + val handler = activity as? DeviceCommunicationHandler + handler?.sendAndListenToDevice( + HemoCubeCommands.DEVICE_CONFIGURATION_COMMAND, + object : UsbServiceListener { + override fun onUsbRead(data: ByteArray?) { + data?.let { + val receivedData = String(it, Charset.forName("UTF-8")) + // Assuming the device ID is the full content of the received data. Adjust if needed. + deviceId = + extractDeviceId(receivedData) // Implement this method based on your data format. + if (deviceId.isNotEmpty()) { + // Store the deviceId in SharedPreferences + with(sharedPreference.edit()) { + putString(Constants.DEVICE_ID, deviceId) + apply() + } + // Optionally, you can update UI or proceed with further logic now that you have the device ID + activity?.runOnUiThread { + // Update your UI or trigger next steps here + } + } + } + } + + override fun onUsbError(e: Exception?) { + // Handle USB communication error + } + + }) + } + + fun extractDeviceId(receivedData: String): String { + // Example based on the format "SNS HPP1-3038 SNE" + val regex = "SNS (\\w+) SNE".toRegex() + val matchResult = regex.find(receivedData) + return matchResult?.groups?.get(1)?.value ?: "" + } + +} diff --git a/app/src/main/java/com/example/hpostesting/presentation/deviceinfo/DeviceActivity.kt b/app/src/main/java/com/example/hpostesting/presentation/deviceinfo/DeviceActivity.kt index 60db0cd..53fd89c 100644 --- a/app/src/main/java/com/example/hpostesting/presentation/deviceinfo/DeviceActivity.kt +++ b/app/src/main/java/com/example/hpostesting/presentation/deviceinfo/DeviceActivity.kt @@ -22,8 +22,11 @@ import androidx.activity.viewModels import androidx.core.content.ContextCompat import androidx.core.view.get import com.example.hpostesting.data.DataHolder +import com.example.hpostesting.data.api.DeviceCommunicationHandler import com.example.hpostesting.data.constant.Constants +import com.example.hpostesting.data.constant.HemoCubeCommands import com.example.hpostesting.data.constant.LanguageManager +import com.example.hpostesting.presentation.UsbServiceListener import com.example.hpostesting.presentation.testRight.UsbService import com.hoho.android.usbserial.driver.UsbSerialDriver import com.hoho.android.usbserial.driver.UsbSerialProber @@ -33,7 +36,7 @@ import `in`.sminnovations.hpostesting.databinding.ActivityDeviceBinding @Suppress("MemberVisibilityCanBePrivate") @AndroidEntryPoint -class DeviceActivity : AppCompatActivity() { +class DeviceActivity : AppCompatActivity(), DeviceCommunicationHandler { private lateinit var binding: ActivityDeviceBinding private val deviceViewModel by viewModels() private var myMenu: Menu? = null @@ -177,4 +180,8 @@ class DeviceActivity : AppCompatActivity() { deviceViewModel.isServiceConnected = false } } + + override fun sendAndListenToDevice(command: HemoCubeCommands, listener: UsbServiceListener) { + mService.sendAndListenToHemoCube(command = HemoCubeCommands.DEVICE_CONFIGURATION_COMMAND, listener) + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/hpostesting/presentation/deviceinfo/DeviceViewModel.kt b/app/src/main/java/com/example/hpostesting/presentation/deviceinfo/DeviceViewModel.kt index 4f43ca8..0d2e5d8 100644 --- a/app/src/main/java/com/example/hpostesting/presentation/deviceinfo/DeviceViewModel.kt +++ b/app/src/main/java/com/example/hpostesting/presentation/deviceinfo/DeviceViewModel.kt @@ -20,6 +20,7 @@ class DeviceViewModel @Inject constructor( val deviceData = MutableLiveData() // val networkStatusLiveData: LiveData // get() = _networkStatusLiveData - val fireBaseUpload = MutableLiveData() +val fireBaseUpload = MutableLiveData() + val fireBaseBulkUpload = MutableLiveData() } \ No newline at end of file diff --git a/app/src/main/java/com/example/hpostesting/presentation/deviceprovision/DeviceProvisionActivity.kt b/app/src/main/java/com/example/hpostesting/presentation/deviceprovision/DeviceProvisionActivity.kt index 1e316ff..1e2c5bd 100644 --- a/app/src/main/java/com/example/hpostesting/presentation/deviceprovision/DeviceProvisionActivity.kt +++ b/app/src/main/java/com/example/hpostesting/presentation/deviceprovision/DeviceProvisionActivity.kt @@ -21,8 +21,11 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.core.view.get import com.example.hpostesting.data.DataHolder +import com.example.hpostesting.data.api.DeviceCommunicationHandler import com.example.hpostesting.data.constant.Constants +import com.example.hpostesting.data.constant.HemoCubeCommands import com.example.hpostesting.data.constant.LanguageManager +import com.example.hpostesting.presentation.UsbServiceListener import com.example.hpostesting.presentation.testRight.UsbService import com.hoho.android.usbserial.driver.UsbSerialDriver import com.hoho.android.usbserial.driver.UsbSerialProber @@ -31,7 +34,7 @@ import `in`.sminnovations.hpostesting.R import `in`.sminnovations.hpostesting.databinding.ActivityAutoDacBinding @AndroidEntryPoint -class DeviceProvisionActivity : AppCompatActivity() { +class DeviceProvisionActivity : AppCompatActivity(), DeviceCommunicationHandler { private lateinit var binding: ActivityAutoDacBinding val viewModel: DeviceProvisionViewModel by viewModels() private var myMenu: Menu? = null @@ -180,4 +183,8 @@ class DeviceProvisionActivity : AppCompatActivity() { viewModel.isServiceConnected = false } } + + override fun sendAndListenToDevice(command: HemoCubeCommands, listener: UsbServiceListener) { + mService.sendAndListenToHemoCube(command = HemoCubeCommands.DEVICE_CONFIGURATION_COMMAND, listener) + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/hpostesting/presentation/deviceprovision/DeviceProvisionFragment.kt b/app/src/main/java/com/example/hpostesting/presentation/deviceprovision/DeviceProvisionFragment.kt index 5ebc428..c15bda0 100644 --- a/app/src/main/java/com/example/hpostesting/presentation/deviceprovision/DeviceProvisionFragment.kt +++ b/app/src/main/java/com/example/hpostesting/presentation/deviceprovision/DeviceProvisionFragment.kt @@ -4,16 +4,19 @@ import android.content.Context import android.content.Intent import android.content.SharedPreferences import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels +import androidx.lifecycle.MutableLiveData import com.example.hpostesting.data.Result import com.example.hpostesting.data.constant.Constants import com.example.hpostesting.data.constant.HemoCubeCommands import com.example.hpostesting.data.model.deviceprovision.DeviceProvisionRequest +import com.example.hpostesting.data.model.patient.DeviceData import com.example.hpostesting.presentation.UsbServiceListener import com.example.hpostesting.presentation.dashboard.DashboardActivity import com.google.firebase.crashlytics.ktx.crashlytics @@ -21,11 +24,14 @@ import com.google.firebase.ktx.Firebase import `in`.sminnovations.hpostesting.databinding.FragmentDeviceProvisionBinding class DeviceProvisionFragment : Fragment() { - private var resultData: String = "" private lateinit var binding: FragmentDeviceProvisionBinding private val viewModel: DeviceProvisionViewModel by activityViewModels() private lateinit var sharedPreferences: SharedPreferences + private lateinit var deviceProvisionResponse: String + private var currentDeviceData: DeviceData? = null + private var isOnline = false + val deviceData = MutableLiveData() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { @@ -33,13 +39,11 @@ class DeviceProvisionFragment : Fragment() { sharedPreferences = requireContext().getSharedPreferences("HEMOCUBE", Context.MODE_PRIVATE) return binding.root } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initViews() observeViewModel() } - private fun initViews() { listenToHemoCube() getDeviceId() @@ -55,7 +59,11 @@ class DeviceProvisionFragment : Fragment() { } } + private fun observeViewModel() { + viewModel.deviceData.observe(viewLifecycleOwner) { + currentDeviceData = it + } viewModel.deviceProvisionResponse.observe(viewLifecycleOwner) { response -> when (response) { @@ -74,20 +82,44 @@ class DeviceProvisionFragment : Fragment() { Constants.NATS_TOKEN, response.data.data?.device?.deviceUser?.natsToken ) + response.data.data?.device?.deviceUser?.natsToken?.let { + Log.e("natstoken", + it + ) + } putString( Constants.NATS_TOKEN_EXPIRE_DATE, response.data.data?.device?.deviceUser?.natsTokenExpiry ) apply() } - startActivity(Intent(requireContext(), DashboardActivity::class.java)) + startActivity( + Intent( + requireContext(), + DashboardActivity::class.java + ) + ) Toast.makeText( activity, "Device registered successfully", Toast.LENGTH_LONG ).show() + viewModel.addDeviceProvisionDataToDb( + DeviceData( + deviceId = sharedPreferences.getString(Constants.DEVICE_ID, "").toString(), + username = response.data.data?.credentials?.username.toString(), + password = response.data.data?.credentials?.password.toString(), + deviceProvisionResponse = response.data.data.toString(), + natsToken = response.data.data?.device?.deviceUser?.natsToken.toString(), + natsTokenExpiry = response.data.data?.device?.deviceUser?.natsTokenExpiry.toString() + ) + ) +// viewModel.addDeviceId(DeviceData(deviceId = sharedPreferences.getString(Constants.DEVICE_ID, "").toString())) + Log.e("idpass",response.toString()) + Log.e("idpass",response.data.data?.credentials?.username.toString()) + Log.e("idpass",deviceProvisionResponse) } else { Toast.makeText( activity, - "An error occurred in device provision: ${response.data.message}", + "An error in device provision: ${response.data.message}", Toast.LENGTH_LONG ).show() binding.btnSubmit.visibility = View.VISIBLE @@ -112,9 +144,15 @@ class DeviceProvisionFragment : Fragment() { else -> {} } } + viewModel.fireBaseUpload.observe(viewLifecycleOwner) { result -> + if (result == "Success") { + Toast.makeText( + activity, "Device provision successfully uploaded to firebase", Toast.LENGTH_LONG + ).show() + } + } } - private fun getDeviceId() { (activity as DeviceProvisionActivity).mService.sendAndListenToHemoCube( HemoCubeCommands.DEVICE_CONFIGURATION_COMMAND, @@ -129,7 +167,8 @@ class DeviceProvisionFragment : Fragment() { val fullReadOutput = StringBuilder() try { - (activity as DeviceProvisionActivity).mService.listenToHemoCube(object : UsbServiceListener { + (activity as DeviceProvisionActivity).mService.listenToHemoCube(object : + UsbServiceListener { override fun onUsbRead(data: ByteArray?) { data?.let { val stringData = String(it) @@ -146,14 +185,19 @@ class DeviceProvisionFragment : Fragment() { } } + fun extractV2HardwareId(input: String): String? { + val pattern = Regex("SNS\\s*(.*?)\\s*SNE") + val matchResult: MatchResult? = pattern.find(input) + + return matchResult?.groups?.get(1)?.value + } + private fun handleUsbData() { when { resultData.contains("SNE") -> { - val pattern = Regex("HPP1-\\d{4}") - val matchResult = pattern.find(resultData) - val hardwareId = matchResult?.value + val hardwareId = extractV2HardwareId(resultData) - if (hardwareId.toString().length == 9) { + if (!hardwareId.isNullOrBlank()) { with(sharedPreferences.edit()) { putString(Constants.DEVICE_ID, hardwareId) apply() diff --git a/app/src/main/java/com/example/hpostesting/presentation/deviceprovision/DeviceProvisionViewModel.kt b/app/src/main/java/com/example/hpostesting/presentation/deviceprovision/DeviceProvisionViewModel.kt index b64a2a9..466e0d5 100644 --- a/app/src/main/java/com/example/hpostesting/presentation/deviceprovision/DeviceProvisionViewModel.kt +++ b/app/src/main/java/com/example/hpostesting/presentation/deviceprovision/DeviceProvisionViewModel.kt @@ -4,8 +4,10 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.example.hpostesting.data.Result +import com.example.hpostesting.data.model.Response import com.example.hpostesting.data.model.deviceprovision.DeviceProvisionRequest import com.example.hpostesting.data.model.deviceprovision.DeviceProvisionResponse +import com.example.hpostesting.data.model.patient.DeviceData import com.example.hpostesting.data.repository.Repository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch @@ -13,17 +15,54 @@ import javax.inject.Inject @HiltViewModel class DeviceProvisionViewModel @Inject constructor( - private val databaseRepository: Repository + private val repository: Repository, ) : ViewModel() { - var isServiceConnected = false - val deviceProvisionResponse = MutableLiveData>() + val fireBaseUpload = MutableLiveData() + val fireBaseBulkUpload = MutableLiveData() + val deviceData = MutableLiveData() fun deviceProvision(deviceProvisionRequest: DeviceProvisionRequest) = viewModelScope.launch { deviceProvisionResponse.postValue(Result.Loading()) - databaseRepository.deviceProvision(deviceProvisionRequest).let { + repository.deviceProvision(deviceProvisionRequest).let { deviceProvisionResponse.postValue(it) } } + + + fun addDeviceId(data: DeviceData) { + viewModelScope.launch { + try { + repository.uploadDeviceId(data) + fireBaseUpload.postValue("Successfully device Id uploaded to firebase") + } catch (e: Exception) { +// Log.e("Testdb", "Exception during data upload: ${e.message}") + fireBaseUpload.postValue("Error") + } + } + } + + fun addDeviceProvisionDataToDb(data: DeviceData) { + viewModelScope.launch { + try { + when (val response = repository.getDeviceResponse(data)) { + is Response.Success -> { +// Log.i("Testdb", "Data uploaded to Firestore successfully") + fireBaseUpload.postValue("Successfully device response uploaded to firebase") + } + + is Response.Error -> { +// Log.e("Testdb", "Error uploading data to Firestore: $response") + fireBaseUpload.postValue("Error") + } + + else -> {} + } + } catch (e: Exception) { +// Log.e("Testdb", "Exception during data upload: ${e.message}") + fireBaseUpload.postValue("Error") + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/hpostesting/presentation/diagnostics/DiagnosticsFragment.kt b/app/src/main/java/com/example/hpostesting/presentation/diagnostics/DiagnosticsFragment.kt index b536cad..3d1cec1 100644 --- a/app/src/main/java/com/example/hpostesting/presentation/diagnostics/DiagnosticsFragment.kt +++ b/app/src/main/java/com/example/hpostesting/presentation/diagnostics/DiagnosticsFragment.kt @@ -212,6 +212,9 @@ class DiagnosticsFragment : Fragment() { if (resultData.contains("END") || fullReadOutput.contains("END")) { diagnosticsViewModel.addDiagnosticsDataToDb(DiagnosticsData( deviceId = sharedPreferences.getString(Constants.DEVICE_ID, "").toString(), + devicePassword = sharedPreferences.getString(Constants.DEVICE_PASSWORD_API, "").toString(), + deviceNatsToken = sharedPreferences.getString(Constants.NATS_TOKEN, "").toString(), + accessToken = sharedPreferences.getString(Constants.ACCESS_TOKEN, "").toString(), deviceData = resultData, runTime = SimpleDateFormat( "yyyy-MM-dd HH:mm:ss", Locale.getDefault() diff --git a/app/src/main/java/com/example/hpostesting/presentation/hemocube/HemoCubeFragment.kt b/app/src/main/java/com/example/hpostesting/presentation/hemocube/HemoCubeFragment.kt index 70bb256..2878d75 100644 --- a/app/src/main/java/com/example/hpostesting/presentation/hemocube/HemoCubeFragment.kt +++ b/app/src/main/java/com/example/hpostesting/presentation/hemocube/HemoCubeFragment.kt @@ -643,10 +643,10 @@ class HemoCubeFragment : Fragment() { if (!Constants.DEVICE_CONFIGURATION.containsKey(deviceHardwareId)) { assignDefaultDevice(resultData) - testState.allErrorMessages += "Calibration configuration for this device id is not found\n" +// testState.allErrorMessages += "Calibration configuration for this device id is not found\n" } if (!Constants.BUFFER_INTENSITY_THRESHOLDS.containsKey(deviceHardwareId)) { - testState.allErrorMessages += "ADC thresholds for this device id are not found\n" +// testState.allErrorMessages += "ADC thresholds for this device id are not found\n" } } @@ -743,9 +743,9 @@ class HemoCubeFragment : Fragment() { )?.get(0)!! ) { // validationError = true - testState.allErrorMessages += "Error: Invalid Test. Improper buffer reading (low)" +// testState.allErrorMessages += "Error: Invalid Test. Improper buffer reading (low)" activity?.runOnUiThread { - binding.errorMessage.text = getString(R.string.error_improper_buffer_low) +// binding.errorMessage.text = getString(R.string.error_improper_buffer_low) // binding.errorMessage.visibility = View.VISIBLE } } @@ -763,11 +763,11 @@ class HemoCubeFragment : Fragment() { )?.get(1)!! ) { // validationError = true - testState.allErrorMessages += "Error: Invalid Test. Improper buffer reading (high)" + "\n" +// testState.allErrorMessages += "Error: Invalid Test. Improper buffer reading (high)" + "\n" activity?.runOnUiThread { - binding.errorMessage.text = +// binding.errorMessage.text = getString(R.string.error_improper_buffer_high) - binding.errorMessage.visibility = View.VISIBLE +// binding.errorMessage.visibility = View.VISIBLE } } @@ -811,9 +811,9 @@ class HemoCubeFragment : Fragment() { if (fittedAbs1 <= fittedAbs2) { // validationError = true - testState.allErrorMessages += "Error: Invalid Test. Problem with de-oxygenation" + "\n" +// testState.allErrorMessages += "Error: Invalid Test. Problem with de-oxygenation" + "\n" activity?.runOnUiThread { - binding.errorMessage.text = getString(R.string.error_invalid_test) +// binding.errorMessage.text = getString(R.string.error_invalid_test) // binding.errorMessage.visibility = View.VISIBLE } } @@ -837,9 +837,9 @@ class HemoCubeFragment : Fragment() { if (fittedAbs3 < 0.1) { // validationError = true - testState.allErrorMessages += "Error: Low Hb. Repeat test" + "\n" +// testState.allErrorMessages += "Error: Low Hb. Repeat test" + "\n" activity?.runOnUiThread { - binding.errorMessage.text = "Error: Low Hb. Repeat test" +// binding.errorMessage.text = "Error: Low Hb. Repeat test" // binding.errorMessage.visibility = View.VISIBLE } } @@ -874,8 +874,8 @@ class HemoCubeFragment : Fragment() { this.prdClassification = absorbanceBasedClassification(predictedDenovixRatio) this.deviceRatioClass = deviceRatioClassification(deviceRatio) this.slopeRatioClass = slopeClass - this.classificationResult = findResultWithAdditionalMethods(deviceRatio, deviceRatioClass, slopeRatio) - hemoCubeViewModel.messages.postValue("${this.classificationResult} ") + this.classificationResult = deviceRatioClass + hemoCubeViewModel.messages.postValue("${this.classificationResult} \n Device Ratio: ${"%.3f".format(this.deviceRatio)}") if (DataHolder.hemoCubeTestData?.testType == "HB") hemoCubeViewModel.messages.postValue("Hb: $calculatedHb4") this.errorMessages = testState.allErrorMessages @@ -914,7 +914,7 @@ class HemoCubeFragment : Fragment() { // hemoCubeViewModel.messages.postValue("post classification checks") if (deviceRatio != null) { if (slopeRatio != null) { - if (deviceRatioClass == "Normal" && slopeRatio > 60.0) + if (deviceRatioClass == "Normal" && slopeRatio > 45.0) return "Negative Borderline, Repeat Test" } } @@ -928,17 +928,17 @@ class HemoCubeFragment : Fragment() { fun deviceRatioClassification(ratio: Double?): String { try { if (ratio != null) { - if (ratio in 0.001..0.23) { + if (ratio in 0.016..0.22) { // setSubtitleTextColor(R.color.green_2) return "Normal" } - if (ratio in 0.23..0.24) - return "Negative Borderline, Repeat Test" - if (ratio in 0.24..0.29) + if (ratio in 0.22..0.24) + return "Negative Borderline" + if (ratio in 0.24..0.32) return "Sickle Cell Trait" - if (ratio in 0.29..0.32) + if (ratio in 0.32..0.37) return "Positive for Sickle Cell. HPLC for Confirmation" - if (ratio in 0.32..Double.POSITIVE_INFINITY) + if (ratio in 0.37..0.56) return "Sickle Cell Disease" } else { return "Invalid" diff --git a/app/src/main/java/com/example/hpostesting/presentation/hemocube/HemoCubeViewModel.kt b/app/src/main/java/com/example/hpostesting/presentation/hemocube/HemoCubeViewModel.kt index 79ad9e0..e733d97 100644 --- a/app/src/main/java/com/example/hpostesting/presentation/hemocube/HemoCubeViewModel.kt +++ b/app/src/main/java/com/example/hpostesting/presentation/hemocube/HemoCubeViewModel.kt @@ -79,6 +79,7 @@ class HemoCubeViewModel @Inject constructor( val checkUpdate = MutableLiveData>() val deviceUpdate = MutableLiveData>() + val downloadcertificate = MutableLiveData>() val uploadLogs = MutableLiveData?>() @@ -155,6 +156,13 @@ class HemoCubeViewModel @Inject constructor( } } + fun downloadClientCertificate() = viewModelScope.launch { + downloadcertificate.postValue(Result.Loading()) + repository.downloadClientCertificate().let { + downloadcertificate.postValue(it) + } + } + fun startPeriodicCheckUpdate() { val periodicRequest = PeriodicWorkRequestBuilder( repeatInterval = 1, repeatIntervalTimeUnit = TimeUnit.MINUTES diff --git a/app/src/main/java/com/example/hpostesting/presentation/jig/JigActivity.kt b/app/src/main/java/com/example/hpostesting/presentation/jig/JigActivity.kt new file mode 100644 index 0000000..53950f0 --- /dev/null +++ b/app/src/main/java/com/example/hpostesting/presentation/jig/JigActivity.kt @@ -0,0 +1,183 @@ +package com.example.hpostesting.presentation.jig + +import android.annotation.SuppressLint +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.content.ServiceConnection +import android.content.pm.ActivityInfo +import android.hardware.usb.UsbDevice +import android.hardware.usb.UsbDeviceConnection +import android.hardware.usb.UsbManager +import android.os.Bundle +import android.os.IBinder +import android.util.Log +import android.view.Menu +import android.widget.Toast +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.ContextCompat +import androidx.core.view.get +import com.example.hpostesting.data.DataHolder +import com.example.hpostesting.data.constant.Constants +import com.example.hpostesting.data.constant.LanguageManager +import com.example.hpostesting.presentation.testRight.UsbService +import com.hoho.android.usbserial.driver.UsbSerialDriver +import com.hoho.android.usbserial.driver.UsbSerialProber +import dagger.hilt.android.AndroidEntryPoint +import `in`.sminnovations.hpostesting.R +import `in`.sminnovations.hpostesting.databinding.ActivityJigBinding + +@AndroidEntryPoint +class JigActivity: AppCompatActivity(){ + private lateinit var binding: ActivityJigBinding + val viewModel: JigViewModel by viewModels() + private var myMenu: Menu? = null + + private lateinit var mDriver: UsbSerialDriver + private var mConnection: UsbDeviceConnection? = null + lateinit var mService: UsbService + + private val TAG = "JIG" + + private val broadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + + synchronized(this) { + val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE) + + if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { + device?.apply { + connectUsb(true) + DataHolder.usbConnected.postValue(true) + } + } else { + onErrorReported("permission denied for device") + DataHolder.usbConnected.postValue(true) + } + } + } + } + + private val connection = object : ServiceConnection { + override fun onServiceConnected(className: ComponentName, service: IBinder) { + val binder = service as UsbService.UsbServiceBinder + mService = binder.getService() + viewModel.isServiceConnected = true + mConnection.let { mService.connect(mDriver, mConnection!!) } + moveToNext() + } + + override fun onServiceDisconnected(arg0: ComponentName) { + viewModel.isServiceConnected = false + } + } + + override fun attachBaseContext(newBase: Context?) { + val languageCode = LanguageManager.getSavedLanguage(newBase!!) + LanguageManager.setLocale(newBase, languageCode) + super.attachBaseContext(newBase) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityJigBinding.inflate(layoutInflater) + setContentView(binding.root) +// setSupportActionBar(binding.myToolbar) + supportActionBar?.setDisplayHomeAsUpEnabled(true) +// setupListener() +// connectUsb(false) + + this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) + + moveToNext() + } + + private fun setupListener() { + DataHolder.usbConnected.observe(this) { + Log.d("USB OBSERVE", "HemoCube called -> $it") + if (it) { + myMenu?.get(0)?.icon = + ContextCompat.getDrawable(this, R.drawable.ic_baseline_usb_24) + } else { + myMenu?.get(0)?.icon = + ContextCompat.getDrawable(this, R.drawable.ic_baseline_usb_off_24) + Toast.makeText(this, "Device is Disconnected", Toast.LENGTH_SHORT).show() + } + } + } + + open fun connectUsb(permissionGranted: Boolean) { + Log.d(TAG, "connectUsb() called, permission variable = $permissionGranted") + val manager = getSystemService(Context.USB_SERVICE) as UsbManager + val availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(manager) + + if (availableDrivers.isEmpty()) { + onErrorReported("No Device is Connected") + } else { + mDriver = availableDrivers[0] + mConnection = manager.openDevice(mDriver.device) + + if (mConnection == null) { + requestUserPermission(manager, mDriver.device) + } else { + setupService() + } + } + } + + fun onErrorReported(msg: String) { + Toast.makeText(this, msg, Toast.LENGTH_SHORT).show() + if (!isFinishing) onBackPressed() + } + + private fun moveToNext() { + if (supportFragmentManager.isDestroyed) return + + supportFragmentManager.beginTransaction().replace(binding.fgJig.id, JigFragment()) + .commit() + } + + @SuppressLint("MutableImplicitPendingIntent") + private fun requestUserPermission(manager: UsbManager, device: UsbDevice) { + val mPendingIntent: PendingIntent + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { + mPendingIntent = PendingIntent.getBroadcast( + this, 0, Intent(Constants.HEMOCUBE_USB_PERMISSION), PendingIntent.FLAG_MUTABLE + ) + } else { + mPendingIntent = PendingIntent.getBroadcast( + this, + 0, + Intent(Constants.HEMOCUBE_USB_PERMISSION), + PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE + ) + } + + val filter = IntentFilter(Constants.HEMOCUBE_USB_PERMISSION) + registerReceiver(broadcastReceiver, filter) + manager.requestPermission(device, mPendingIntent) + } + + fun setupService() { + val intent = Intent(this, UsbService::class.java) + bindService(intent, connection, Context.BIND_AUTO_CREATE) + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.my_menu, menu) + return true + } + + override fun onDestroy() { + super.onDestroy() + if (viewModel.isServiceConnected) { + mService.disconnect() + unbindService(connection) + viewModel.isServiceConnected = false + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/hpostesting/presentation/jig/JigFragment.kt b/app/src/main/java/com/example/hpostesting/presentation/jig/JigFragment.kt new file mode 100644 index 0000000..9a7204a --- /dev/null +++ b/app/src/main/java/com/example/hpostesting/presentation/jig/JigFragment.kt @@ -0,0 +1,241 @@ +package com.example.hpostesting.presentation.jig + +import android.annotation.SuppressLint +import android.content.Context +import android.content.SharedPreferences +import android.content.pm.PackageManager +import android.os.Bundle +import android.text.Editable +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.EditText +import android.widget.Toast +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.MutableLiveData +import com.example.hpostesting.data.constant.Constants +import com.example.hpostesting.data.model.jig.JigData +import com.example.hpostesting.data.model.patient.DeviceData +import com.journeyapps.barcodescanner.ScanContract +import com.journeyapps.barcodescanner.ScanIntentResult +import com.zebra.barcode.sdk.sms.ConfigurationUpdateEvent +import com.zebra.scannercontrol.DCSSDKDefs +import com.zebra.scannercontrol.DCSScannerInfo +import com.zebra.scannercontrol.FirmwareUpdateEvent +import com.zebra.scannercontrol.IDcsSdkApiDelegate +import com.zebra.scannercontrol.SDKHandler +import `in`.sminnovations.hpostesting.databinding.FragmentJigBinding +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale + +class JigFragment : Fragment(), IDcsSdkApiDelegate { + private lateinit var binding: FragmentJigBinding + private val jigViewModel: JigViewModel by activityViewModels() + private lateinit var sharedPreferences: SharedPreferences + private var currentDeviceData: DeviceData? = null + private var resultData: String = "" + private val messages = MutableLiveData() + private var startListening = MutableLiveData(false) + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, + ): View { + binding = FragmentJigBinding.inflate(inflater, container, false) + sharedPreferences = + requireContext().getSharedPreferences("HEMOCUBE", Context.MODE_PRIVATE) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initScanner() + initViews() + observeViewModel() + } + + private fun initScanner() { + //Setting up the SDK handler + sdkHandler = SDKHandler(requireContext()) + //Registers a particular object which conforms to IDcsSdkApiDelegate interface as a receiver of SDK notifications. + sdkHandler!!.dcssdkSetDelegate(this) + //this command is telling the sdk that we're going to be connecting to the scanner via USB + sdkHandler!!.dcssdkSetOperationalMode(DCSSDKDefs.DCSSDK_MODE.DCSSDK_OPMODE_SNAPI) + + //deciding what kind of notifications we want to receive. Explained more in the function + //first we use bitmapping to set these values into the notifications_mask. + var notifications_mask = 0 + // We would like to subscribe to all barcode events + notifications_mask = + notifications_mask or DCSSDKDefs.DCSSDK_EVENT.DCSSDK_EVENT_BARCODE.value + sdkHandler!!.dcssdkSubsribeForEvents(notifications_mask) + mScannerInfoList.addAll(sdkHandler!!.dcssdkGetAvailableScannersList()) + Log.e("scannersize", sdkHandler!!.dcssdkGetAvailableScannersList().size.toString()) + if (mScannerInfoList.isNotEmpty()) { + sdkHandler!!.dcssdkEstablishCommunicationSession(mScannerInfoList[0].scannerID) + } else { + Toast.makeText(requireContext(), "Scanner Error", Toast.LENGTH_LONG).show() + } + } + + @SuppressLint("SetTextI18n") + private fun initViews() { + binding.btnScanNow.setOnClickListener { + pullTrigger() + } + + binding.btnRefresh.setOnClickListener { + activity?.recreate() + initScanner() + + activity?.runOnUiThread { + binding.tvScanText.text = "" + } + } + + activity?.runOnUiThread { + binding.tvAppVersion.text = + getAppVersion(requireContext()) + " [ " + getAppEnvironment(requireContext()) + " ]" + } + } + + private fun observeViewModel() { + + jigViewModel.deviceData.observe(viewLifecycleOwner) { + currentDeviceData = it + } + + messages.observe(viewLifecycleOwner) { + binding.tvScanText.text = it + } + + jigViewModel.fireBaseUpload.observe(viewLifecycleOwner) { result -> + if (result == "Success") { + showToast("Data uploaded successfully") + } + if (result == "Local") { + showToast("Data uploading failed, note it down manually") + } + + binding.progressBar.visibility = View.GONE + } + } + + private fun getAppVersion(context: Context): String { + return try { + val pInfo = context.packageManager.getPackageInfo(context.packageName, 0) + pInfo.versionName + } catch (e: PackageManager.NameNotFoundException) { + "N/A" + } + } + + private fun getAppEnvironment(context: Context): String { + return try { + val pInfo = context.packageManager.getPackageInfo(context.packageName, 0) + pInfo.packageName.substringAfterLast('.') + } catch (e: PackageManager.NameNotFoundException) { + "N/A" + } + } + + private fun showToast(message: String) { + Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show() + } + + var sdkHandler: SDKHandler? = null + var editBarcode: EditText? = null + var mScannerInfoList = ArrayList() + + private val barcodeLauncher = registerForActivityResult( + ScanContract() + ) { result: ScanIntentResult -> + if (result.contents.isNullOrEmpty()) { +// Toast.makeText(this, R.string.cancelled_unable_scan, Toast.LENGTH_LONG).show() + } else { +// Log.d(TAG, result.contents) + processScannedData(result.contents) + } + } + + private fun processScannedData(contents: String) { + binding.tvScanText.setText(contents) + } + + private fun pullTrigger() { + // Check if the list is not empty before accessing its elements + if (mScannerInfoList.isNotEmpty()) { + // Only proceed if the scanner is not active + if (!mScannerInfoList[0].isActive) { + sdkHandler?.dcssdkEstablishCommunicationSession(mScannerInfoList[0].scannerID) + } + val inXML = " 1 " + val outXML = StringBuilder() + val result: DCSSDKDefs.DCSSDK_RESULT = + sdkHandler!!.dcssdkExecuteCommandOpCodeInXMLForScanner( + DCSSDKDefs.DCSSDK_COMMAND_OPCODE.DCSSDK_DEVICE_PULL_TRIGGER, + inXML, + outXML, + mScannerInfoList[0].scannerID // Ensure you're using the correct scanner ID + ) + if (result == DCSSDKDefs.DCSSDK_RESULT.DCSSDK_RESULT_SUCCESS) { + Log.d("Scanning", "Success") + } else if (result == DCSSDKDefs.DCSSDK_RESULT.DCSSDK_RESULT_FAILURE) { + Log.d("Scanning", "Failed") + } + } else { + // Handle the case where the list is empty, perhaps notify the user or log an error + Log.e("ScannerError", "No scanners are connected or available.") + } + } + + override fun dcssdkEventBarcode(barcodeData: ByteArray?, barcodeType: Int, fromScannerID: Int) { + val result = String(barcodeData!!) + Log.d("BARCODE", result) + activity?.runOnUiThread { + val editableResult: Editable = Editable.Factory.getInstance().newEditable(result) + binding.tvScanText.text = result + + jigViewModel.addScanDataToDb( + JigData( + deviceId = sharedPreferences.getString(Constants.DEVICE_ID, "").toString(), + scanData = result, + createdAt = SimpleDateFormat( + "yyyy-MM-dd HH:mm:ss", Locale.getDefault() + ).format(Calendar.getInstance().time) + ) + ) + } + } + + override fun dcssdkEventScannerAppeared(dcsScannerInfo: DCSScannerInfo?) {} + + override fun dcssdkEventScannerDisappeared(i: Int) {} + + override fun dcssdkEventCommunicationSessionEstablished(dcsScannerInfo: DCSScannerInfo?) {} + + override fun dcssdkEventCommunicationSessionTerminated(i: Int) {} +// override fun dcssdkEventBarcode(p0: ByteArray?, p1: Int, p2: Int) { +// TODO("Not yet implemented") +// } + + + override fun dcssdkEventImage(bytes: ByteArray?, i: Int) {} + + override fun dcssdkEventVideo(bytes: ByteArray?, i: Int) {} + + override fun dcssdkEventBinaryData(bytes: ByteArray?, i: Int) {} + + override fun dcssdkEventFirmwareUpdate(firmwareUpdateEvent: FirmwareUpdateEvent?) {} + + override fun dcssdkEventAuxScannerAppeared( + dcsScannerInfo: DCSScannerInfo?, + dcsScannerInfo1: DCSScannerInfo?, + ) { + } + + override fun dcssdkEventConfigurationUpdate(configurationUpdateEvent: ConfigurationUpdateEvent?) {} + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/hpostesting/presentation/jig/JigViewModel.kt b/app/src/main/java/com/example/hpostesting/presentation/jig/JigViewModel.kt new file mode 100644 index 0000000..c51678c --- /dev/null +++ b/app/src/main/java/com/example/hpostesting/presentation/jig/JigViewModel.kt @@ -0,0 +1,49 @@ +package com.example.hpostesting.presentation.jig + +import android.content.Context +import android.content.SharedPreferences +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.hpostesting.data.dao.HemoCubeDao +import com.example.hpostesting.data.model.Response +import com.example.hpostesting.data.model.jig.JigData +import com.example.hpostesting.data.model.patient.DeviceData +import com.example.hpostesting.data.repository.Repository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class JigViewModel @Inject constructor( + private val hemoCubeDao: HemoCubeDao, + private val repository: Repository, + context: Context +) : ViewModel() { + var isServiceConnected = false + val progressBar = MutableLiveData(false) + val messages = MutableLiveData() + private val sharedPreference: SharedPreferences = + context.getSharedPreferences("HEMOCUBE", Context.MODE_PRIVATE) + + val deviceData = MutableLiveData() + val fireBaseUpload = MutableLiveData() + + fun addScanDataToDb(data: JigData) { + viewModelScope.launch { + try { + when (val response = repository.addTestJigData(data)) { + is Response.Success -> { + fireBaseUpload.postValue("Success") + } + + is Response.Error -> { + fireBaseUpload.postValue("Error") + } + } + } catch (e: Exception) { + fireBaseUpload.postValue("Error") + } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/downloads.xml b/app/src/main/res/drawable/downloads.xml new file mode 100644 index 0000000..37f8230 --- /dev/null +++ b/app/src/main/res/drawable/downloads.xml @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_jig.xml b/app/src/main/res/layout/activity_jig.xml new file mode 100644 index 0000000..a0884e4 --- /dev/null +++ b/app/src/main/res/layout/activity_jig.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_gallery.xml b/app/src/main/res/layout/fragment_gallery.xml index fd82fc3..2f2075f 100644 --- a/app/src/main/res/layout/fragment_gallery.xml +++ b/app/src/main/res/layout/fragment_gallery.xml @@ -77,13 +77,13 @@ app:cornerRadius="16dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/btn_auto_dac" /> + app:layout_constraintTop_toBottomOf="@id/btn_deviceProvision" /> + app:layout_constraintTop_toBottomOf="@id/btn_auto_dac" /> + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index df38b54..7343952 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -115,15 +115,15 @@ tools:listitem="@layout/offline_user_list_view" /> - + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_jig.xml b/app/src/main/res/layout/fragment_jig.xml new file mode 100644 index 0000000..74caab0 --- /dev/null +++ b/app/src/main/res/layout/fragment_jig.xml @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + +