Bug in FloatABC.lua: SinglePrecisionFloat mantissa crash when value is 0

Hi, I found a bug in the SmartThings Edge SDK library (st/zigbee/data_types/base_defs/FloatABC.lua). When trying to set a value to exactly 0 for a Zigbee device using SinglePrecisionFloat, the platform crashes at line 201 with the error: "SinglePrecisionFloat mantissa must be non-negative".

It seems math.frexp(0) returns 0, causing the internal calculation to result in -1 for the mantissa. Please check the logs below:

2026-05-18T23:50:08.292901553Z DEBUG SiHAS People Counter V2(CSM-300-ZB)  Received event with handler capability
2026-05-18T23:50:08.296493095Z INFO SiHAS People Counter V2(CSM-300-ZB)  <ZigbeeDevice: 82b1923d-4172-4d9f-a7c9-889ee902e2df [0x4DF7] (화장실 카운터)> received command: {"args":{},"capability":"momentary","command":"push","component":"main","named_args":{},"positional_args":{}}
2026-05-18T23:50:08.297501386Z DEBUG SiHAS People Counter V2(CSM-300-ZB)  Found CapabilityCommandDispatcher handler in zigbee_people_counter_v2
2026-05-18T23:50:08.298964845Z INFO SiHAS People Counter V2(CSM-300-ZB)  setPeopleCounter =     0
2026-05-18T23:50:08.299869095Z ERROR SiHAS People Counter V2(CSM-300-ZB)  CSM-300-ZB thread encountered error: [string "st/dispatcher.lua"]:270: Error encountered while processing event for <ZigbeeDevice: 82b1923d-4172-4d9f-a7c9-889ee902e2df [0x4DF7] (화장실 카운터)>:
    arg1: {args={value=0}, capability="momentary", command="push", component="main", named_args=RecursiveTable: args, positional_args={}}
"[string "st/zigbee/data_types/base_defs/FloatABC.lua"]:201: SinglePrecisionFloat mantissa must be non-negative"
2026-05-18T23:50:14.987655970Z DEBUG SiHAS People Counter V2(CSM-300-ZB)  Received event with handler capability
2026-05-18T23:50:14.988816429Z INFO SiHAS People Counter V2(CSM-300-ZB)  <ZigbeeDevice: 82b1923d-4172-4d9f-a7c9-889ee902e2df [0x4DF7] (화장실 카운터)> received command: {"args":{"value":0},"capability":"afterguide46998.peopleCounterV2","command":"setPeopleCounter","component":"main","named_args":{"value":0},"positional_args":[0]}
2026-05-18T23:50:14.990577137Z DEBUG SiHAS People Counter V2(CSM-300-ZB)  Found CapabilityCommandDispatcher handler in zigbee_people_counter_v2
2026-05-18T23:50:14.991174137Z INFO SiHAS People Counter V2(CSM-300-ZB)  setPeopleCounter =     0
2026-05-18T23:50:14.991743137Z ERROR SiHAS People Counter V2(CSM-300-ZB)  CSM-300-ZB thread encountered error: [string "st/dispatcher.lua"]:270: Error encountered while processing event for <ZigbeeDevice: 82b1923d-4172-4d9f-a7c9-889ee902e2df [0x4DF7] (화장실 카운터)>:
    arg1: {args={value=0}, capability="afterguide46998.peopleCounterV2", command="setPeopleCounter", component="main", named_args=RecursiveTable: args, positional_args={0}}
"[string "st/zigbee/data_types/base_defs/FloatABC.lua"]:201: SinglePrecisionFloat mantissa must be non-negative"

Created an issue on GitHub.

Suggested fix:

