1
0
mirror of https://github.com/oliexdev/openScale.git synced 2025-08-23 08:43:15 +02:00

Refactor unique number generation for legacy scales. Additionally, fix corrected date parsing and history data for the MiScale2 driver:

This commit is contained in:
oliexdev
2025-08-20 08:36:34 +02:00
parent 01fd3214aa
commit 9dc61d7e53
5 changed files with 91 additions and 79 deletions

View File

@@ -83,6 +83,8 @@ public abstract class BluetoothCommunication {
private List<ScaleUser> cachedAppUserList; private List<ScaleUser> cachedAppUserList;
protected ScaleMeasurement cachedLastMeasurementForSelectedUser; protected ScaleMeasurement cachedLastMeasurementForSelectedUser;
private int uniqueBase = -1;
public BluetoothCommunication(Context context) public BluetoothCommunication(Context context)
{ {
this.context = context; this.context = context;
@@ -129,6 +131,21 @@ public abstract class BluetoothCommunication {
return null; 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) { protected void requestUserInteraction(UserInteractionType interactionType, Object data) {
if (callbackBtHandler != null) { if (callbackBtHandler != null) {
Message msg = callbackBtHandler.obtainMessage(BT_STATUS.USER_INTERACTION_REQUIRED.ordinal()); Message msg = callbackBtHandler.obtainMessage(BT_STATUS.USER_INTERACTION_REQUIRED.ordinal());

View File

@@ -223,23 +223,4 @@ public class BluetoothMiScale extends BluetoothCommunication {
return false; 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;
}
} }

View File

@@ -49,6 +49,8 @@ public class BluetoothMiScale2 extends BluetoothCommunication {
private int pendingHistoryCount = -1; private int pendingHistoryCount = -1;
private int importedHistory = 0; private int importedHistory = 0;
private boolean historyMode = false;
// warn only once per session if we see "unexpected" flags in history status byte // warn only once per session if we see "unexpected" flags in history status byte
private boolean historyUnexpectedFlagWarned = false; private boolean historyUnexpectedFlagWarned = false;
@@ -65,6 +67,8 @@ public class BluetoothMiScale2 extends BluetoothCommunication {
// STOP (end of history transfer) // STOP (end of history transfer)
if (value.length == 1 && value[0] == 0x03) { if (value.length == 1 && value[0] == 0x03) {
historyMode = false;
// Check for truncated leftover BEFORE flush (for debug visibility) // Check for truncated leftover BEFORE flush (for debug visibility)
int leftoverLen = histBuf.size(); int leftoverLen = histBuf.size();
if (leftoverLen % 10 != 0) { if (leftoverLen % 10 != 0) {
@@ -95,15 +99,27 @@ public class BluetoothMiScale2 extends BluetoothCommunication {
// Live frame (13 bytes) // Live frame (13 bytes)
if (value.length == 13) { if (value.length == 13) {
parseLiveFrame(value); if (historyMode) {
return; if (parseLiveFrame(value)) {
importedHistory++;
LogManager.d(TAG, "History (13B) parsed");
}
return;
} else {
parseLiveFrame(value);
return;
}
} }
// Rare: two live frames combined in one notify (2x13) // Rare: two live frames combined in one notify (2x13)
if (value.length == 26) { if (value.length == 26) {
LogManager.d(TAG, "26-byte payload -> split into 2x13 live frames"); LogManager.d(TAG, "26-byte payload -> split into 2x13 live frames");
parseLiveFrame(Arrays.copyOfRange(value, 0, 13)); boolean s1 = parseLiveFrame(Arrays.copyOfRange(value, 0, 13));
parseLiveFrame(Arrays.copyOfRange(value, 13, 26)); boolean s2 = parseLiveFrame(Arrays.copyOfRange(value, 13, 26));
if (historyMode) {
int inc = (s1 ? 1 : 0) + (s2 ? 1 : 0);
if (inc > 0) importedHistory += inc;
}
return; return;
} }
@@ -160,6 +176,7 @@ public class BluetoothMiScale2 extends BluetoothCommunication {
case 4: { // trigger history transfer case 4: { // trigger history transfer
writeBytes(BluetoothGattUuid.SERVICE_BODY_COMPOSITION, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, new byte[]{0x02}); writeBytes(BluetoothGattUuid.SERVICE_BODY_COMPOSITION, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, new byte[]{0x02});
LogManager.d(TAG, "History transfer triggered (0x02)"); LogManager.d(TAG, "History transfer triggered (0x02)");
historyMode = true;
stopMachineState(); // wait for notifications stopMachineState(); // wait for notifications
break; break;
} }
@@ -171,27 +188,22 @@ public class BluetoothMiScale2 extends BluetoothCommunication {
// --- Parsing --- // --- Parsing ---
private void parseLiveFrame(byte[] data) { private boolean parseLiveFrame(byte[] data) {
// ctrl bytes final byte c0 = data[0], c1 = data[1];
final byte c0 = data[0]; final boolean isLbs = isBitSet(c0,0), isCatty = isBitSet(c1,6),
final byte c1 = data[1]; isStabilized = isBitSet(c1,5), isRemoved = isBitSet(c1,7),
isImpedance = isBitSet(c1,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);
if (!isStabilized || isRemoved) { if (!isStabilized || isRemoved) {
LogManager.d(TAG, "Live ignored (unstable/removed)"); LogManager.d(TAG, "Live ignored (unstable/removed)");
return; return false;
} }
final int year = ((data[3] & 0xFF) << 8) | (data[4] & 0xFF); final int year = ((data[3] & 0xFF) << 8) | (data[2] & 0xFF);
final int month = (data[5] & 0xFF); final int month = (data[4] & 0xFF);
final int day = (data[6] & 0xFF); final int day = (data[5] & 0xFF);
final int hour = (data[7] & 0xFF); final int hour = (data[6] & 0xFF);
final int minute = (data[8] & 0xFF); final int minute = (data[7] & 0xFF);
int weightRaw = ((data[12] & 0xFF) << 8) | (data[11] & 0xFF); int weightRaw = ((data[12] & 0xFF) << 8) | (data[11] & 0xFF);
float weight = (isLbs || isCatty) ? (weightRaw / 100.0f) : (weightRaw / 200.0f); float weight = (isLbs || isCatty) ? (weightRaw / 100.0f) : (weightRaw / 200.0f);
@@ -199,7 +211,7 @@ public class BluetoothMiScale2 extends BluetoothCommunication {
float impedance = 0.0f; float impedance = 0.0f;
if (isImpedance) { if (isImpedance) {
impedance = ((data[10] & 0xFF) << 8) | (data[9] & 0xFF); impedance = ((data[10] & 0xFF) << 8) | (data[9] & 0xFF);
LogManager.d(TAG, "Impedance: " + impedance + " Ω"); LogManager.d(TAG, "Impedance: " + impedance + " Ohm");
} }
try { try {
@@ -208,7 +220,7 @@ public class BluetoothMiScale2 extends BluetoothCommunication {
if (!validateDate(dt, 20)) { if (!validateDate(dt, 20)) {
LogManager.w(TAG, "Live date out of range: " + dt, null); LogManager.w(TAG, "Live date out of range: " + dt, null);
return; return false;
} }
final ScaleUser user = getSelectedScaleUser(); final ScaleUser user = getSelectedScaleUser();
@@ -222,15 +234,17 @@ public class BluetoothMiScale2 extends BluetoothCommunication {
m.setWater(lib.getWater(weight, impedance)); m.setWater(lib.getWater(weight, impedance));
m.setVisceralFat(lib.getVisceralFat(weight)); m.setVisceralFat(lib.getVisceralFat(weight));
m.setFat(lib.getBodyFat(weight, impedance)); 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.setLbm(lib.getLBM(weight, impedance));
m.setBone(lib.getBoneMass(weight, impedance)); m.setBone(lib.getBoneMass(weight, impedance));
} }
addScaleMeasurement(m); addScaleMeasurement(m);
LogManager.i(TAG, "Live saved @ " + dt + " kg=" + m.getWeight()); LogManager.i(TAG, "Live saved @ " + dt + " kg=" + m.getWeight());
return true;
} catch (ParseException e) { } catch (ParseException e) {
setBluetoothStatus(UNEXPECTED_ERROR, "Live date parse error (" + e.getMessage() + ")"); 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); int weightRaw = ((data[2] & 0xFF) << 8) | (data[1] & 0xFF);
float weight = (isLbs || isCatty) ? (weightRaw / 100.0f) : (weightRaw / 200.0f); float weight = (isLbs || isCatty) ? (weightRaw / 100.0f) : (weightRaw / 200.0f);
int year = ((data[4] & 0xFF) << 8) | (data[3] & 0xFF); final int year = ((data[4] & 0xFF) << 8) | (data[3] & 0xFF);
int month = data[5] & 0xFF; final int month = (data[5] & 0xFF);
int day = data[6] & 0xFF; final int day = (data[6] & 0xFF);
int hour = data[7] & 0xFF; final int hour = (data[7] & 0xFF);
int minute = data[8] & 0xFF; final int minute = (data[8] & 0xFF);
// int second = data[9] & 0xFF; // int second = data[9] & 0xFF;
try { try {
@@ -294,20 +308,35 @@ public class BluetoothMiScale2 extends BluetoothCommunication {
private void appendAndParseHistory(byte[] chunk) { private void appendAndParseHistory(byte[] chunk) {
try { 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); histBuf.write(chunk, 0, chunk.length);
byte[] buf = histBuf.toByteArray(); byte[] buf = histBuf.toByteArray();
// Process all complete 10-byte records in buffer
int full = (buf.length / 10) * 10; int full = (buf.length / 10) * 10;
if (full >= 10) { if (full >= 10) {
int ok = 0; int ok = 0;
for (int off = 0; off < full; off += 10) { 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) { if (ok > 0) {
importedHistory += ok; importedHistory += ok;
LogManager.d(TAG, "History parsed: " + ok + " record(s) from " + full + " bytes"); LogManager.d(TAG, "History parsed: " + ok + " record(s) from " + full + " bytes");
} }
// keep remainder
// Reset buffer and keep any remainder
histBufReset(); histBufReset();
if (buf.length > full) { if (buf.length > full) {
histBuf.write(buf, full, 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); Calendar min = Calendar.getInstance(); min.add(Calendar.YEAR, -rangeYears);
return weightDate.before(max.getTime()) && weightDate.after(min.getTime()); 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;
}
} }

View File

@@ -169,23 +169,4 @@ public class BluetoothYunmaiSE_Mini extends BluetoothCommunication {
addScaleMeasurement(scaleBtData); 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;
}
} }

View File

@@ -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.bluetooth.data.ScaleUser
import com.health.openscale.core.data.MeasurementTypeKey import com.health.openscale.core.data.MeasurementTypeKey
import com.health.openscale.core.database.DatabaseRepository 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 com.health.openscale.core.utils.LogManager
import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -50,6 +52,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import java.util.Date import java.util.Date
import kotlin.random.Random
import kotlin.text.find import kotlin.text.find
import kotlin.text.toDouble import kotlin.text.toDouble
@@ -125,6 +128,18 @@ class LegacyScaleAdapter(
LogManager.i(TAG, "Successfully provided ${userListFromDb.size} users to legacy driver ${bluetoothDriverInstance.driverName()}.") 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 -> this.currentInternalUser?.let { userId ->
if (userId.id != -1) { if (userId.id != -1) {
LogManager.d(TAG, "Attempting to load last measurement for user ID: $userId using existing repository methods.") LogManager.d(TAG, "Attempting to load last measurement for user ID: $userId using existing repository methods.")