diff --git a/.idea/androidTestResultsUserPreferences.xml b/.idea/androidTestResultsUserPreferences.xml
new file mode 100644
index 0000000..8997d1b
--- /dev/null
+++ b/.idea/androidTestResultsUserPreferences.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 1ccc02f..80bbd6a 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -58,5 +58,7 @@ dependencies {
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation 'androidx.recyclerview:recyclerview:1.2.0'
+ implementation group: 'commons-codec', name: 'commons-codec', version: '1.16.0'
+
implementation "com.android.volley:volley:1.2.1"
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/sensortestingapp/DecoderIaq.kt b/app/src/main/java/com/example/sensortestingapp/DecoderIaq.kt
new file mode 100644
index 0000000..abfb42b
--- /dev/null
+++ b/app/src/main/java/com/example/sensortestingapp/DecoderIaq.kt
@@ -0,0 +1,102 @@
+package com.example.sensortestingapp
+
+import org.apache.commons.codec.DecoderException
+import org.apache.commons.codec.binary.Hex
+import kotlin.math.roundToInt
+
+object DecoderIaq {
+ private const val INVALID_CO2 = 16383
+ private const val INVALID_VOC = 2047
+ private const val INVALID_HUMIDITY = 1023
+ private const val INVALID_TEMPERATURE = 2047
+ private const val INVALID_PRESSURE = 65535
+ private const val INVALID_OCCUPANCY = 3
+ private const val INVALID_PM25 = 1023
+ private const val INVALID_PM10 = 1023
+ fun parseMeasurement(input: String): Measurement {
+ val measurement = Measurement()
+ val inputBytes = hexStringToByteArray(input)
+ val msgType = inputBytes[0].toInt() and 0xFF ushr 4
+ measurement.msgType = msgType
+
+ val co2 = parseUnsignedInt(inputBytes, 0, 3) ushr 6 and INVALID_CO2
+ measurement.co2 = if (co2 == INVALID_CO2) null else co2
+
+ val voc = parseUnsignedInt(inputBytes, 2, 4) ushr 3 and INVALID_VOC
+ measurement.voc = if (co2 == INVALID_VOC) null else voc
+
+ val humidity =
+ parseUnsignedInt(inputBytes, 3, 5) ushr 1 and INVALID_HUMIDITY
+ measurement.humidity = if (humidity == INVALID_HUMIDITY) null else humidity / 10
+
+ val temperature =
+ parseUnsignedInt(inputBytes, 4, 7) ushr 6 and INVALID_TEMPERATURE
+ measurement.temperature =
+ if (temperature == INVALID_TEMPERATURE) null else ((temperature / 10.0 - 40) * 10.0).roundToInt() / 10.0
+
+ val pressure =
+ parseUnsignedInt(inputBytes, 6, 9) ushr 6 and INVALID_PRESSURE
+ measurement.pressure =
+ if (pressure == INVALID_PRESSURE) null else (30000 + 19000.0 * pressure / 13107).roundToInt()
+
+ val occupancy =
+ parseUnsignedInt(inputBytes, 8, 9) ushr 4 and INVALID_OCCUPANCY
+ measurement.occupancy = if (occupancy == INVALID_OCCUPANCY) null else occupancy
+
+ if (msgType == 0) {
+ val pm25 =
+ parseUnsignedInt(inputBytes, 8, 10) ushr 2 and INVALID_PM25
+ measurement.pm25 = if (pm25 == INVALID_PM25) null else pm25
+
+ val pm10 = parseUnsignedInt(inputBytes, 9, 11) and INVALID_PM10
+ measurement.pm10 = if (pm10 == INVALID_PM10) null else pm10
+ }
+ return measurement
+ }
+
+ private fun parseUnsignedInt(bytes: ByteArray, startIncl: Int, endExcl: Int): Int {
+ val section = bytes.copyOfRange(startIncl, endExcl)
+ var unsignedInt = 0
+ for (i in section.indices) {
+ unsignedInt = unsignedInt shl 8
+ unsignedInt = unsignedInt or (section[i].toInt() and 0xFF)
+ }
+ return unsignedInt
+ }
+
+ private fun hexStringToByteArray(encoded: String): ByteArray {
+ return try {
+ Hex.decodeHex(encoded)
+ } catch (e: DecoderException) {
+ throw RuntimeException(e)
+ }
+ }
+
+ data class Measurement (
+ var msgType: Number? = null,
+ var co2: Number? = null,
+ var voc: Number? = null,
+ var humidity: Number? = null,
+ var temperature: Number? = null,
+ var pressure: Number? = null,
+ var occupancy: Number? = null,
+ var pm25: Number? = null,
+ var pm10: Number? = null
+ ) {
+
+
+ override fun toString(): String {
+ return "M{" +
+ "type=" + msgType +
+ "co2=" + co2 +
+ ", voc=" + voc +
+ ", hum=" + humidity +
+ ", temp=" + temperature +
+ ", press=" + pressure +
+ ", occ=" + occupancy +
+ ", pm25=" + pm25 +
+ ", pm10=" + pm10 +
+ '}'
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/sensortestingapp/KirbyDevice.kt b/app/src/main/java/com/example/sensortestingapp/KirbyDevice.kt
index 541f5d8..e840b51 100644
--- a/app/src/main/java/com/example/sensortestingapp/KirbyDevice.kt
+++ b/app/src/main/java/com/example/sensortestingapp/KirbyDevice.kt
@@ -128,7 +128,7 @@ class KirbyDevice(
override var rssi: Int? = null
private fun addMeasurement(characteristic: BluetoothGattCharacteristic) {
- val hexPayload = characteristic.value.toHexString()
+ val hexPayload = characteristic.value.toHexString().substring(2)
val payload = Payload(hexPayload)
val base64Payload = Base64.getEncoder().encodeToString(characteristic.value)
Log.i("BleListener", "Char received: $payload")
@@ -183,25 +183,10 @@ class KirbyDevice(
get() = statuses.stream().map { it.name }.collect(Collectors.joining(", "))
override fun getMeasurements(): List {
- if (measurements.isEmpty()) {
- return emptyList()
- }
- val latest = measurements.last()
- val result = mutableListOf(object : Measurement {
- override fun getLabel(): String {
- return "Index"
- }
+ val result = mutableListOf()
- override fun getIcon(): Int {
- return R.drawable.baseline_numbers_24
- }
-
- override fun getFormattedValue(): String {
- return measurements.size.toString()
- }
- })
- result.addAll(payloadToMeasurements(latest))
- return result
+ measurements.forEach { m -> result.addAll(payloadToMeasurements(m))}
+ return result.reversed()
}
override fun getActions(): List {
@@ -345,10 +330,9 @@ class KirbyDevice(
data class Payload(
val payload: String,
val ts: String = LocalDateTime.now()
- .format(DateTimeFormatter.ofPattern("HH:mm:ss"))
+ .format(DateTimeFormatter.ofPattern("dd.MM.yy HH:mm:ss"))
)
-
fun bytesToUInt16(arr: ByteArray, start: Int): Int {
return ByteBuffer.wrap(arr, start, 2)
.order(ByteOrder.LITTLE_ENDIAN).short.toInt() and 0xFFFF
@@ -376,15 +360,15 @@ private fun payloadToMeasurements(payload: Payload): List {
}
override fun getIcon(): Int? {
- return R.drawable.baseline_access_time_24
+ return R.drawable.baseline_numbers_24
}
}, object : Measurement {
override fun getLabel(): String {
- return "Payload"
+ return ""
}
override fun getFormattedValue(): String {
- return payload.payload
+ return DecoderIaq.parseMeasurement(payload.payload).toString();
}
override fun getIcon(): Int? {
diff --git a/app/src/main/res/layout/row_measurements_list.xml b/app/src/main/res/layout/row_measurements_list.xml
index 4cd3b3e..5e2b941 100644
--- a/app/src/main/res/layout/row_measurements_list.xml
+++ b/app/src/main/res/layout/row_measurements_list.xml
@@ -10,22 +10,26 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawablePadding="2dp"
- android:textSize="18sp"
+ android:textSize="16sp"
app:drawableStartCompat="@drawable/baseline_device_thermostat_24"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+
+ app:layout_constraintTop_toTopOf="parent"
tools:text="Temperature" />
+ tools:text="android:layout_margin="18dp" android:maxWidth="250dp" android:text="@string/year" android:maxLines="24" android:textSize="18sp" app:layout_constraintBottom_toBottomOf="parent"" />
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 125df87..7895a17 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -1,3 +1,4 @@
16dp
+ 0dp
\ No newline at end of file
diff --git a/app/src/test/java/com/example/sensortestingapp/DecoderTest.kt b/app/src/test/java/com/example/sensortestingapp/DecoderTest.kt
new file mode 100644
index 0000000..a52aa8f
--- /dev/null
+++ b/app/src/test/java/com/example/sensortestingapp/DecoderTest.kt
@@ -0,0 +1,27 @@
+package com.example.sensortestingapp
+
+import org.junit.Assert.*
+import org.junit.Test
+
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class DecoderTest {
+ @Test
+ fun message_type_0_decodes_correctly() {
+ val res2 = DecoderIaq.parseMeasurement("006b04ab74a1ed0d101404");
+ val testMeasurement = DecoderIaq.Measurement(msgType = 0, co2 = 428, voc = 149, humidity = 44, pressure = 96873, occupancy = 1, pm10 = 4, pm25 = 5, temperature = 24.7 )
+ assertEquals(testMeasurement, res2)
+ }
+
+
+ @Test
+ fun message_type_1_decodes_correctly() {
+ val res2 = DecoderIaq.parseMeasurement("106b04ab74a1ed0d10");
+ val testMeasurement = DecoderIaq.Measurement(msgType = 1, co2 = 428, voc = 149, humidity = 44, pressure = 96873, occupancy = 1, pm10 = null, pm25 = null, temperature = 24.7 )
+ assertEquals(testMeasurement, res2)
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/java/com/example/sensortestingapp/ExampleUnitTest.kt b/app/src/test/java/com/example/sensortestingapp/ExampleUnitTest.kt
deleted file mode 100644
index 7a120f7..0000000
--- a/app/src/test/java/com/example/sensortestingapp/ExampleUnitTest.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.example.sensortestingapp
-
-import org.junit.Test
-
-import org.junit.Assert.*
-
-/**
- * Example local unit test, which will execute on the development machine (host).
- *
- * See [testing documentation](http://d.android.com/tools/testing).
- */
-class ExampleUnitTest {
- @Test
- fun addition_isCorrect() {
- assertEquals(4, 2 + 2)
- }
-}
\ No newline at end of file