From 9dc61d7e530c17236ee0b5c6ce92b22ebf3e9efa Mon Sep 17 00:00:00 2001 From: oliexdev Date: Wed, 20 Aug 2025 08:36:34 +0200 Subject: [PATCH] Refactor unique number generation for legacy scales. Additionally, fix corrected date parsing and history data for the MiScale2 driver: --- .../scalesJava/BluetoothCommunication.java | 17 +++ .../scalesJava/BluetoothMiScale.java | 19 ---- .../scalesJava/BluetoothMiScale2.java | 100 +++++++++++------- .../scalesJava/BluetoothYunmaiSE_Mini.java | 19 ---- .../scalesJava/LegacyScaleAdapter.kt | 15 +++ 5 files changed, 91 insertions(+), 79 deletions(-) diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/scalesJava/BluetoothCommunication.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/scalesJava/BluetoothCommunication.java index e7102536..3005361a 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/scalesJava/BluetoothCommunication.java +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/scalesJava/BluetoothCommunication.java @@ -83,6 +83,8 @@ public abstract class BluetoothCommunication { private List cachedAppUserList; protected ScaleMeasurement cachedLastMeasurementForSelectedUser; + private int uniqueBase = -1; + public BluetoothCommunication(Context context) { this.context = context; @@ -129,6 +131,21 @@ public abstract class BluetoothCommunication { return null; } + public void setUniqueNumber(int base) { + this.uniqueBase = base; + } + + protected int getUniqueNumber() { + if (uniqueBase == -1) { + LogManager.w(TAG, "(Unique number base not set! Call setUniqueNumber() first.", null); + uniqueBase = 99; + } + int userId = getSelectedScaleUser().getId(); + int finalUnique = uniqueBase + userId; + LogManager.d(TAG, "Returning unique number " + finalUnique + " (base=" + uniqueBase + " + userId=" + userId + ")"); + return finalUnique; + } + protected void requestUserInteraction(UserInteractionType interactionType, Object data) { if (callbackBtHandler != null) { Message msg = callbackBtHandler.obtainMessage(BT_STATUS.USER_INTERACTION_REQUIRED.ordinal()); diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/scalesJava/BluetoothMiScale.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/scalesJava/BluetoothMiScale.java index e51586fb..2ea7672e 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/scalesJava/BluetoothMiScale.java +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/scalesJava/BluetoothMiScale.java @@ -223,23 +223,4 @@ public class BluetoothMiScale extends BluetoothCommunication { return false; } - - private int getUniqueNumber() { - int uniqueNumber; - - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - - uniqueNumber = prefs.getInt("uniqueNumber", 0x00); - - if (uniqueNumber == 0x00) { - Random r = new Random(); - uniqueNumber = r.nextInt(65535 - 100 + 1) + 100; - - prefs.edit().putInt("uniqueNumber", uniqueNumber).apply(); - } - - int userId = getSelectedScaleUser().getId(); - - return uniqueNumber + userId; - } } diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/scalesJava/BluetoothMiScale2.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/scalesJava/BluetoothMiScale2.java index 73a9b6dd..0870bf82 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/scalesJava/BluetoothMiScale2.java +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/scalesJava/BluetoothMiScale2.java @@ -49,6 +49,8 @@ public class BluetoothMiScale2 extends BluetoothCommunication { private int pendingHistoryCount = -1; private int importedHistory = 0; + private boolean historyMode = false; + // warn only once per session if we see "unexpected" flags in history status byte private boolean historyUnexpectedFlagWarned = false; @@ -65,6 +67,8 @@ public class BluetoothMiScale2 extends BluetoothCommunication { // STOP (end of history transfer) if (value.length == 1 && value[0] == 0x03) { + historyMode = false; + // Check for truncated leftover BEFORE flush (for debug visibility) int leftoverLen = histBuf.size(); if (leftoverLen % 10 != 0) { @@ -95,15 +99,27 @@ public class BluetoothMiScale2 extends BluetoothCommunication { // Live frame (13 bytes) if (value.length == 13) { - parseLiveFrame(value); - return; + if (historyMode) { + if (parseLiveFrame(value)) { + importedHistory++; + LogManager.d(TAG, "History (13B) parsed"); + } + return; + } else { + parseLiveFrame(value); + return; + } } // Rare: two live frames combined in one notify (2x13) if (value.length == 26) { LogManager.d(TAG, "26-byte payload -> split into 2x13 live frames"); - parseLiveFrame(Arrays.copyOfRange(value, 0, 13)); - parseLiveFrame(Arrays.copyOfRange(value, 13, 26)); + boolean s1 = parseLiveFrame(Arrays.copyOfRange(value, 0, 13)); + boolean s2 = parseLiveFrame(Arrays.copyOfRange(value, 13, 26)); + if (historyMode) { + int inc = (s1 ? 1 : 0) + (s2 ? 1 : 0); + if (inc > 0) importedHistory += inc; + } return; } @@ -160,6 +176,7 @@ public class BluetoothMiScale2 extends BluetoothCommunication { case 4: { // trigger history transfer writeBytes(BluetoothGattUuid.SERVICE_BODY_COMPOSITION, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, new byte[]{0x02}); LogManager.d(TAG, "History transfer triggered (0x02)"); + historyMode = true; stopMachineState(); // wait for notifications break; } @@ -171,27 +188,22 @@ public class BluetoothMiScale2 extends BluetoothCommunication { // --- Parsing --- - private void parseLiveFrame(byte[] data) { - // ctrl bytes - final byte c0 = data[0]; - final byte c1 = data[1]; - - final boolean isLbs = isBitSet(c0, 0); - final boolean isCatty = isBitSet(c1, 6); // catty/jin - final boolean isStabilized = isBitSet(c1, 5); - final boolean isRemoved = isBitSet(c1, 7); - final boolean isImpedance = isBitSet(c1, 1); + private boolean parseLiveFrame(byte[] data) { + final byte c0 = data[0], c1 = data[1]; + final boolean isLbs = isBitSet(c0,0), isCatty = isBitSet(c1,6), + isStabilized = isBitSet(c1,5), isRemoved = isBitSet(c1,7), + isImpedance = isBitSet(c1,1); if (!isStabilized || isRemoved) { LogManager.d(TAG, "Live ignored (unstable/removed)"); - return; + return false; } - final int year = ((data[3] & 0xFF) << 8) | (data[4] & 0xFF); - final int month = (data[5] & 0xFF); - final int day = (data[6] & 0xFF); - final int hour = (data[7] & 0xFF); - final int minute = (data[8] & 0xFF); + final int year = ((data[3] & 0xFF) << 8) | (data[2] & 0xFF); + final int month = (data[4] & 0xFF); + final int day = (data[5] & 0xFF); + final int hour = (data[6] & 0xFF); + final int minute = (data[7] & 0xFF); int weightRaw = ((data[12] & 0xFF) << 8) | (data[11] & 0xFF); float weight = (isLbs || isCatty) ? (weightRaw / 100.0f) : (weightRaw / 200.0f); @@ -199,7 +211,7 @@ public class BluetoothMiScale2 extends BluetoothCommunication { float impedance = 0.0f; if (isImpedance) { impedance = ((data[10] & 0xFF) << 8) | (data[9] & 0xFF); - LogManager.d(TAG, "Impedance: " + impedance + " Ω"); + LogManager.d(TAG, "Impedance: " + impedance + " Ohm"); } try { @@ -208,7 +220,7 @@ public class BluetoothMiScale2 extends BluetoothCommunication { if (!validateDate(dt, 20)) { LogManager.w(TAG, "Live date out of range: " + dt, null); - return; + return false; } final ScaleUser user = getSelectedScaleUser(); @@ -222,15 +234,17 @@ public class BluetoothMiScale2 extends BluetoothCommunication { m.setWater(lib.getWater(weight, impedance)); m.setVisceralFat(lib.getVisceralFat(weight)); m.setFat(lib.getBodyFat(weight, impedance)); - m.setMuscle(lib.getMuscle(weight, impedance)); // expects % from MiScaleLib + m.setMuscle(lib.getMuscle(weight, impedance)); m.setLbm(lib.getLBM(weight, impedance)); m.setBone(lib.getBoneMass(weight, impedance)); } addScaleMeasurement(m); LogManager.i(TAG, "Live saved @ " + dt + " kg=" + m.getWeight()); + return true; } catch (ParseException e) { setBluetoothStatus(UNEXPECTED_ERROR, "Live date parse error (" + e.getMessage() + ")"); + return false; } } @@ -262,11 +276,11 @@ public class BluetoothMiScale2 extends BluetoothCommunication { int weightRaw = ((data[2] & 0xFF) << 8) | (data[1] & 0xFF); float weight = (isLbs || isCatty) ? (weightRaw / 100.0f) : (weightRaw / 200.0f); - int year = ((data[4] & 0xFF) << 8) | (data[3] & 0xFF); - int month = data[5] & 0xFF; - int day = data[6] & 0xFF; - int hour = data[7] & 0xFF; - int minute = data[8] & 0xFF; + final int year = ((data[4] & 0xFF) << 8) | (data[3] & 0xFF); + final int month = (data[5] & 0xFF); + final int day = (data[6] & 0xFF); + final int hour = (data[7] & 0xFF); + final int minute = (data[8] & 0xFF); // int second = data[9] & 0xFF; try { @@ -294,20 +308,35 @@ public class BluetoothMiScale2 extends BluetoothCommunication { private void appendAndParseHistory(byte[] chunk) { try { + // Ignore 13-byte frames when in historyMode (they get parsed separately) + if (historyMode && (chunk.length == 13)) { + return; + } + // Ignore control/short frames + if (chunk.length < 10) { + return; + } + + // Append raw chunk to buffer histBuf.write(chunk, 0, chunk.length); byte[] buf = histBuf.toByteArray(); + // Process all complete 10-byte records in buffer int full = (buf.length / 10) * 10; if (full >= 10) { int ok = 0; for (int off = 0; off < full; off += 10) { - if (parseHistoryRecord10(Arrays.copyOfRange(buf, off, off + 10))) ok++; + byte[] rec = Arrays.copyOfRange(buf, off, off + 10); + if (parseHistoryRecord10(rec)) { + ok++; + } } if (ok > 0) { importedHistory += ok; LogManager.d(TAG, "History parsed: " + ok + " record(s) from " + full + " bytes"); } - // keep remainder + + // Reset buffer and keep any remainder histBufReset(); if (buf.length > full) { histBuf.write(buf, full, buf.length - full); @@ -345,15 +374,4 @@ public class BluetoothMiScale2 extends BluetoothCommunication { Calendar min = Calendar.getInstance(); min.add(Calendar.YEAR, -rangeYears); return weightDate.before(max.getTime()) && weightDate.after(min.getTime()); } - - private int getUniqueNumber() { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - int n = prefs.getInt("uniqueNumber", 0x00); - if (n == 0x00) { - n = new Random().nextInt(65535 - 100 + 1) + 100; - prefs.edit().putInt("uniqueNumber", n).apply(); - } - int userId = getSelectedScaleUser().getId(); - return n + userId; - } } diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/scalesJava/BluetoothYunmaiSE_Mini.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/scalesJava/BluetoothYunmaiSE_Mini.java index a609e4ec..a2d21998 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/scalesJava/BluetoothYunmaiSE_Mini.java +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/scalesJava/BluetoothYunmaiSE_Mini.java @@ -169,23 +169,4 @@ public class BluetoothYunmaiSE_Mini extends BluetoothCommunication { addScaleMeasurement(scaleBtData); } - - private int getUniqueNumber() { - int uniqueNumber; - - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - - uniqueNumber = prefs.getInt("uniqueNumber", 0x00); - - if (uniqueNumber == 0x00) { - Random r = new Random(); - uniqueNumber = r.nextInt(65535 - 100 + 1) + 100; - - prefs.edit().putInt("uniqueNumber", uniqueNumber).apply(); - } - - int userId = getSelectedScaleUser().getId(); - - return uniqueNumber + userId; - } } diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/scalesJava/LegacyScaleAdapter.kt b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/scalesJava/LegacyScaleAdapter.kt index e2e38802..b760e1da 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/scalesJava/LegacyScaleAdapter.kt +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/scalesJava/LegacyScaleAdapter.kt @@ -32,6 +32,8 @@ import com.health.openscale.core.bluetooth.data.ScaleMeasurement import com.health.openscale.core.bluetooth.data.ScaleUser import com.health.openscale.core.data.MeasurementTypeKey import com.health.openscale.core.database.DatabaseRepository +import com.health.openscale.core.database.UserSettingsRepository +import com.health.openscale.core.database.provideUserSettingsRepository import com.health.openscale.core.utils.LogManager import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope @@ -50,6 +52,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import java.lang.ref.WeakReference import java.util.Date +import kotlin.random.Random import kotlin.text.find import kotlin.text.toDouble @@ -125,6 +128,18 @@ class LegacyScaleAdapter( LogManager.i(TAG, "Successfully provided ${userListFromDb.size} users to legacy driver ${bluetoothDriverInstance.driverName()}.") + val userSettingsRepository = provideUserSettingsRepository(applicationContext) + val keyName = "unique_number_base" + var base: Int = userSettingsRepository.observeSetting(keyName, 0).first() + if (base == 0) { + base = Random.nextInt(100, 65535) + userSettingsRepository.saveSetting(keyName, base) + LogManager.i(TAG, "Generated and saved unique_number_base=$base in DataStore") + } else { + LogManager.d(TAG, "Loaded unique_number_base=$base from DataStore") + } + + bluetoothDriverInstance.setUniqueNumber(base) this.currentInternalUser?.let { userId -> if (userId.id != -1) { LogManager.d(TAG, "Attempting to load last measurement for user ID: $userId using existing repository methods.")