From 10d51cf6756ddc8ce9e23a806e073047f1054a17 Mon Sep 17 00:00:00 2001 From: pnfzygrzgf-svg Date: Mon, 17 Nov 2025 09:12:45 +0100 Subject: [PATCH 1/7] =?UTF-8?q?EN:=20This=20pull=20request=20adjusts=20the?= =?UTF-8?q?=20OBS-Lite=20distance=20handling=20logic=20in=20RecorderServic?= =?UTF-8?q?e.InsertHandler=20and=20cleans=20up=20the=20related=20comment.?= =?UTF-8?q?=20The=20rest=20of=20the=20class=20remains=20unchanged=20compar?= =?UTF-8?q?ed=20to=20the=20original=20implementation.=20DE:=20Dieser=20Pul?= =?UTF-8?q?l=20Request=20passt=20die=20OBS-Lite-Distanzlogik=20im=20Record?= =?UTF-8?q?erService.InsertHandler=20an=20und=20bereinigt=20den=20zugeh?= =?UTF-8?q?=C3=B6rigen=20Kommentar.=20Der=20Rest=20der=20Klasse=20bleibt?= =?UTF-8?q?=20im=20Vergleich=20zur=20Original-Implementierung=20unver?= =?UTF-8?q?=C3=A4ndert.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../simra/app/activities/OBSLiteActivity.kt | 158 +++++++++++------- .../simra/app/services/RecorderService.java | 70 ++++---- 2 files changed, 134 insertions(+), 94 deletions(-) diff --git a/app/src/main/java/de/tuberlin/mcc/simra/app/activities/OBSLiteActivity.kt b/app/src/main/java/de/tuberlin/mcc/simra/app/activities/OBSLiteActivity.kt index b4ec6992..c39fd642 100644 --- a/app/src/main/java/de/tuberlin/mcc/simra/app/activities/OBSLiteActivity.kt +++ b/app/src/main/java/de/tuberlin/mcc/simra/app/activities/OBSLiteActivity.kt @@ -62,64 +62,64 @@ class OBSLiteActivity : BaseActivity(), SerialInputOutputManager.Listener { if (ACTION_USB_PERMISSION == intent.action) { // synchronized(this) { - val device: UsbDevice? = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - intent.getParcelableExtra( - UsbManager.EXTRA_DEVICE, - UsbDevice::class.java - ) - } else { - intent.getParcelableExtra(UsbManager.EXTRA_DEVICE) - } - - Log.d(TAG, "device: $device") - Log.d( - TAG, - "UsbManager.EXTRA_PERMISSION_GRANTED: ${ - intent.getBooleanExtra( - UsbManager.EXTRA_PERMISSION_GRANTED, - false - ) - }" - ) - if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { - device?.apply { - usbDevice = device - binding.obsLiteMainView.visibility = View.VISIBLE - binding.loadingAnimationLayout.visibility = View.GONE - obsLiteConnected = true - updateOBSLiteButton() - - val availableDrivers = - UsbSerialProber.getDefaultProber().findAllDrivers(usbManager) - if (availableDrivers.isNotEmpty()) { - val driver = availableDrivers[0] - val connection = usbManager!!.openDevice(driver.device) - port = driver.ports[0] // Most devices have just one port - try { - port.open(connection) - port.setParameters( - 115200, - 8, - UsbSerialPort.STOPBITS_1, - UsbSerialPort.PARITY_NONE - ) - Log.d(RecorderService.TAG, "usb serial port opened") - val usbIoManager = - SerialInputOutputManager(port, this@OBSLiteActivity) - // usbIoManager!!.run() - usbIoManager.start() - } catch (e: IOException) { - throw RuntimeException(e) - } - + val device: UsbDevice? = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.getParcelableExtra( + UsbManager.EXTRA_DEVICE, + UsbDevice::class.java + ) + } else { + intent.getParcelableExtra(UsbManager.EXTRA_DEVICE) + } + Log.d(TAG, "device: $device") + Log.d( + TAG, + "UsbManager.EXTRA_PERMISSION_GRANTED: ${ + intent.getBooleanExtra( + UsbManager.EXTRA_PERMISSION_GRANTED, + false + ) + }" + ) + if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { + device?.apply { + usbDevice = device + binding.obsLiteMainView.visibility = View.VISIBLE + binding.loadingAnimationLayout.visibility = View.GONE + obsLiteConnected = true + updateOBSLiteButton() + + val availableDrivers = + UsbSerialProber.getDefaultProber().findAllDrivers(usbManager) + if (availableDrivers.isNotEmpty()) { + val driver = availableDrivers[0] + val connection = usbManager!!.openDevice(driver.device) + port = driver.ports[0] // Most devices have just one port + try { + port.open(connection) + port.setParameters( + 115200, + 8, + UsbSerialPort.STOPBITS_1, + UsbSerialPort.PARITY_NONE + ) + Log.d(RecorderService.TAG, "usb serial port opened") + val usbIoManager = + SerialInputOutputManager(port, this@OBSLiteActivity) + // usbIoManager!!.run() + usbIoManager.start() + } catch (e: IOException) { + throw RuntimeException(e) } + } - } else { - Log.d(TAG, "permission denied for device $device") + } + } else { + Log.d(TAG, "permission denied for device $device") + } // } } } @@ -285,32 +285,60 @@ class OBSLiteActivity : BaseActivity(), SerialInputOutputManager.Listener { // Log.d(TAG, "" + event) // event is distance event if (event.hasDistanceMeasurement() && event.distanceMeasurement.distance < 5) { - // convert distance to cm + handlebar width - val distance = ((event.distanceMeasurement.distance * 100) + SharedPref.Settings.Ride.OvertakeWidth.getHandlebarWidth(this)).toInt() + // Rohwert (Meter -> cm) + val rawDistanceCm = (event.distanceMeasurement.distance * 100).toInt() // left sensor event if (event.distanceMeasurement.sourceId == 1) { - binding.leftSensorTextView.text = this@OBSLiteActivity.getString(R.string.obs_lite_text_last_distance_left,distance) - setColePassBarColor(distance,binding.leftSensorProgressBar) + // Linker Sensor -> linke Lenkerbreite verwenden + val handlebarWidthLeftCm = + SharedPref.Settings.Ride.OvertakeWidth.getHandlebarWidthLeft(this) + val correctedDistanceLeft = + (rawDistanceCm - handlebarWidthLeftCm).coerceAtLeast(0) + + binding.leftSensorTextView.text = this@OBSLiteActivity.getString( + R.string.obs_lite_text_last_distance_left, + correctedDistanceLeft + ) + setColePassBarColor(correctedDistanceLeft, binding.leftSensorProgressBar) val eventTime = event.getTime(0).seconds if (startTime == -1L) { startTime = eventTime } // calculate minimal moving median for when the user presses obs lite button - movingMedian.newValue(distance) + // Moving-Median arbeitet mit bereits korrigiertem Abstand (cm) + movingMedian.newValue(correctedDistanceLeft) // right sensor event } else { - binding.rightSensorTextView.text = this@OBSLiteActivity.getString(R.string.obs_lite_text_last_distance_right,distance) - setColePassBarColor(distance,binding.rightSensorProgressBar) + // Rechter Sensor -> rechte Lenkerbreite verwenden + val handlebarWidthRightCm = + SharedPref.Settings.Ride.OvertakeWidth.getHandlebarWidthRight(this) + val correctedDistanceRight = + (rawDistanceCm - handlebarWidthRightCm).coerceAtLeast(0) + + binding.rightSensorTextView.text = this@OBSLiteActivity.getString( + R.string.obs_lite_text_last_distance_right, + correctedDistanceRight + ) + setColePassBarColor(correctedDistanceRight, binding.rightSensorProgressBar) } // event is user input event } else if (event.hasUserInput()) { + // Der Median enthält bereits den korrigierten Abstand in cm val dm: DistanceMeasurement = DistanceMeasurement.newBuilder() - .setDistance(movingMedian.median.toFloat()).build() + .setDistance(movingMedian.median.toFloat()) // cm + .build() event = event.toBuilder().setDistanceMeasurement(dm).build() - binding.userInputProgressbarTextView.text = this@OBSLiteActivity.getString(R.string.overtake_distance_left,movingMedian.median) - setColePassBarColor(movingMedian.median,binding.leftSensorUserInputProgressBar) + binding.userInputProgressbarTextView.text = + this@OBSLiteActivity.getString( + R.string.overtake_distance_left, + movingMedian.median + ) + setColePassBarColor( + movingMedian.median, + binding.leftSensorUserInputProgressBar + ) binding.userInputTextView.text = this@OBSLiteActivity.getString(R.string.overtake_press_button) + event @@ -513,10 +541,10 @@ class OBSLiteActivity : BaseActivity(), SerialInputOutputManager.Listener { distanceArray = distanceArray.drop(1) as ArrayList } distanceArray.add(distance) - // calculate median only if if distanceArray is big enough. + // calculate median only if distanceArray is big enough. if (distanceArray.size >= windowSize) { median = findMedian(distanceArray, windowSize).minOrNull()!! } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/de/tuberlin/mcc/simra/app/services/RecorderService.java b/app/src/main/java/de/tuberlin/mcc/simra/app/services/RecorderService.java index 3ee82798..127fc8e2 100644 --- a/app/src/main/java/de/tuberlin/mcc/simra/app/services/RecorderService.java +++ b/app/src/main/java/de/tuberlin/mcc/simra/app/services/RecorderService.java @@ -534,7 +534,7 @@ private void addOBSIncidents(LinkedList obsMeasur // finish, when at the end of the ride. if (j+1 >= gpsLines.size()) { return; - // else, update gpsLinesIndex, so that the next obs measurement is searched in the rest of the ride. + // else, update gpsLinesIndex, so that the next obs measurement is searched in the rest of the ride. } else { gpsLinesIndex = j+1; break; @@ -569,17 +569,13 @@ public void run() { - GPS (lat, lon, accuracy) roughly every 3 seconds - accelerometer data (x,y,z) roughly 50 times a second - gyroscope data (a,b,c) roughly every 3 seconds -

+ Every Data Type is given asynchronously via its Callback function. - In order to synchronize the accelerometer interval ist used as baseline. - 1. We wait till there are 30 values generated - 2. We write a Log Entry every {@link Constants.MVG_AVG_STEP} - as this number of values is removed at the end of this function - and we wait again till there are 30 + In order to synchronize the accelerometer interval the accelerometer interval is used as baseline. */ boolean isGPSLine = false; long start = System.currentTimeMillis() - startTime; - /**/ + if (accelerometerQueueX.size() < 30) { accelerometerQueueX.add(accelerometerMatrix[0]); accelerometerQueueY.add(accelerometerMatrix[1]); @@ -597,31 +593,31 @@ public void run() { rotationQueueZ.add(rotationMatrix[2]); rotationQueueC.add(rotationMatrix[3]); } - /**/ - /**/if (accelerometerQueueX.size() >= 30 && linearAccelerometerQueueX.size() >= 30 && rotationQueueX.size() >= 30) { + + if (accelerometerQueueX.size() >= 30 && linearAccelerometerQueueX.size() >= 30 && rotationQueueX.size() >= 30) { DataLogEntry.DataLogEntryBuilder dataLogEntryBuilder = DataLogEntry.newBuilder(); long lastAccUpdate = System.currentTimeMillis(); dataLogEntryBuilder.withTimestamp(lastAccUpdate); dataLogEntryBuilder.withAccelerometer( // Every average is computed over 30 data points - /**/computeAverage(accelerometerQueueX), + computeAverage(accelerometerQueueX), computeAverage(accelerometerQueueY), - computeAverage(accelerometerQueueZ)/**/ + computeAverage(accelerometerQueueZ) ); dataLogEntryBuilder.withLinearAccelerometer( // Every average is computed over 30 data points - /**/computeAverage(linearAccelerometerQueueX), + computeAverage(linearAccelerometerQueueX), computeAverage(linearAccelerometerQueueY), - computeAverage(linearAccelerometerQueueZ)/**/ + computeAverage(linearAccelerometerQueueZ) ); dataLogEntryBuilder.withRotation( // Every average is computed over 30 data points - /**/computeAverage(rotationQueueX), + computeAverage(rotationQueueX), computeAverage(rotationQueueY), computeAverage(rotationQueueZ), - computeAverage(rotationQueueC)/**/ + computeAverage(rotationQueueC) ); if ((lastAccUpdate - lastGPSUpdate) >= Constants.GPS_FREQUENCY) { @@ -654,16 +650,34 @@ public void run() { incidentDuringRide = null; } + // OBS-Lite Ereignisse (Knopf) – Distanz ist bereits korrigiert (cm) in DistanceMeasurement.distance if (obsLiteEvent != null && lastLocation != null) { - double handleBarLength = SharedPref.Settings.Ride.OvertakeWidth.getHandlebarWidth(RecorderService.this); - double eventDistance = obsLiteEvent.getDistanceMeasurement().getDistance() * 100.0; - double realDistance = handleBarLength + eventDistance; - if (realDistance >= 150) { - Log.d(TAG, "Adding hidden Close Pass with TS: " + lastAccUpdate + " realLeftDistance: " + realDistance); - incidentLog.updateOrAddIncident(IncidentLogEntry.newBuilder().withBaseInformation(lastAccUpdate,lastLocation.getLatitude(),lastLocation.getLongitude()).withIncidentType(IncidentLogEntry.INCIDENT_TYPE.OBS_LITE).withDescription(getString(R.string.overtake_distance_left,((int)obsLiteEvent.getDistanceMeasurement().getDistance()))).withKey(5000).build()); + double realDistanceCm = obsLiteEvent.getDistanceMeasurement().getDistance(); // bereits in cm + if (realDistanceCm < 0) { + realDistanceCm = 0; + } + int realDistanceInt = (int) Math.round(realDistanceCm); + + if (realDistanceCm >= 150) { + Log.d(TAG, "Adding hidden Close Pass with TS: " + lastAccUpdate + " realLeftDistance(cm): " + realDistanceCm); + incidentLog.updateOrAddIncident( + IncidentLogEntry.newBuilder() + .withBaseInformation(lastAccUpdate, lastLocation.getLatitude(), lastLocation.getLongitude()) + .withIncidentType(IncidentLogEntry.INCIDENT_TYPE.OBS_LITE) + .withDescription(getString(R.string.overtake_distance_left, realDistanceInt)) + .withKey(5000) + .build() + ); } else { - Log.d(TAG, "Adding visible Close Pass with TS: " + lastAccUpdate + " realLeftDistance: " + realDistance); - incidentLog.updateOrAddIncident(IncidentLogEntry.newBuilder().withBaseInformation(lastAccUpdate,lastLocation.getLatitude(),lastLocation.getLongitude()).withIncidentType(IncidentLogEntry.INCIDENT_TYPE.CLOSE_PASS).withDescription(getString(R.string.overtake_distance_left,obsLiteEvent.getDistanceMeasurement().getDistance())).withKey(4000).build()); + Log.d(TAG, "Adding visible Close Pass with TS: " + lastAccUpdate + " realLeftDistance(cm): " + realDistanceCm); + incidentLog.updateOrAddIncident( + IncidentLogEntry.newBuilder() + .withBaseInformation(lastAccUpdate, lastLocation.getLatitude(), lastLocation.getLongitude()) + .withIncidentType(IncidentLogEntry.INCIDENT_TYPE.CLOSE_PASS) + .withDescription(getString(R.string.overtake_distance_left, realDistanceInt)) + .withKey(4000) + .build() + ); } obsLiteEvent = null; } @@ -678,9 +692,9 @@ public void run() { }*/ } dataLogEntryBuilder.withGyroscope( - /**/gyroscopeMatrix[0], + gyroscopeMatrix[0], gyroscopeMatrix[1], - gyroscopeMatrix[2]/**/ + gyroscopeMatrix[2] ); /*if (lastOBSDistanceValues.size() > 0) { @@ -700,7 +714,6 @@ public void run() { endTime = System.currentTimeMillis(); - /**/ for (int i = 0; i < Constants.MVG_AVG_STEP; i++) { accelerometerQueueX.remove(); accelerometerQueueY.remove(); @@ -713,8 +726,7 @@ public void run() { rotationQueueZ.remove(); rotationQueueC.remove(); } - /**/ - /**/} + } lastHandlerStart = start; recordingHandler.postDelayed(this,50); } From 1ee49d319afab03a41a55e477a2ebf5d41754933 Mon Sep 17 00:00:00 2001 From: pnfzygrzgf-svg Date: Mon, 17 Nov 2025 10:13:11 +0100 Subject: [PATCH 2/7] =?UTF-8?q?EN:=20This=20pull=20request=20adjusts=20the?= =?UTF-8?q?=20OBS-Lite=20distance=20handling=20logic=20in=20RecorderServic?= =?UTF-8?q?e.InsertHandler=20and=20cleans=20up=20the=20related=20comment.?= =?UTF-8?q?=20The=20rest=20of=20the=20class=20remains=20unchanged=20compar?= =?UTF-8?q?ed=20to=20the=20original=20implementation.=20DE:=20Dieser=20Pul?= =?UTF-8?q?l=20Request=20passt=20die=20OBS-Lite-Distanzlogik=20im=20Record?= =?UTF-8?q?erService.InsertHandler=20an=20und=20bereinigt=20den=20zugeh?= =?UTF-8?q?=C3=B6rigen=20Kommentar.=20Der=20Rest=20der=20Klasse=20bleibt?= =?UTF-8?q?=20im=20Vergleich=20zur=20Original-Implementierung=20unver?= =?UTF-8?q?=C3=A4ndert.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../de/tuberlin/mcc/simra/app/activities/OBSLiteActivity.kt | 1 + .../de/tuberlin/mcc/simra/app/services/RecorderService.java | 2 ++ 2 files changed, 3 insertions(+) diff --git a/app/src/main/java/de/tuberlin/mcc/simra/app/activities/OBSLiteActivity.kt b/app/src/main/java/de/tuberlin/mcc/simra/app/activities/OBSLiteActivity.kt index c39fd642..c1245db0 100644 --- a/app/src/main/java/de/tuberlin/mcc/simra/app/activities/OBSLiteActivity.kt +++ b/app/src/main/java/de/tuberlin/mcc/simra/app/activities/OBSLiteActivity.kt @@ -548,3 +548,4 @@ class OBSLiteActivity : BaseActivity(), SerialInputOutputManager.Listener { } } } + diff --git a/app/src/main/java/de/tuberlin/mcc/simra/app/services/RecorderService.java b/app/src/main/java/de/tuberlin/mcc/simra/app/services/RecorderService.java index 127fc8e2..70ecad38 100644 --- a/app/src/main/java/de/tuberlin/mcc/simra/app/services/RecorderService.java +++ b/app/src/main/java/de/tuberlin/mcc/simra/app/services/RecorderService.java @@ -847,3 +847,5 @@ public RecorderService getService() { } } } + + From f4cf2467b171efc4ec4690425a4ae40c25e7244b Mon Sep 17 00:00:00 2001 From: pnfzygrzgf-svg Date: Mon, 17 Nov 2025 13:12:57 +0100 Subject: [PATCH 3/7] =?UTF-8?q?EN:=20This=20pull=20request=20adjusts=20the?= =?UTF-8?q?=20OBS-Lite=20distance=20handling=20logic=20in=20RecorderServic?= =?UTF-8?q?e.InsertHandler=20and=20cleans=20up=20the=20related=20comment.?= =?UTF-8?q?=20The=20rest=20of=20the=20class=20remains=20unchanged=20compar?= =?UTF-8?q?ed=20to=20the=20original=20implementation.=20DE:=20Dieser=20Pul?= =?UTF-8?q?l=20Request=20passt=20die=20OBS-Lite-Distanzlogik=20im=20Record?= =?UTF-8?q?erService.InsertHandler=20an=20und=20bereinigt=20den=20zugeh?= =?UTF-8?q?=C3=B6rigen=20Kommentar.=20Der=20Rest=20der=20Klasse=20bleibt?= =?UTF-8?q?=20im=20Vergleich=20zur=20Original-Implementierung=20unver?= =?UTF-8?q?=C3=A4ndert.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/de/tuberlin/mcc/simra/app/activities/OBSLiteActivity.kt | 1 - .../java/de/tuberlin/mcc/simra/app/services/RecorderService.java | 1 - 2 files changed, 2 deletions(-) diff --git a/app/src/main/java/de/tuberlin/mcc/simra/app/activities/OBSLiteActivity.kt b/app/src/main/java/de/tuberlin/mcc/simra/app/activities/OBSLiteActivity.kt index c1245db0..c39fd642 100644 --- a/app/src/main/java/de/tuberlin/mcc/simra/app/activities/OBSLiteActivity.kt +++ b/app/src/main/java/de/tuberlin/mcc/simra/app/activities/OBSLiteActivity.kt @@ -548,4 +548,3 @@ class OBSLiteActivity : BaseActivity(), SerialInputOutputManager.Listener { } } } - diff --git a/app/src/main/java/de/tuberlin/mcc/simra/app/services/RecorderService.java b/app/src/main/java/de/tuberlin/mcc/simra/app/services/RecorderService.java index 70ecad38..f98eeb21 100644 --- a/app/src/main/java/de/tuberlin/mcc/simra/app/services/RecorderService.java +++ b/app/src/main/java/de/tuberlin/mcc/simra/app/services/RecorderService.java @@ -848,4 +848,3 @@ public RecorderService getService() { } } - From eb2ce62a3ab4a5dc0f123ecea627909fbc909006 Mon Sep 17 00:00:00 2001 From: pnfzygrzgf-svg Date: Mon, 17 Nov 2025 13:13:36 +0100 Subject: [PATCH 4/7] =?UTF-8?q?EN:=20This=20pull=20request=20adjusts=20the?= =?UTF-8?q?=20OBS-Lite=20distance=20handling=20logic=20in=20RecorderServic?= =?UTF-8?q?e.InsertHandler=20and=20cleans=20up=20the=20related=20comment.?= =?UTF-8?q?=20The=20rest=20of=20the=20class=20remains=20unchanged=20compar?= =?UTF-8?q?ed=20to=20the=20original=20implementation.=20DE:=20Dieser=20Pul?= =?UTF-8?q?l=20Request=20passt=20die=20OBS-Lite-Distanzlogik=20im=20Record?= =?UTF-8?q?erService.InsertHandler=20an=20und=20bereinigt=20den=20zugeh?= =?UTF-8?q?=C3=B6rigen=20Kommentar.=20Der=20Rest=20der=20Klasse=20bleibt?= =?UTF-8?q?=20im=20Vergleich=20zur=20Original-Implementierung=20unver?= =?UTF-8?q?=C3=A4ndert.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../de/tuberlin/mcc/simra/app/activities/OBSLiteActivity.kt | 2 +- .../de/tuberlin/mcc/simra/app/services/RecorderService.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/de/tuberlin/mcc/simra/app/activities/OBSLiteActivity.kt b/app/src/main/java/de/tuberlin/mcc/simra/app/activities/OBSLiteActivity.kt index c39fd642..c559e5e9 100644 --- a/app/src/main/java/de/tuberlin/mcc/simra/app/activities/OBSLiteActivity.kt +++ b/app/src/main/java/de/tuberlin/mcc/simra/app/activities/OBSLiteActivity.kt @@ -547,4 +547,4 @@ class OBSLiteActivity : BaseActivity(), SerialInputOutputManager.Listener { } } } -} +} \ No newline at end of file diff --git a/app/src/main/java/de/tuberlin/mcc/simra/app/services/RecorderService.java b/app/src/main/java/de/tuberlin/mcc/simra/app/services/RecorderService.java index f98eeb21..127fc8e2 100644 --- a/app/src/main/java/de/tuberlin/mcc/simra/app/services/RecorderService.java +++ b/app/src/main/java/de/tuberlin/mcc/simra/app/services/RecorderService.java @@ -847,4 +847,3 @@ public RecorderService getService() { } } } - From dc83044a8ca2aec86682b1838125bcfc1255a884 Mon Sep 17 00:00:00 2001 From: pnfzygrzgf-svg Date: Mon, 17 Nov 2025 13:22:18 +0100 Subject: [PATCH 5/7] =?UTF-8?q?EN:=20This=20pull=20request=20adjusts=20the?= =?UTF-8?q?=20OBS-Lite=20distance=20handling=20logic=20in=20RecorderServic?= =?UTF-8?q?e.InsertHandler=20and=20cleans=20up=20the=20related=20comment.?= =?UTF-8?q?=20The=20rest=20of=20the=20class=20remains=20unchanged=20compar?= =?UTF-8?q?ed=20to=20the=20original=20implementation.=20DE:=20Dieser=20Pul?= =?UTF-8?q?l=20Request=20passt=20die=20OBS-Lite-Distanzlogik=20im=20Record?= =?UTF-8?q?erService.InsertHandler=20an=20und=20bereinigt=20den=20zugeh?= =?UTF-8?q?=C3=B6rigen=20Kommentar.=20Der=20Rest=20der=20Klasse=20bleibt?= =?UTF-8?q?=20im=20Vergleich=20zur=20Original-Implementierung=20unver?= =?UTF-8?q?=C3=A4ndert.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../de/tuberlin/mcc/simra/app/activities/OBSLiteActivity.kt | 2 +- .../de/tuberlin/mcc/simra/app/services/RecorderService.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/de/tuberlin/mcc/simra/app/activities/OBSLiteActivity.kt b/app/src/main/java/de/tuberlin/mcc/simra/app/activities/OBSLiteActivity.kt index c559e5e9..c39fd642 100644 --- a/app/src/main/java/de/tuberlin/mcc/simra/app/activities/OBSLiteActivity.kt +++ b/app/src/main/java/de/tuberlin/mcc/simra/app/activities/OBSLiteActivity.kt @@ -547,4 +547,4 @@ class OBSLiteActivity : BaseActivity(), SerialInputOutputManager.Listener { } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/de/tuberlin/mcc/simra/app/services/RecorderService.java b/app/src/main/java/de/tuberlin/mcc/simra/app/services/RecorderService.java index 127fc8e2..f98eeb21 100644 --- a/app/src/main/java/de/tuberlin/mcc/simra/app/services/RecorderService.java +++ b/app/src/main/java/de/tuberlin/mcc/simra/app/services/RecorderService.java @@ -847,3 +847,4 @@ public RecorderService getService() { } } } + From 23bd7f9d79a63109904f25bfd6c0def4a05241d9 Mon Sep 17 00:00:00 2001 From: pnfzygrzgf-svg Date: Sat, 22 Nov 2025 16:40:50 +0100 Subject: [PATCH 6/7] next --- .../mcc/simra/app/obslite/OBSLiteSession.kt | 96 +++++++++++++------ 1 file changed, 68 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/de/tuberlin/mcc/simra/app/obslite/OBSLiteSession.kt b/app/src/main/java/de/tuberlin/mcc/simra/app/obslite/OBSLiteSession.kt index efed20b4..300a4a75 100644 --- a/app/src/main/java/de/tuberlin/mcc/simra/app/obslite/OBSLiteSession.kt +++ b/app/src/main/java/de/tuberlin/mcc/simra/app/obslite/OBSLiteSession.kt @@ -28,29 +28,31 @@ class OBSLiteSession(val context: Context) { private var lastLat: Double = 0.0 private var lastLon: Double = 0.0 private var completeEvents = ArrayList() + init { - val handlebarOffsetLeft: ByteString = ByteString.copyFromUtf8(SharedPref.Settings.Ride.OvertakeWidth.getHandlebarWidthLeft(context) - .toString()) - val handlebarOffsetRight: ByteString = ByteString.copyFromUtf8(SharedPref.Settings.Ride.OvertakeWidth.getHandlebarWidthRight(context) - .toString()) + val handlebarOffsetLeft: ByteString = ByteString.copyFromUtf8( + SharedPref.Settings.Ride.OvertakeWidth.getHandlebarWidthLeft(context).toString() + ) + val handlebarOffsetRight: ByteString = ByteString.copyFromUtf8( + SharedPref.Settings.Ride.OvertakeWidth.getHandlebarWidthRight(context).toString() + ) val metaData: Metadata = Metadata.newBuilder() - .putData("HandlebarOffsetLeft",handlebarOffsetLeft) - .putData("HandlebarOffsetRight",handlebarOffsetRight) + .putData("HandlebarOffsetLeft", handlebarOffsetLeft) + .putData("HandlebarOffsetRight", handlebarOffsetRight) .putData("SimRaVersion", ByteString.copyFromUtf8(BuildConfig.VERSION_NAME)) .build() + // Nur intern merken, NICHT in completeEvents (Binary), sonst hat das Portal ein Event ohne Zeit events.add(Event.newBuilder().setMetadata(metaData).build()) } - - // handles distance event and user input events of obs lite - fun handleEvent(lat: Double, lon: Double, altitude: Double, accuracy: Float) : Event? { + fun handleEvent(lat: Double, lon: Double, altitude: Double, accuracy: Float): Event? { val decodedData = CobsUtils.decode(byteListQueue.first) try { var obsEvent: Event = Event.parseFrom(decodedData) - val currentTimeMillis: Long = System.currentTimeMillis(); + val currentTimeMillis: Long = System.currentTimeMillis() if (startTime == -1L) { startTime = obsEvent.getTime(0).seconds @@ -62,8 +64,8 @@ class OBSLiteSession(val context: Context) { .setSourceId(2).setReference(Time.Reference.UNIX).build() val smartphoneTime: Time = Time.newBuilder() - .setSeconds(currentTimeMillis/1000) - .setNanoseconds(((currentTimeMillis%1000) * 1000000).toInt()) + .setSeconds(currentTimeMillis / 1000) + .setNanoseconds(((currentTimeMillis % 1000) * 1000000).toInt()) .setSourceId(3).setReference(Time.Reference.UNIX).build() if (lat != lastLat || lon != lastLon) { @@ -71,7 +73,11 @@ class OBSLiteSession(val context: Context) { .setLatitude(lat).setLongitude(lon) .setAltitude(altitude).setHdop(accuracy).build() - val gpsEvent = Event.newBuilder().setGeolocation(geolocation).addTime(obsTime).addTime(smartphoneTime).build() + val gpsEvent = Event.newBuilder() + .setGeolocation(geolocation) + .addTime(obsTime) + .addTime(smartphoneTime) + .build() events.add(gpsEvent) completeEvents.addAll(encodeEvent(gpsEvent)) lastLat = lat @@ -79,29 +85,64 @@ class OBSLiteSession(val context: Context) { } if (obsEvent.hasDistanceMeasurement()) { - val dm = obsEvent.distanceMeasurement - obsEvent = obsEvent.toBuilder().addTime(obsTime).addTime(smartphoneTime).setDistanceMeasurement(dm).build() - // left sensor event - if (obsEvent.distanceMeasurement.sourceId == 1) { - val distance = ((obsEvent.distanceMeasurement.distance * 100) + SharedPref.Settings.Ride.OvertakeWidth.getHandlebarWidthLeft(context)).toInt() + // >>> Änderung: Lenkerbreite vom linken Sensor-Abstand abziehen + var dm: DistanceMeasurement = obsEvent.distanceMeasurement + var dmBuilder = dm.toBuilder() + + if (dm.sourceId == 1) { + val handlebarLeftCm = + SharedPref.Settings.Ride.OvertakeWidth.getHandlebarWidthLeft(context) + + val rawMeters = dm.distance // Sensor -> Objekt + val handlebarMeters = handlebarLeftCm / 100.0f + + var correctedMeters = rawMeters - handlebarMeters + if (correctedMeters < 0f) { + correctedMeters = 0f + } + + dmBuilder = dmBuilder.setDistance(correctedMeters) + + val correctedCm = (correctedMeters * 100.0f) + .toInt() + .coerceAtLeast(0) // calculate minimal moving median for when the user presses obs lite button - movingMedian.newValue(distance) - // Log.d(TAG, "distance event: $event") + movingMedian.newValue(correctedCm) + } else { + // andere Sensoren unverändert lassen } + + dm = dmBuilder.build() + obsEvent = obsEvent.toBuilder() + .addTime(obsTime) + .addTime(smartphoneTime) + .setDistanceMeasurement(dm) + .build() + } else if (obsEvent.hasUserInput()) { val ui = obsEvent.userInput - obsEvent = obsEvent.toBuilder().addTime(obsTime).addTime(smartphoneTime).setUserInput(ui).build() + obsEvent = obsEvent.toBuilder() + .addTime(obsTime) + .addTime(smartphoneTime) + .setUserInput(ui) + .build() events.add(obsEvent) completeEvents.addAll(encodeEvent(obsEvent)) val dm: DistanceMeasurement = DistanceMeasurement.newBuilder() .setDistance(movingMedian.median.toFloat()).build() - obsEvent = obsEvent.toBuilder().addTime(obsTime).addTime(smartphoneTime).setDistanceMeasurement(dm).build() + obsEvent = obsEvent.toBuilder() + .addTime(obsTime) + .addTime(smartphoneTime) + .setDistanceMeasurement(dm).build() // Log.d(TAG, "user input event: $obsEvent") byteListQueue.removeFirst() return obsEvent } else { - obsEvent = obsEvent.toBuilder().addTime(obsTime).addTime(smartphoneTime).build() + obsEvent = obsEvent.toBuilder() + .addTime(obsTime) + .addTime(smartphoneTime) + .build() // Log.d(TAG, obsEvent.toString()) } @@ -109,7 +150,6 @@ class OBSLiteSession(val context: Context) { events.add(obsEvent) completeEvents.addAll(encodeEvent(obsEvent)) - } catch (_: InvalidProtocolBufferException) { } // if first byte list is handled, remove it. @@ -131,7 +171,7 @@ class OBSLiteSession(val context: Context) { fun fillByteList(data: ByteArray?) { for (datum in data!!) { // start new COBS package when last byte was 00 or it is the first data - if (lastByteRead?.toInt() == 0x00 || byteListQueue.isEmpty()){ + if (lastByteRead?.toInt() == 0x00 || byteListQueue.isEmpty()) { val newByteList = LinkedList() newByteList.add(datum) byteListQueue.add(newByteList) @@ -177,11 +217,11 @@ class OBSLiteSession(val context: Context) { .setLatitude(location.latitude).setLongitude(location.longitude) .setAltitude(location.altitude).setHdop(location.accuracy).build() - val time: Time = Time.newBuilder().setNanoseconds((location.time*1000000).toInt()).build() + val time: Time = Time.newBuilder() + .setNanoseconds((location.time * 1000000).toInt()).build() val gpsEvent = Event.newBuilder().setGeolocation(geolocation).addTime(time).build() events.add(gpsEvent) completeEvents.addAll(encodeEvent(gpsEvent)) - } -} \ No newline at end of file +} From b3615503a6c3a183b75d110de8110130c9fb4b03 Mon Sep 17 00:00:00 2001 From: pnfzygrzgf-svg Date: Sat, 22 Nov 2025 20:29:58 +0100 Subject: [PATCH 7/7] Anpassungen beim Umgang mit Lenkerbreite --- .../simra/app/activities/OBSLiteActivity.kt | 74 ++++--- .../app/activities/OpenBikeSensorActivity.kt | 204 +++++++++++++++--- .../de/tuberlin/mcc/simra/app/util/Utils.java | 52 +++-- build.gradle | 2 +- 4 files changed, 242 insertions(+), 90 deletions(-) diff --git a/app/src/main/java/de/tuberlin/mcc/simra/app/activities/OBSLiteActivity.kt b/app/src/main/java/de/tuberlin/mcc/simra/app/activities/OBSLiteActivity.kt index c39fd642..521a7949 100644 --- a/app/src/main/java/de/tuberlin/mcc/simra/app/activities/OBSLiteActivity.kt +++ b/app/src/main/java/de/tuberlin/mcc/simra/app/activities/OBSLiteActivity.kt @@ -36,7 +36,6 @@ import java.util.TreeSet import java.util.concurrent.ConcurrentLinkedDeque import java.util.concurrent.ConcurrentLinkedQueue - class OBSLiteActivity : BaseActivity(), SerialInputOutputManager.Listener { private lateinit var binding: ActivityObsliteBinding private val TAG = "OBSLiteActivity_LOG" @@ -112,10 +111,7 @@ class OBSLiteActivity : BaseActivity(), SerialInputOutputManager.Listener { } catch (e: IOException) { throw RuntimeException(e) } - - } - } } else { Log.d(TAG, "permission denied for device $device") @@ -123,7 +119,6 @@ class OBSLiteActivity : BaseActivity(), SerialInputOutputManager.Listener { // } } } - } override fun onCreate(savedInstanceState: Bundle?) { @@ -141,7 +136,10 @@ class OBSLiteActivity : BaseActivity(), SerialInputOutputManager.Listener { binding.handleBarWidthLeft.value = SharedPref.Settings.Ride.OvertakeWidth.getHandlebarWidthLeft(this) binding.handleBarWidthLeft.setOnValueChangedListener { picker, oldVal, newVal -> - SharedPref.Settings.Ride.OvertakeWidth.setTotalWidthThroughHandlebarWidthLeft(newVal, this) + SharedPref.Settings.Ride.OvertakeWidth.setTotalWidthThroughHandlebarWidthLeft( + newVal, + this + ) } // right binding.handleBarWidthRight.maxValue = 60 @@ -149,7 +147,10 @@ class OBSLiteActivity : BaseActivity(), SerialInputOutputManager.Listener { binding.handleBarWidthRight.value = SharedPref.Settings.Ride.OvertakeWidth.getHandlebarWidthRight(this) binding.handleBarWidthRight.setOnValueChangedListener { picker, oldVal, newVal -> - SharedPref.Settings.Ride.OvertakeWidth.setTotalWidthThroughHandlebarWidthRight(newVal, this) + SharedPref.Settings.Ride.OvertakeWidth.setTotalWidthThroughHandlebarWidthRight( + newVal, + this + ) } // OBS-Lite @@ -157,11 +158,14 @@ class OBSLiteActivity : BaseActivity(), SerialInputOutputManager.Listener { usbManager = getSystemService(Context.USB_SERVICE) as UsbManager val explicitIntent = Intent(ACTION_USB_PERMISSION) - explicitIntent.setPackage(this.packageName); - - - permissionIntent = PendingIntent.getBroadcast(this, 0, explicitIntent, - PendingIntent.FLAG_MUTABLE) + explicitIntent.setPackage(this.packageName) + + permissionIntent = PendingIntent.getBroadcast( + this, + 0, + explicitIntent, + PendingIntent.FLAG_MUTABLE + ) val filter = IntentFilter(ACTION_USB_PERMISSION) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { this.registerReceiver(usbReceiver, filter, RECEIVER_EXPORTED) @@ -170,18 +174,16 @@ class OBSLiteActivity : BaseActivity(), SerialInputOutputManager.Listener { } updateOBSLiteButton() val obsLiteUrl = SharedPref.Settings.OBSLite.getObsLiteURL(this) - binding.obsLiteURL.setText(obsLiteUrl,TextView.BufferType.EDITABLE) + binding.obsLiteURL.setText(obsLiteUrl, TextView.BufferType.EDITABLE) val obsUsername = SharedPref.Settings.OBSLite.getObsLiteUsername(this) - binding.obsLiteUsername.setText(obsUsername,TextView.BufferType.EDITABLE) + binding.obsLiteUsername.setText(obsUsername, TextView.BufferType.EDITABLE) val obsLiteAPIKey = SharedPref.Settings.OBSLite.getObsLiteAPIKey(this) - binding.obsLiteAPIKey.setText(obsLiteAPIKey,TextView.BufferType.EDITABLE) - + binding.obsLiteAPIKey.setText(obsLiteAPIKey, TextView.BufferType.EDITABLE) } private fun getOBSLitePermission() { - val deviceList = usbManager?.getDeviceList() deviceList?.values?.forEach { device -> Log.d(TAG, "deviceList device $device") @@ -189,7 +191,6 @@ class OBSLiteActivity : BaseActivity(), SerialInputOutputManager.Listener { } usbManager?.requestPermission(usbDevice, permissionIntent) - } private fun disconnectOBSLite() { @@ -205,21 +206,20 @@ class OBSLiteActivity : BaseActivity(), SerialInputOutputManager.Listener { } } - override fun onResume() { super.onResume() } override fun onPause() { super.onPause() - val obsLiteUrl = binding.obsLiteURL.getText() - SharedPref.Settings.OBSLite.setObsLiteURL(obsLiteUrl.toString(),this) + val obsLiteUrl = binding.obsLiteURL.text + SharedPref.Settings.OBSLite.setObsLiteURL(obsLiteUrl.toString(), this) - val obsLiteUsername = binding.obsLiteUsername.getText() - SharedPref.Settings.OBSLite.setObsLiteUsername(obsLiteUsername.toString(),this) + val obsLiteUsername = binding.obsLiteUsername.text + SharedPref.Settings.OBSLite.setObsLiteUsername(obsLiteUsername.toString(), this) - val obsLiteAPIKey = binding.obsLiteAPIKey.getText() - SharedPref.Settings.OBSLite.setObsLiteAPIKey(obsLiteAPIKey.toString(),this) + val obsLiteAPIKey = binding.obsLiteAPIKey.text + SharedPref.Settings.OBSLite.setObsLiteAPIKey(obsLiteAPIKey.toString(), this) } override fun onDestroy() { @@ -249,7 +249,6 @@ class OBSLiteActivity : BaseActivity(), SerialInputOutputManager.Listener { } @OptIn(ExperimentalStdlibApi::class) - /** OBS-Lite related **/ // Called when new data from usb is read @@ -266,7 +265,6 @@ class OBSLiteActivity : BaseActivity(), SerialInputOutputManager.Listener { handleEvent() } } - } // handles distance event and user input events of obs lite @@ -304,7 +302,7 @@ class OBSLiteActivity : BaseActivity(), SerialInputOutputManager.Listener { if (startTime == -1L) { startTime = eventTime } - // calculate minimal moving median for when the user presses obs lite button + // calculate median for when the user presses obs lite button // Moving-Median arbeitet mit bereits korrigiertem Abstand (cm) movingMedian.newValue(correctedDistanceLeft) // right sensor event @@ -341,7 +339,6 @@ class OBSLiteActivity : BaseActivity(), SerialInputOutputManager.Listener { ) binding.userInputTextView.text = this@OBSLiteActivity.getString(R.string.overtake_press_button) + event - } } catch (_: InvalidProtocolBufferException) { } @@ -378,11 +375,10 @@ class OBSLiteActivity : BaseActivity(), SerialInputOutputManager.Listener { // handles the byteListQueues, which contain the COBS packages private fun fillByteList(data: ByteArray?) { for (datum in data!!) { - if (lastByteRead?.toInt() == 0x00){ // start new COBS package when last byte was 00 + if (lastByteRead?.toInt() == 0x00) { // start new COBS package when last byte was 00 val newByteList = LinkedList() newByteList.add(datum) byteListQueue.add(newByteList) - } else { // COBS package is not completed yet, continue the same package if (byteListQueue.isNotEmpty()) { byteListQueue.last.add(datum) @@ -403,7 +399,8 @@ class OBSLiteActivity : BaseActivity(), SerialInputOutputManager.Listener { private fun updateOBSLiteButton() { runOnUiThread { if (obsLiteConnected) { - binding.usbButton.text = getString(R.string.obs_activity_button_disconnect_device) + binding.usbButton.text = + getString(R.string.obs_activity_button_disconnect_device) binding.usbButton.setOnClickListener { disconnectOBSLite() } @@ -414,7 +411,6 @@ class OBSLiteActivity : BaseActivity(), SerialInputOutputManager.Listener { } } } - } /** @@ -426,9 +422,9 @@ class OBSLiteActivity : BaseActivity(), SerialInputOutputManager.Listener { var distanceArray: ArrayList = ArrayList() var windowSize = 3 var median = 0 + // Pair class for the value and its index - class Pair // Constructor - (private var value: Int, private var index: Int) : Comparable { + class Pair(private var value: Int, private var index: Int) : Comparable { // This method will be used by the treeset to search a value by index and setting the tree nodes (left or right) override fun compareTo(other: Pair?): Int { @@ -459,7 +455,6 @@ class OBSLiteActivity : BaseActivity(), SerialInputOutputManager.Listener { } } - // Function to print the median for the current window fun printMedian(minSet: TreeSet, maxSet: TreeSet, window: Int): Int { @@ -467,7 +462,8 @@ class OBSLiteActivity : BaseActivity(), SerialInputOutputManager.Listener { return if (window % 2 == 0) { (((minSet.last()!!.value() + maxSet.first()!!.value()) / 2.0).toInt()) } else { - (if (minSet.size > maxSet.size) minSet.last()!!.value() else maxSet.first()!!.value()) + (if (minSet.size > maxSet.size) minSet.last()!!.value() else maxSet.first()!! + .value()) } } @@ -543,7 +539,9 @@ class OBSLiteActivity : BaseActivity(), SerialInputOutputManager.Listener { distanceArray.add(distance) // calculate median only if distanceArray is big enough. if (distanceArray.size >= windowSize) { - median = findMedian(distanceArray, windowSize).minOrNull()!! + val medians = findMedian(distanceArray, windowSize) + // Statt globalem Minimum: Median des letzten Fensters verwenden + median = medians.last() } } } diff --git a/app/src/main/java/de/tuberlin/mcc/simra/app/activities/OpenBikeSensorActivity.kt b/app/src/main/java/de/tuberlin/mcc/simra/app/activities/OpenBikeSensorActivity.kt index fb00fb92..203d2995 100644 --- a/app/src/main/java/de/tuberlin/mcc/simra/app/activities/OpenBikeSensorActivity.kt +++ b/app/src/main/java/de/tuberlin/mcc/simra/app/activities/OpenBikeSensorActivity.kt @@ -1,18 +1,14 @@ package de.tuberlin.mcc.simra.app.activities -import android.Manifest import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothManager import android.content.Context import android.content.Intent -import android.content.pm.PackageManager import android.content.res.ColorStateList import android.graphics.Color -import android.os.Build import android.os.Bundle import android.util.Log import androidx.activity.result.ActivityResultLauncher -import androidx.core.app.ActivityCompat import de.tuberlin.mcc.simra.app.R import de.tuberlin.mcc.simra.app.databinding.ActivityOpenbikesensorBinding import de.tuberlin.mcc.simra.app.util.BaseActivity @@ -24,41 +20,50 @@ import de.tuberlin.mcc.simra.app.util.PermissionHelper.REQUEST_ENABLE_BT import de.tuberlin.mcc.simra.app.util.PermissionHelper.hasBLEPermissions import de.tuberlin.mcc.simra.app.util.PermissionHelper.requestBlePermissions import de.tuberlin.mcc.simra.app.util.SharedPref -import de.tuberlin.mcc.simra.app.util.Utils import de.tuberlin.mcc.simra.app.util.Utils.activityResultLauncher import de.tuberlin.mcc.simra.app.util.ble.ConnectionEventListener - +import java.util.TreeSet private const val TAG = "OpenBikeSensorActivityCM_LOG" -private const val ENABLE_BLUETOOTH_REQUEST_CODE = 1 class OpenBikeSensorActivity : BaseActivity() { private lateinit var binding: ActivityOpenbikesensorBinding - - private val notifyingCharacteristicsToSubscribeTo = listOf(SENSOR_DISTANCE_CHARACTERISTIC_UUID, CLOSE_PASS_CHARACTERISTIC_UUID) + private val notifyingCharacteristicsToSubscribeTo = + listOf(SENSOR_DISTANCE_CHARACTERISTIC_UUID, CLOSE_PASS_CHARACTERISTIC_UUID) private var deviceName = "---" - private lateinit var activityResultLauncher:ActivityResultLauncher + private lateinit var activityResultLauncher: ActivityResultLauncher + // Gleitender Median über die gemessenen Distanzen (nur Anzeige) + private val movingMedian = MovingMedian() private fun updateUI() { runOnUiThread { Log.d(TAG, "updateUI() - blestate: ${ConnectionManager.bleState}") when (ConnectionManager.bleState) { BLESTATE.DISCONNECTED -> { - binding.bluetoothButton.text = getString(R.string.obs_activity_button_start_scan) + binding.bluetoothButton.text = + getString(R.string.obs_activity_button_start_scan) binding.statusText.text = getString(R.string.obs_activity_text_start) } + BLESTATE.FOUND -> { - binding.bluetoothButton.text = getString(R.string.obs_activity_button_connect_device) - binding.statusText.text = getString(R.string.obs_activity_text_connect,deviceName) + binding.bluetoothButton.text = + getString(R.string.obs_activity_button_connect_device) + binding.statusText.text = + getString(R.string.obs_activity_text_connect, deviceName) } + BLESTATE.CONNECTED -> { - binding.bluetoothButton.text = getString(R.string.obs_activity_button_disconnect_device) - binding.statusText.text = getString(R.string.obs_activity_text_disconnect,deviceName) + binding.bluetoothButton.text = + getString(R.string.obs_activity_button_disconnect_device) + binding.statusText.text = + getString(R.string.obs_activity_text_disconnect, deviceName) } + else -> { - binding.bluetoothButton.text = getString(R.string.obs_activity_button_stop_scan) + binding.bluetoothButton.text = + getString(R.string.obs_activity_button_stop_scan) binding.statusText.text = getString(R.string.obs_activity_text_stop) } } @@ -78,7 +83,6 @@ class OpenBikeSensorActivity : BaseActivity() { } binding.bluetoothButton.setOnClickListener { - Log.d(TAG, "pressed button. blestate: ${ConnectionManager.bleState}") when (ConnectionManager.bleState) { BLESTATE.DISCONNECTED -> { @@ -88,7 +92,12 @@ class OpenBikeSensorActivity : BaseActivity() { ConnectionManager.startScan(this) } } - BLESTATE.FOUND -> ConnectionManager.connect(notifyingCharacteristicsToSubscribeTo,this) + + BLESTATE.FOUND -> ConnectionManager.connect( + notifyingCharacteristicsToSubscribeTo, + this + ) + BLESTATE.CONNECTED -> ConnectionManager.disconnect(ConnectionManager.scanResult.device) else -> ConnectionManager.stopScan() } @@ -97,14 +106,19 @@ class OpenBikeSensorActivity : BaseActivity() { // handlebar width binding.handleBarWidth.maxValue = 60 binding.handleBarWidth.minValue = 0 - binding.handleBarWidth.value = SharedPref.Settings.Ride.OvertakeWidth.getHandlebarWidth(this) - binding.handleBarWidth.setOnValueChangedListener { picker, oldVal, newVal -> - SharedPref.Settings.Ride.OvertakeWidth.setTotalWidthThroughHandlebarWidth(newVal, this) + binding.handleBarWidth.value = + SharedPref.Settings.Ride.OvertakeWidth.getHandlebarWidth(this) + binding.handleBarWidth.setOnValueChangedListener { _, _, newVal -> + SharedPref.Settings.Ride.OvertakeWidth.setTotalWidthThroughHandlebarWidth( + newVal, + this + ) } } private val bluetoothAdapter: BluetoothAdapter by lazy { - val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager + val bluetoothManager = + getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager bluetoothManager.adapter } @@ -133,42 +147,52 @@ class OpenBikeSensorActivity : BaseActivity() { ConnectionEventListener().apply { onScanStart = { Log.d(TAG, "connectionEventListener: onScanStart") - // blestate = BLESTATE.SEARCHING updateUI() } onDeviceFound = { Log.d(TAG, "connectionEventListener: onDeviceFound") deviceName = it.name - // blestate = BLESTATE.FOUND updateUI() } onScanStop = { Log.d(TAG, "connectionEventListener: onScanStop") if (!it) { - // blestate = BLESTATE.DISCONNECTED updateUI() } } onConnectionSetupComplete = { Log.d(TAG, "connectionEventListener: onConnectionSetupComplete") deviceName = it.device.name - SharedPref.App.OpenBikeSensor.setObsDeviceName(deviceName, this@OpenBikeSensorActivity) - // blestate = BLESTATE.CONNECTED + SharedPref.App.OpenBikeSensor.setObsDeviceName( + deviceName, + this@OpenBikeSensorActivity + ) updateUI() } onDisconnect = { Log.d(TAG, "connectionEventListener: onDisconnect") deviceName = "---" SharedPref.App.OpenBikeSensor.deleteObsDeviceName(this@OpenBikeSensorActivity) - // blestate = BLESTATE.DISCONNECTED updateUI() } onSensorDistanceNotification = { - binding.deviceInfoTextView.text = this@OpenBikeSensorActivity.getString(R.string.obs_activity_text_last_distance,it.leftDistance) - setColePassBarColor(it.leftDistance.toInt()) + // it.leftDistance ist (in cm) – wir glätten die Anzeige mit einem gleitenden Median + val distanceCm = it.leftDistance.toInt() + movingMedian.newValue(distanceCm) + val medianDistance = movingMedian.median + + binding.deviceInfoTextView.text = + this@OpenBikeSensorActivity.getString( + R.string.obs_activity_text_last_distance, + medianDistance + ) + setColePassBarColor(medianDistance) } onClosePassNotification = { - Log.d(TAG, "ClosePass - time: ${it.obsTime} left distance: ${it.leftDistance} right distance: ${it.rightDistance}") + Log.d( + TAG, + "ClosePass - time: ${it.obsTime} left distance: ${it.leftDistance} right distance: ${it.rightDistance}" + ) } onTimeRead = { Log.d(TAG, "Time - time: $it") @@ -183,7 +207,8 @@ class OpenBikeSensorActivity : BaseActivity() { val green = (255 * normalizedValue) / 100 val blue = 0 // Color and Progress are dependant of distance - binding.progressBarClosePass.progressTintList = ColorStateList.valueOf(Color.rgb(red,green,blue)) + binding.progressBarClosePass.progressTintList = + ColorStateList.valueOf(Color.rgb(red, green, blue)) binding.progressBarClosePass.progress = normalizedValue } @@ -196,4 +221,117 @@ class OpenBikeSensorActivity : BaseActivity() { binding.toolbar.backButton.setOnClickListener { v -> finish() } } -} \ No newline at end of file + /** + * Gleitinenden Median über die letzten Messwerte berechnen. + * Analog zur MovingMedian-Implementierung in OBSLiteActivity, aber nur für die Anzeige hier. + */ + class MovingMedian { + private val TAG = "MovingMedian_LOG" + var distanceArray: ArrayList = ArrayList() + var windowSize = 3 + var median: Int = 0 + + // Pair class for the value and its index + class Pair(private var value: Int, private var index: Int) : Comparable { + override fun compareTo(other: Pair?): Int { + return if (index == other?.index) { + 0 + } else if (value == other?.value) { + index.compareTo(other.index) + } else { + value.compareTo(other!!.value) + } + } + + fun value(): Int = value + + fun renew(v: Int, p: Int) { + value = v + index = p + } + + override fun toString(): String { + return String.format("(%d, %d)", value, index) + } + } + + // Function to print the median for the current window + private fun printMedian( + minSet: TreeSet, + maxSet: TreeSet, + window: Int + ): Int { + return if (window % 2 == 0) { + (((minSet.last()!!.value() + maxSet.first()!!.value()) / 2.0).toInt()) + } else { + if (minSet.size > maxSet.size) minSet.last()!!.value() else maxSet.first()!!.value() + } + } + + // Function to find the median of every window of size k + private fun findMedian(arr: ArrayList, k: Int): ArrayList { + val minSet = TreeSet() + val maxSet = TreeSet() + + val result: ArrayList = ArrayList() + + // To hold the pairs, we will keep renewing these instead of creating the new pairs + val windowPairs = arrayOfNulls(k) + for (i in 0 until k) { + windowPairs[i] = Pair(arr[i], i) + } + + // Add k/2 items to maxSet + for (i in 0 until (k / 2)) { + maxSet.add(windowPairs[i]) + } + for (i in k / 2 until k) { + if (arr[i] < maxSet.first()!!.value()) { + minSet.add(windowPairs[i]) + } else { + minSet.add(maxSet.pollFirst()) + maxSet.add(windowPairs[i]) + } + } + result.add(printMedian(minSet, maxSet, k)) + for (i in k until arr.size) { + val temp = windowPairs[i % k] + if (temp!!.value() <= minSet.last()!!.value()) { + minSet.remove(temp) + temp.renew(arr[i], i) + if (temp.value() < maxSet.first()!!.value()) { + minSet.add(temp) + } else { + minSet.add(maxSet.pollFirst()) + maxSet.add(temp) + } + } else { + maxSet.remove(temp) + temp.renew(arr[i], i) + if (temp.value() > minSet.last()!!.value()) { + maxSet.add(temp) + } else { + maxSet.add(minSet.pollLast()) + minSet.add(temp) + } + } + result.add(printMedian(minSet, maxSet, k)) + } + return result + } + + fun newValue(distance: Int) { + // max array size ist 122 (~ 5 Sekunden), älteste Werte löschen, wenn überschritten + if (distanceArray.size >= 122) { + distanceArray = distanceArray.drop(1) as ArrayList + } + distanceArray.add(distance) + // calculate median only if distanceArray is big enough. + if (distanceArray.size >= windowSize) { + val medians = findMedian(distanceArray, windowSize) + // Wichtig: Median des *letzten* Fensters verwenden, nicht das Minimum aller + median = medians.last() + } + } + } +} diff --git a/app/src/main/java/de/tuberlin/mcc/simra/app/util/Utils.java b/app/src/main/java/de/tuberlin/mcc/simra/app/util/Utils.java index 82b608a4..63159aa1 100644 --- a/app/src/main/java/de/tuberlin/mcc/simra/app/util/Utils.java +++ b/app/src/main/java/de/tuberlin/mcc/simra/app/util/Utils.java @@ -618,15 +618,31 @@ public static String[] getCorrectRegionNames(String[] regionLines) { * @return the correct display name as a string according to System locale */ public static String getCorrectRegionName(String regionLine) { + if (regionLine == null) { + return ""; + } + + String[] parts = regionLine.split("="); + + // Falls das Format nicht stimmt (kein "=" o.ä.), lieber + // den Originalstring zurückgeben als abzustürzen. + if (parts.length == 0) { + return regionLine.trim(); + } + String locale = Resources.getSystem().getConfiguration().locale.getLanguage(); boolean languageIsEnglish = locale.equals(new Locale("en").getLanguage()); - /*if(regionLine.split("=").length < 2) { - return ""; - }*/ + if (languageIsEnglish) { - return regionLine.split("=")[0]; + // Englisch: erster Teil, sonst irgendein Fallback + return parts[0].trim(); } else { - return regionLine.split("=")[1]; + // Deutsch: zweiter Teil, falls vorhanden, sonst erster + if (parts.length >= 2) { + return parts[1].trim(); + } else { + return parts[0].trim(); + } } } @@ -682,7 +698,7 @@ public static boolean isGPSAvailable(LocationManager locationManager) { */ public static void getGPSLocation(Context context, long timeoutMs, LocationCallback callback) { LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); - + if (!isGPSAvailable(locationManager)) { callback.onLocationResult(null); return; @@ -713,13 +729,13 @@ public void onProviderDisabled(String provider) { // Request location updates try { locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListener); - + // Set timeout new Handler().postDelayed(() -> { locationManager.removeUpdates(locationListener); callback.onLocationResult(null); }, timeoutMs); - + } catch (SecurityException e) { callback.onLocationResult(null); } @@ -817,17 +833,17 @@ public static void showBluetoothNotEnableWarning(ActivityResultLauncher public static ActivityResultLauncher activityResultLauncher(Activity activity) { return ((ComponentActivity)activity).registerForActivityResult( - new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback() { - @Override - public void onActivityResult(ActivityResult result) { - if (result.getResultCode() == Activity.RESULT_OK) { - Log.e(TAG, "Activity result: OK"); - // There are no request codes - Intent data = result.getData(); - Log.d(TAG, "data: " + data); + new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback() { + @Override + public void onActivityResult(ActivityResult result) { + if (result.getResultCode() == Activity.RESULT_OK) { + Log.e(TAG, "Activity result: OK"); + // There are no request codes + Intent data = result.getData(); + Log.d(TAG, "data: " + data); + } } } - } - ); + ); } } diff --git a/build.gradle b/build.gradle index cd5f28e4..c6af1707 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ buildscript { maven { url 'https://jitpack.io' } } dependencies { - classpath 'com.android.tools.build:gradle:8.11.1' + classpath 'com.android.tools.build:gradle:8.13.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22" // NOTE: Do not place your application dependencies here; they belong