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.