feat(gateway): food/kitchen scale support (arboleaf CK10G) v1.6.0

- Generic parser now supports food scale weight ranges (1g-15kg)
  with candidates for gram, 0.1g, and 0.5g divisors
- WebSocket sends grams (unit: 'g') for weights under 15kg
- MainActivity displays grams for food-scale readings
- Enhanced debug: raw byte dump on decode failure with index/decimal/hex
- versionCode=5, versionName=1.6.0
This commit is contained in:
dadaloop82
2026-04-15 15:49:12 +00:00
parent 71c49e2c82
commit d03a4853b5
5 changed files with 64 additions and 26 deletions
+2 -2
View File
@@ -11,8 +11,8 @@ android {
applicationId = "it.dadaloop.evershelf.scalegate"
minSdk = 24
targetSdk = 34
versionCode = 4
versionName = "1.5.0"
versionCode = 5
versionName = "1.6.0"
}
buildFeatures {
@@ -423,7 +423,12 @@ class BleScaleManager(
if (reading != null && reading.weightKg > 0f) {
mainHandler.post { listener.onWeightReceived(reading) }
} else {
mainHandler.post { listener.onDebugEvent("⚠️ Peso non decodificato") }
val rawDump = data.mapIndexed { i, b ->
val v = b.toInt() and 0xFF
val h = "%02X".format(v)
"[$i]=$v(0x$h)"
}.joinToString(" ")
mainHandler.post { listener.onDebugEvent("⚠️ Peso non decodificato\n RAW: $rawDump") }
}
}
}
@@ -136,12 +136,18 @@ class GatewayWebSocketServer(
}
private fun buildWeightJson(weightKg: Float, stable: Boolean): String {
// Round to 2 decimal places
val rounded = (weightKg * 100).toLong() / 100.0
val obj = JSONObject()
obj.put("type", "weight")
obj.put("value", rounded)
obj.put("unit", "kg")
// Food scale (<15kg): send grams for better precision
if (weightKg < 15f) {
val grams = Math.round(weightKg * 1000f)
obj.put("value", grams)
obj.put("unit", "g")
} else {
val rounded = (weightKg * 100).toLong() / 100.0
obj.put("value", rounded)
obj.put("unit", "kg")
}
obj.put("stable", stable)
obj.put("timestamp", System.currentTimeMillis())
return obj.toString()
@@ -222,8 +222,14 @@ class MainActivity : AppCompatActivity(), BleScaleListener, ServerEventListener
}
override fun onWeightReceived(reading: WeightReading) {
val kg = "%.2f".format(reading.weightKg)
binding.tvWeight.text = "$kg kg"
val isFood = reading.weightKg < 15f
if (isFood) {
val grams = (reading.weightKg * 1000).toInt()
binding.tvWeight.text = "$grams g"
} else {
val kg = "%.2f".format(reading.weightKg)
binding.tvWeight.text = "$kg kg"
}
val extras = buildString {
reading.fatPct?.let { append("Grasso: ${"%.1f".format(it)}% ") }
@@ -267,7 +267,7 @@ object ScaleProtocol {
return null
}
// --- Safe generic fallback ---
// --- Safe generic fallback (body + food scales) ---
fun parseGenericSafe(data: ByteArray, debug: ((String) -> Unit)? = null): WeightReading? {
if (data.size < 4) {
@@ -275,30 +275,51 @@ object ScaleProtocol {
return null
}
data class Candidate(val pos: Int, val div: Float, val be: Boolean, val label: String)
data class Candidate(
val pos: Int, val div: Float, val be: Boolean,
val minKg: Float, val maxKg: Float, val label: String,
)
val candidates = listOf(
Candidate(1, 100f, true, "pos1 BE/100"),
Candidate(1, 100f, false, "pos1 LE/100"),
Candidate(3, 100f, true, "pos3 BE/100"),
Candidate(3, 100f, false, "pos3 LE/100"),
Candidate(2, 100f, true, "pos2 BE/100"),
Candidate(2, 100f, false, "pos2 LE/100"),
Candidate(1, 10f, true, "pos1 BE/10"),
Candidate(1, 10f, false, "pos1 LE/10"),
Candidate(3, 10f, true, "pos3 BE/10"),
Candidate(3, 10f, false, "pos3 LE/10"),
Candidate(1, 20f, true, "pos1 BE/20"),
Candidate(1, 20f, false, "pos1 LE/20"),
// Food scale: raw value in grams (div=1 -> kg=raw/1000 via pos)
Candidate(1, 1000f, false, 0.001f, 15f, "pos1 LE/g"),
Candidate(1, 1000f, true, 0.001f, 15f, "pos1 BE/g"),
Candidate(2, 1000f, false, 0.001f, 15f, "pos2 LE/g"),
Candidate(2, 1000f, true, 0.001f, 15f, "pos2 BE/g"),
Candidate(3, 1000f, false, 0.001f, 15f, "pos3 LE/g"),
Candidate(3, 1000f, true, 0.001f, 15f, "pos3 BE/g"),
// Food scale: raw in 0.1g (div=10000)
Candidate(1, 10000f, false, 0.001f, 15f, "pos1 LE/0.1g"),
Candidate(1, 10000f, true, 0.001f, 15f, "pos1 BE/0.1g"),
Candidate(2, 10000f, false, 0.001f, 15f, "pos2 LE/0.1g"),
Candidate(2, 10000f, true, 0.001f, 15f, "pos2 BE/0.1g"),
// Food scale: raw in 0.5g (div=2000)
Candidate(1, 2000f, false, 0.001f, 15f, "pos1 LE/0.5g"),
Candidate(1, 2000f, true, 0.001f, 15f, "pos1 BE/0.5g"),
// Body scale: standard divisors
Candidate(1, 100f, true, 2f, 250f, "pos1 BE/100"),
Candidate(1, 100f, false, 2f, 250f, "pos1 LE/100"),
Candidate(3, 100f, true, 2f, 250f, "pos3 BE/100"),
Candidate(3, 100f, false, 2f, 250f, "pos3 LE/100"),
Candidate(2, 100f, true, 2f, 250f, "pos2 BE/100"),
Candidate(2, 100f, false, 2f, 250f, "pos2 LE/100"),
Candidate(1, 10f, true, 2f, 250f, "pos1 BE/10"),
Candidate(1, 10f, false, 2f, 250f, "pos1 LE/10"),
Candidate(3, 10f, true, 2f, 250f, "pos3 BE/10"),
Candidate(3, 10f, false, 2f, 250f, "pos3 LE/10"),
Candidate(1, 20f, true, 2f, 250f, "pos1 BE/20"),
Candidate(1, 20f, false, 2f, 250f, "pos1 LE/20"),
)
for (c in candidates) {
if (c.pos + 1 >= data.size) continue
val raw = if (c.be) u16be(data, c.pos) else u16le(data, c.pos)
if (raw == 0) continue
val w = raw / c.div
if (w in 2f..250f) {
val wStr = "%.2f".format(w)
debug?.invoke("generic [" + c.label + "]: raw=$raw -> ${wStr}kg (UNSTABLE)")
if (w in c.minKg..c.maxKg) {
val grams = (w * 1000).toInt()
val wStr = "%.3f".format(w)
debug?.invoke("generic [" + c.label + "]: raw=$raw -> ${wStr}kg (${grams}g) (UNSTABLE)")
return WeightReading(w, stable = false)
}
}