Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • World
  • Users
  • Groups
Skins
  • Light
  • Dark

Collapse
SHARUR
Community Wiki DLMS/COSEM Tools
Search Sign in

Why does signed 8-bit negative scaler (-3) cause 10^253 overflow and float32 rounding errors in smart metering billing?

Scheduled Pinned Locked Moved General Discussion
dlmscosemscaler-unitclass-3data-precision
10 Posts 6 Posters 48 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
  • S Offline
    S Offline
    sarah_jenkins
    wrote on last edited by
    #1

    [!WARNING]
    Technical Redaction & Safe Harbor Notice:
    All communication captures, system titles, and cryptographic parameters in this thread have been sanitized and neutralized. Real utility meter identifiers, production encryption keys (GUEK/GAK/MK), SIM IMSIs, and proprietary network envelopes have been substituted with standard IEC 62056 synthetic test vectors. Do not post unredacted production captures or live cryptographic keys.

    We are auditing smart meter billing integration between DLMS/COSEM meters and Head-End MDM systems. When reading instantaneous current (OBIS 1.0.31.7.0.255, L1 Current, Class 3) and accumulated active import energy (OBIS 1.0.1.8.0.255, Total Energy, Class 3), we discovered two severe data corruption anomalies across several client parsers:

    1. The $10^{253}$ Value Explosion: For Current L1 (raw value = 5230 mA, expected = 5.230 A), some parsing libraries reported 5.23e+256 A!
    2. Billing Drift on High Energy Meters: For industrial meters with cumulative energy > 167,772.16 kWh (raw value = 0x000100000001 Wh), converting the integer to standard float32 resulted in lost energy increments and billing discrepancies!

    Here is the raw Get-Response-Normal for Current L1 (1.0.31.7.0.255 Attribute 2 & 3):

    00 01 00 01 00 01 00 10 C4 01 C1 00 02 02 12 14 6E 02 02 0F FD 16 21
    

    Dissecting the structure:

    • 12 14 6E: Long-Unsigned 5230
    • 02 02: Structure of 2 elements (scaler_unit)
    • 0F FD: scaler (Integer8)
    • 16 21: unit (Enum 33 = Ampere [A])

    Why did the scaler byte 0xFD cause a $10^{253}$ explosion?

    1 Reply Last reply
    0
    • D Offline
      D Offline
      david_miller
      wrote on last edited by
      #2

      Sarah, this is the classic Unsigned vs Signed Byte Decoding Bug in A-XDR parsing!

      Per DLMS Blue Book Ed. 17 Section 4.3.3 (Register Interface Class 3) and IEC 62056-62 Table 12:
      Attribute 3 scaler_unit is defined as:

      ScalerUnit ::= Structure {
        scaler: Integer8 (Tag 0x0F), // SIGNED 8-bit integer!
        unit:   Enum     (Tag 0x16)
      }
      

      In two's complement 8-bit signed arithmetic:
      0xFD is $-3$ ($253 - 256 = -3$).
      $$\text{Physical Value} = \text{Raw Value} \times 10^{\text{scaler}} = 5230 \times 10^{-3} = 5.230\text{ A}$$

      If a parser incorrectly reads Tag 0x0F as an Unsigned Byte (uint8), it treats 0xFD as $+253$, calculating:
      $$\text{Corrupted Value} = 5230 \times 10^{+253} = 5.23 \times 10^{256}\text{ A}!$$

      1 Reply Last reply
      0
      • E Offline
        E Offline
        elena_rostova
        wrote on last edited by
        #3

        David's mathematical breakdown is spot on. Now let's address the second issue: Float32 Precision Loss on Cumulative Billing Registers.

        Smart meters measure cumulative energy in Watt-hours (Wh) or milli-Watt-hours as a 64-bit integer (Double-Long-Unsigned 0x06 or Long64-Unsigned 0x15).

        When a meter accumulates $20{,}000{,}000\text{ Wh}$ ($20{,}000\text{ kWh}$):

        • In IEEE 754 Single Precision (float32), the significand/mantissa is only 24 bits (approx 7.22 decimal digits of precision).
        • Maximum exact integer in float32: $2^{24} = 16{,}777{,}216$.

        Beyond 16,777,216 Wh, float32 cannot represent individual 1 Wh increments! Adding $1\text{ Wh}$ to $20{,}000{,}000\text{ Wh}$ in float32 results in exact zero change:
        $$20{,}000{,}000.0f + 1.0f = 20{,}000{,}000.0f$$
        Over time, thousands of kilowatt-hours are lost to rounding!

        1 Reply Last reply
        0
        • A Offline
          A Offline
          alex_turner
          wrote on last edited by
          #4

          Elena's point regarding IEEE 754 precision is critical for utility MDMS architectures.

          The Correct Datatype Standard:

          In all billing and data ingestion pipelines:

          1. Never use float (32-bit) for energy quantities;
          2. Use Double (float64: 53-bit mantissa $\implies 9 \times 10^{15}$ exact integers) or Fixed-Point Decimal (Python decimal.Decimal, Java BigDecimal);
          3. Perform all aggregation using raw integer arithmetic before applying the scaler.
          1 Reply Last reply
          0
          • M Offline
            M Offline
            marcus_vance
            wrote on last edited by
            #5

            Let's review the standard units table in DLMS Blue Book:

            Unit Enum Symbol Physical Quantity Standard Scaler
            30 Wh Active Energy 0 ($10^0$)
            31 kWh Active Energy 3 ($10^3$)
            32 varh Reactive Energy 0
            33 A Current -3 (mA $\to$ A) or -2
            35 V Voltage -1 (0.1V $\to$ V) or 0
            27 W Active Power 0 or -1

            Notice that Voltage and Current scalers are almost ALWAYS negative!

            1 Reply Last reply
            0
            • S Offline
              S Offline
              sarah_jenkins
              wrote on last edited by
              #6

              Here is how a compliant decoder should parse Tag 0x0F (Integer8):

              # Compliant Signed Int8 Unpacking:
              import struct
              
              def parse_scaler(scaler_byte):
                  # struct.unpack('b') reads signed 8-bit integer (-128 to +127)
                  return struct.unpack('b', bytes([scaler_byte]))[0]
              
              # Testing with 0xFD:
              scaler = parse_scaler(0xFD)
              print(f"Parsed Scaler: {scaler}") # Outputs: -3
              
              1 Reply Last reply
              0
              • C Offline
                C Offline
                carlos_mendez
                wrote on last edited by
                #7

                We audited our field data collection scripts and found 3 custom parsers reading scaler via ord(byte) without signed conversion. Fixing this eliminated the $10^{253}$ current spikes across our entire AMI dashboard!

                1 Reply Last reply
                0
                • E Offline
                  E Offline
                  elena_rostova
                  wrote on last edited by
                  #8

                  And for cumulative energy, we migrated our database schema from FLOAT to NUMERIC(18, 4) / BIGINT, preserving 100% of Wh billing increments.

                  1 Reply Last reply
                  0
                  • A Offline
                    A Offline
                    alex_turner
                    wrote on last edited by
                    #9

                    Here is the verified A-XDR payload for Active Energy Import (1.0.1.8.0.255😞

                    00 01 00 01 00 01 00 12 C4 01 C1 00 02 02 06 01 31 2D 00 02 02 0F 00 16 1E
                    
                    • 06 01 31 2D 00: Double-Long-Unsigned 20,000,000 Wh (20,000 kWh)
                    • 0F 00: Scaler 0 ($10^0$)
                    • 16 1E: Unit 30 [Wh]
                    1 Reply Last reply
                    0
                    • S Offline
                      S Offline
                      sarah_jenkins
                      wrote on last edited by
                      #10

                      To synthesize for the community, here is the robust, production-grade Python decoder:

                      from decimal import Decimal
                      import struct
                      
                      def decode_dlms_register(raw_val_int, scaler_byte, unit_enum):
                          # 1. Decode Scaler as Signed 8-bit Integer
                          scaler = struct.unpack('b', bytes([scaler_byte]))[0]
                          
                          # 2. Use Decimal Arithmetic to prevent Float32 precision loss
                          raw_dec = Decimal(str(raw_val_int))
                          scale_factor = Decimal(10) ** Decimal(scaler)
                          physical_value = raw_dec * scale_factor
                          
                          print(f"[+] Scaler: {scaler}, Scale Factor: 10^{scaler}")
                          print(f"[+] Exact Physical Value: {physical_value} (Unit Enum: {unit_enum})")
                          return physical_value
                      
                      # Test Case 1: L1 Current (5230 mA, Scaler 0xFD = -3)
                      assert decode_dlms_register(5230, 0xFD, 33) == Decimal("5.230")
                      
                      # Test Case 2: Cumulative Energy (20,000,000 Wh, Scaler 0x00 = 0)
                      assert decode_dlms_register(20000000, 0x00, 30) == Decimal("20000000")
                      

                      Summary Checklist for Register Parsing:

                      1. Signed Scaler: Always unpack Tag 0x0F as signed 8-bit integer (struct.unpack('b')).
                      2. Decimal Arithmetic: Use Decimal or float64 to prevent mantissa truncation on high cumulative energy registers (Blue Book Section 4.3.3).
                      3. Unit Mapping: Map unit enums to standard SI physical dimensions.
                      1 Reply Last reply
                      0

                      Hello! It looks like you're interested in this conversation, but you don't have an account yet.

                      Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.

                      With your input, this post could be even better 💗

                      Register Login
                      Reply
                      • Reply as topic
                      Log in to reply
                      • Oldest to Newest
                      • Newest to Oldest
                      • Most Votes


                      • Login

                      • Don't have an account? Register

                      • Login or register to search.
                      Powered by NodeBB Contributors
                      • First post
                        Last post
                      0
                      • Categories
                      • Recent
                      • Tags
                      • Popular
                      • World
                      • Users
                      • Groups