diff --git a/st/zigbee/data_types/base_defs/FloatABC.lua b/st/zigbee/data_types/base_defs/FloatABC.lua
index 0000000..0000000 100644
--- a/st/zigbee/data_types/base_defs/FloatABC.lua
+++ b/st/zigbee/data_types/base_defs/FloatABC.lua
@@ -163,11 +163,13 @@ function FloatABC.new_mt(base, byte_length, mantissa_bit_length, exponent_bit_le
   end
   i_table.check_mantissa_is_valid = function(self, mantissa)
     if type(mantissa) ~= "number" then
       error(string.format("%s mantissa values must be numbers", self.NAME), 2)
+    elseif mantissa < 0 then
+      error(string.format("%s mantissa must be non-negative", self.NAME), 2)
     elseif mantissa > 1 then
       error(string.format("%s mantissa must be less than 1", self.NAME))
     end
   end
   i_table.check_exponent_is_valid = function(self, exponent)
@@ -199,6 +201,37 @@ function FloatABC.new_mt(base, byte_length, mantissa_bit_length, exponent_bit_le
     end
     rawset(self, k, v)
   end
+
+  i_table.from_value = function(self, value)
+    if type(value) ~= "number" then
+      error(string.format("%s values must be numbers", self.NAME), 2)
+    end
+
+    local sign_bit = value < 0 and 1 or 0
+
+    -- IEEE-style zero encoding:
+    -- exponent field all zeroes -> stored exponent = -exponent_modifier
+    -- mantissa field all zeroes
+    --
+    -- This must be handled before math.frexp(), because frexp(0) does not
+    -- return a normalized mantissa and naïve conversion can produce -1.
+    if value == 0 then
+      return self(sign_bit, -self.exponent_modifier, 0)
+    end
+
+    local abs_value = math.abs(value)
+    local frexp_mantissa, frexp_exponent = math.frexp(abs_value)
+
+    -- math.frexp returns:
+    --   abs_value = frexp_mantissa * 2^frexp_exponent
+    -- with frexp_mantissa in [0.5, 1).
+    --
+    -- FloatABC represents normal values as:
+    --   (1 + mantissa) * 2^exponent
+    --
+    -- Therefore:
+    --   exponent = frexp_exponent - 1
+    --   mantissa = (frexp_mantissa * 2) - 1
+    local exponent = frexp_exponent - 1
+    local mantissa = (frexp_mantissa * 2) - 1
+
+    return self(sign_bit, exponent, mantissa)
+  end
+
   mt.__call = function(orig, sign, exponent, mantissa)
+    -- Convenience constructor: allow SinglePrecisionFloat(0),
+    -- SinglePrecisionFloat(1.5), etc., while preserving the existing
+    -- component constructor SinglePrecisionFloat(sign, exponent, mantissa).
+    if exponent == nil and mantissa == nil then
+      return orig:from_value(sign)
+    end
+
     local o = {}
     setmetatable(o, mt)
     o.exponent = exponent

Hi @Speed_Park
The team is already aware of this error and is currently working on it.

Hi @Itati,

​My hub hasn’t received the 0.61.6 update yet, so I haven’t been able to test it myself. However, I’ve noticed that other users who already updated to v0.61.6 are reporting that the exact same issue still persists.

​Was the fix actually included in the 0.61.6 firmware? If the hub-side fix is already live, does this mean we need to request an Edge Driver update from the device manufacturer instead?

​Thanks!

It’s now been over four weeks since May 18th, when not only was the bug discovered, but a full investigation was carried out and a complete, ready-to-use suggested fix (including code diff) was submitted.

Gratitude looks a bit different when a community member puts in that level of effort and delivers a working solution, only for it to remain unaddressed for over a month - especially when the reply made it sound like the team had already been working on it independently.

As an aside, this seems to be part of a broader pattern, where community members again did the detailed diagnosis (including fingerprint analysis and confirming the CSA certification mismatch) and even provided a working test driver - yet the official response was again the generic “the team is currently working on it.”.

If SmartThings doesn’t actually want this level of community engagement and detailed contributions - including the many hours of free labor that come with maintaining high-quality drivers, debugging edge cases, and providing thoughtful feedback - it would be helpful if you could just say so clearly. We have limited time and energy, and if that kind of sustained, detailed input isn’t genuinely valued here, we can easily redirect our efforts and spend more time working on a certain other smart home platform that lives on community contributions and actively encourages, integrates, and builds upon them.

Hi, @Speed_Park

We discussed this with the engineering team, and they mentioned that a solution for this was included since 61.4.
So, please check the result once you get version 61.6. If it persists, we would need your help to collect new hub and driver logs. You can send the driver logs to build@smartthings.com (this helps us get context and the timestamp).
Also, provide your Hub’s EUI
Please don’t modify the implementation as you shared it in the issue you opened in GitHub to have this reference while checking the logs.

And, @Andreas_Roedl, we appreciate all the contributions people make to improve the tools. Sometimes communication is delayed, but that’s why we welcome everyone to come back and ask for updates and report if they see the issue persists after new releases.
The Hub team makes a great effort to improve the libraries based on users’ feedback, and contributions like this are considered incredibly valuable for isolating root causes.
When we say “the team is working on it,” it is not a generic brush-off; it means the issue is actively in our engineering pipeline, going through the different phases before the production release.

Thanks for checking with the team, @nayelyz.

Other users in the local community also reported that they were still experiencing the same error even after the hub firmware update. Fortunately, the issue has now been fully resolved on our end following a recent driver update released by SiHAS.

For reference, here are the updated driver details:

  • Driver Name: SiHAS People Counter V2(CSM-300-ZB)

  • Driver Version: 2026-06-22T06:12:11.616739716

Everything is working perfectly now. Thank you and the team for your hard work and support on this!

Hi, @Speed_Park
So, would this mean that the driver changed how this function was used, which helped with the issue?

Hi @nayelyz,

Yes — based on what I can see on my side, it looks like a driver-side change in how the value is handled, rather than 0 going through the original FloatABC.lua path.

Worth noting: my hub is already on 61.6 (000.061.00006), and it only started working after the SiHAS driver update — consistent with what others reported (still failing on 61.6 until the driver changed).

I captured live hub logs (edge:drivers:logcat, trace) on the current build while driving the counter through a full cycle including the transition to 0.

A non-zero count uses the normal normalized form:

received command:        {"value":23,"capability":"afterguide46998.peopleCounterV2","command":"setPeopleCounter"}

sending Zigbee message: WriteAttribute || attr_id: 0x0055, DataType: SinglePrecisionFloat, data: (1 + 0.437500) * 2^(4)

received Zigbee message: ReportAttribute || attr_id: 0x0055, DataType: SinglePrecisionFloat, PresentValue: (1 + 0.437500) * 2^(4)

emitting event: {"attribute_id":"peopleCounter","state":{"value":23}}

That (1 + 0.4375) * 2^4 is exactly the frexp_mantissa*2 - 1 / exp-1 encoding — i.e. the same math.frexp path from the bug report.

But zero now comes through as the all-zero encoding instead:

received command:        {"value":0,"capability":"afterguide46998.peopleCounterV2","command":"setPeopleCounter"}

sending Zigbee message: WriteAttribute || attr_id: 0x0055, DataType: SinglePrecisionFloat, data: 0

received Zigbee message: ReportAttribute || attr_id: 0x0055, DataType: SinglePrecisionFloat, PresentValue: 0

emitting event: {"attribute_id":"peopleCounter","state":{"value":0}}

This lines up with the original crash: math.frexp(0) returns a mantissa of 0, so frexp_mantissa*2 - 1 = -1 trips the “mantissa must be non-negative” check at FloatABC.lua:201. On the current build, 0 simply doesn’t take that path anymore — it’s encoded directly as the zero float, exactly the if value == 0 then return self(sign_bit, -self.exponent_modifier, 0) special-case from the proposed from_value() fix. So it reads as a driver-side workaround that avoids the crashing conversion, rather than the library handling 0 itself.

I haven’t seen SiHAS’s source diff, so this is from the hub logs/behavior rather than the code — but the wire encoding plus the 61.6 timing both point to a driver-side fix.

For reference:

  • Driver: SiHAS People Counter V2(CSM-300-ZB)
  • Driver version: 2026-06-22T06:12:11.616739716
  • Device: ShinaSystem / CSM-300Z (Zigbee, AnalogInput cluster, attr 0x0055)

Thanks again to you and the team!

Code pedant here …

elseif mantissa > 1 then
       error(string.format("%s mantissa must be less than 1", self.NAME))

IF statement allows the value to be 1 exactly, string requires it to be less than 1. Which one is it?

Good catch. The message is the correct intent — mantissa here is the fraction, so it lives in [0, 1) (you can see it in the logs as (1 + 0.4375) * 2^4, where 0.4375 is the stored mantissa). So the bound should be strict: the guard ought to be mantissa >= 1. As written, > 1 lets mantissa == 1 slip through, which would imply a significand of 2.0 — outside the normalized [1, 2) range. So: condition off-by-one, message right.