Decent Scale

Open BLE API documentation

Discussion Forum   Half Decent Scale extended API   contact us


We want to provide code samples in many programming languages that shows how to do things with the Decent Scale.
Please do email us and share your own scale-talking code, so that we can help others get started faster.

Quick start guide: weight and button notifications, tare and the timer

  1. Power on the Decent Scale by holding the O button down for 2 seconds.
  2. Do a BLE scan on your device, and then connect to "Decent Scale"
  3. Send a TARE or LED ON command to the scale to cause APP to display on the scale. The scale will not weight until some sort write command is sent to the scale, to let it know your app is there.
  4. Turn the LED ON: "030A0101000108" or LED OFF: "030A0000000009"
  5. Ask to receive weight notifications from FFF4 ("0000FFF4-0000-1000-8000-00805F9B34FB"). Weight notifications will now arrive at 10x per second, as a signed two byte (Short) integer, on bytes 3 and 4.
  6. Firmware v1.0 and v1.1 sends weight as a 7 byte message. Firmware v1.2 and newer sends weight with a timestamp, as a 10 byte message.
  7. Optional: Start the timer by sending "030B030000000B" and pause the timer with "030B0000000008" and reset the timer to zero with "030B020000000A"
  8. Tare the scale by sending "030F000000010D".
  9. Watch the Light Blue demo doing these commands.
  10. Known bug: in v1.0 firmware for the Decent Scale, a command (such as tare, or timer start) might get occasionally dropped. For safety, send a command to the scale twice, with a 50ms or longer delay. This will be addressed in the v1.1 firmware. A new BLE command to determine the current firmware version, will be added in v1.1, in case you want to put conditional logic in to not send commands twice.
  11. The Half Decent Scale disconnects if a heartbeat of "03 0a 03 ff ff 00 0a" is not received at least every 5 seconds. This is a very useful feature which disconnects zombie bluetooth connections, which happen often on Android 9 and earlier. Sending the heartbeat to older scales has no negative effect. Byte 6 of both the TARE and LED ON commands should be set to 01, otherwise the Half Decent Scale will assume your app is unaware of the heartbeat feature and not enforce it.
  12. Having trouble? Download LightBlue for ios, Android, Mac and follow the steps shows in the video above.
  13. Programmers guide to the Half Decent Scale.

Command reference:

Weight received onFFF4 (0000FFF4-0000-1000-8000-00805F9B34FB)

Firmware v1.0 and v1.1 sends weight as a 7 byte message:
  • 03CE 0000 0000 CD = 0.0 grams
  • 03CE 0065 0000 A8 = 10.1 grams
  • 03CE 0794 0000 5E = 194.0 grams
  • 03CE 1B93 0000 5E = 705.9 grams
  • 03CE 2BAC 0000 4A = 1118.0 grams

    Firmware v1.2 and newer sends weight with a timestamp as a 10 byte message:
  • 03CE 0000 010203 0000 CD = 0.0 grams - (1 minute, 2 seconds, 3 deciseconds)
  • 03CE 0065 010204 0000 A8 = 10.1 grams - (1 minute, 2 seconds, 4 deciseconds)
  • 03CE 0794 010205 0000 5E = 194.0 grams - (1 minute, 2 seconds, 5 deciseconds)
  • 03CE 1B93 010206 0000 5E = 705.9 grams - (1 minute, 2 seconds, 6 deciseconds)
  • 03CE 2BAC 010207 0000 4A = 1118.0 grams - (1 minute, 2 seconds, 7 deciseconds)
  • Tare (set weight to zero)030F000000010E
    LED on030A0101000108 (grams)
    LED off030A0000000009
    Timer start030B030000000B
    Timer stop030B0000000008
    Timer zero030B020000000A
    Power off
    (new in v1.2 firmware)
    030A020000000B

    BLE pairing and timeout

    1. On powering up, the scale will announce itself for BLE pairing. A blue LED will flash. The BLE device is named "Decent Scale".
    2. The scale will remain permanently available to BLE pair. There is no BLE timeout.
    3. If your app connects, and then disconnects, the scale will go back to BLE pair mode.
    4. If running on batteries, the scale will timeout after 2 minutes (10 minutes for v1.1 firmware) if it is not BLE paired, or if no weighing has occured.
    5. If running on USB power, the scale will never power off on its own.
    6. When your app connects, the word APP will appear on the scale display.
    7. A quick tour video of the Decent Scale is available.

    Scale design goals

    1. Weight information is intentionally unsmoothed, as we think smoothing can be done better on a computing device, in software, than it can be done in scale firmware.
    2. The Decent Scale has no concept of "stable weight". The current readings from the load cell are transferred immediately over BLE.
    3. If you wanted smoothed weight, you should implement this yourself on top of the raw data received by the scale.
    4. Weight changes are communicated immediately.
    5. There is no "threshold weight" which needs to be reached in order for the weight to have considered as having changed.
    6. This scale aims to be extremely fast to respond to weight changes, down to 0.1g.
    7. If there is motion on your weighing medium (such as a liquid) that you will likely see small weight variations due to that motion.
    8. If your use case means that you prefer to hide the weight variations on the scale's LED, you can choose to disable them. You can thus choose to only show weight inside your app. This will have the added benefit of greatly lengthening the battery life.
    9. Firmware v1.2 and newer includes a decisecond accurate timestamp with each weight measurement. This is (1) to help you calculate flow rate, (2) to know if you lost a weight measurement or (3) have received an out of order weight message.

    Buttons

    1. There are two buttons, labelled with a circle (button 0) and a square (button 1).
    2. When there is no BLE connection, these buttons control power, tare and the timer.
    3. Once there is a BLE connection, these buttons are completely under your app's control, and taps no longer have any effect on the scale.

    Limitations

    1. Maximum weight is 2000g.
    2. Very hot beverages might cause the scale to not read correctly
      1. This is because they heat up the load cell, changing its resistance under weight.
      2. Usually this manifests itself as the weight not going up beyond a certain number.
      3. You can solve this by using an insulating pad between the scale and your hot beverage, such as a piece of silicone or wood.
    3. The Decent Scale is calibrated with 1000g and 2000g weights, at the factory.
      1. However, the calibration may be affected by shipping.
      2. Refer to the User Manual or this calibration video for more info.
      3. Weight is sent at 10hz.
      4. The Battery status is returned in the weight datafeed in response to the LED on or off BLE command.
        1. See "Sending Commands to turn LEDs on or off".
        2. A 3% or lower battery will cause a LO message to appear on the scale when it is powered up.
      5. We do not recommend leaving heavy weights on the scale permanently, at this will slowly distort the load cell, and send it out of calibration.
      6. There is no limit on the number of times the scale can be recalibrated.
      7. There is no BLE command for setting Grams vs Oz on the scale display.
        1. This must be done manually, by pressing both O and [] buttons immediately after turning the scale on. See the User Manual for more information.
        2. You can discover whether the scale is displaying Grams or Oz by reading the response to the LED on or off BLE command. See "Sending Commands to turn LEDs on or off"
      8. If a second BLE command is sent to the scale before it has finished executing the first command, the scale will ignore the second command.
        1. For example: a tare command immediately followed by a timer start command.
        2. A 200ms delay between commands sent to the scale is recommended.
        3. Alternatively, you can delay sending the second command until you can acknowledge that the first command was executed.
        4. For example: after a tare (or LED on/off command), an answer response is sent back to your app, with byte 6 set to FE to indicate success.


      BLE Overview

      Decent scale packets come in on "0000FFF4-0000-1000-8000-00805F9B34FB"

      All BLE packets to/from the Decent Scale have the following 7 byte structure:

      BYTE 1BYTE 2BYTE 3BYTE 4BYTE 5BYTE 6BYTE 7
      Model
      03 = Decent
      Type
      0A=LED on/off
      0B=Timer on/off
      0F=Tare
      Command / data (1)Command / data (2)Command / data (3)Command / data (4)XOR validation
      6 byte XOR for validation
      More detail about each command and information type is given below.

      Here is a Tcl procedure which builds the 7 character binary package to send to the Decent Scale. It is built first as a text string, which is then converted to a 7 byte binary at the end of the procedure:

        proc decent_scale_make_command {cmdtype cmdddata {cmddata2 {}} } {
            if {$cmddata2 == ""} {
                msg "1 part decent scale command"
                set hex [subst {03${cmdtype}${cmdddata}000000[decent_scale_calc_xor "0x$cmdtype" "0x$cmdddata"]}]
            } else {
                msg "2 part decent scale command"
                set hex [subst {03${cmdtype}${cmdddata}${cmddata2}0000[decent_scale_calc_xor4 "0x$cmdtype" "0x$cmdddata" "0x$cmddata2"]}]
            }
            msg "hex is '$hex' for '$cmdtype' '$cmdddata' '$cmddata2'"
            return [binary decode hex $hex]
        }


      XOR Calculation

      To calculate the XOR, you should do a mathematical XOR of the first 6 bytes.

      Here is Tcl programming code that calculates the XOR for a simple command that takes only one parameter, such as TARE:

        proc decent_scale_calc_xor {cmdtype cmdddata} {
            set xor [format %02X [expr {0x03 ^ $cmdtype ^ $cmdddata ^ 0x00 ^ 0x00 ^ 0x00}]]
            msg "decent_scale_calc_xor for '$cmdtype' '$cmdddata' is '$xor'"
            return $xor
        }
        

      Here is Tcl programming code that calculates the XOR for a two-parameter command, such as enabling the LEDs:

        proc decent_scale_calc_xor4 {cmdtype cmdddata1 cmdddata2} {
            set xor [format %02X [expr {0x03 ^ $cmdtype ^ $cmdddata1 ^ $cmdddata2 ^ 0x00 ^ 0x00}]]
            msg "decent_scale_calc_xor4 for '$cmdtype' '$cmdddata1' '$cmdddata2' is '$xor'"
            return $xor
        }
        

      Receiving Weight

      Receiving weight data is a slightly special case, as the weight is sent as a two-byte signed Short integer of grams weight * 10:

      Firmware v1.0 and v1.1 sends weight as a 7 byte message.

      BYTE 1BYTE 2BYTE 3BYTE 4BYTE 5BYTE 6BYTE 7
      Model
      03 = Decent
      Type
      CE=weight stable
      CA=weight changing
      Data (1)
      Grams weight * 10

      High byte of a signed Short integer
      Data (2)
      Grams weight * 10

      Low byte of a signed Short integer
      Data (3)
      Change * 10

      High byte of a signed Short integer
      Data (4)
      Change * 10

      Low byte of a signed Short integer
      XOR validation
      6 byte XOR for validation
      Firmware v1.2 and newer sends weight with a timestamp, as a 10 byte message.

      BYTE 1BYTE 2BYTE 3BYTE 4BYTE 5BYTE 6BYTE 7BYTE 8BYTE 9BYTE 10
      Model
      03 = Decent
      Type
      CE=weight stable
      CA=weight changing
      Data (1)
      Grams weight * 10

      High byte of a signed Short integer
      Data (2)
      Grams weight * 10

      Low byte of a signed Short integer
      Data (3)
      Minutes on
      Data (4)
      Seconds on (0-59 in hexadecimal)
      Data (5)
      Deciseconds on (0-9)
      Data (6)
      for future use
      Data (7)
      for future use
      XOR validation
      6 byte XOR for validation
      Notes:
      1. Your app should look at the length of the message received, and support parsing both. Most apps only need to know the current weight: simply looking at bytes 3 and 4 is compatible with all Decent Scale firmware. Alternatively, you could check that the firmware version is equal to or greater than 03.
      2. current weight arrives as a two-byte short big-endian signed integer on data bytes 1 and 2.
      3. Weight change per second arrives as a two-byte unsigned big-endian short. However, this feature is currently buggy and not recommended for use. If you need weight change data, you should calculate it yourself. The weight change feature has been discontinued as of firmware v1.2.
      4. Weight data is sent at 10hz.
      5. The scale tries to determine when the weight appears to be stable (no more change) and set the second byte pair to CE. When weight is changing, the second byte part is CA. However, you can choose to ignore this and implement your own logic to decide when the weight is stable, as there is no data smoothing or stabilization performed on the reported weight data.
      6. Look at the top of this page for examples of 7 and 10 byte messages.


      Receiving Button Taps

      BYTE 1BYTE 2BYTE 3BYTE 4BYTE 5BYTE 6BYTE 7
      Model
      03 = Decent
      Type
      AA
      Data (1)
      01=button 0
      02=button 1
      Data (2)
      01=short tap
      02=long press
      Data (3)Data (4)XOR validation
      00
      Notes:
      1. When you have a BLE connection, the buttons taps have no functional effect on the scale, and can be programmed to have any function you like, guided by your programming.
      2. On Scale firmware v1.1 a O button press is not sent via BLE.If your app needs to tare the scale, your app should send the BLE tare command.

      Here are examples of button presses from the Light Blue app log:
        16:59:51.479 - Characteristic (FFF4) notified: 03aa0101 0000a9 (short press of O button)
        17:35:49.591 - Characteristic (FFF4) notified: 03aa0102 0000aa (long press of O button)
        17:38:16.702 - Characteristic (FFF4) notified: 03aa0201 0000aa  (short press of [] button)
        17:39:08.003 - Characteristic (FFF4) notified: 03aa0202 0000a9  (long press of [] button)
        
        



      Sending the Tare Command (reset weight to zero)

      BYTE 1BYTE 2BYTE 3BYTE 4BYTE 5BYTE 6BYTE 7
      Model
      03 = Decent
      Type
      0F
      Data (1)
      Incremented integer
      (Can always be 0)
      Data (2)
      00
      Data (3)
      00
      Data (4)
      00=disable heartbeat requirement
      01=maintain heartbeat
      XOR validation
      calculated
      Notes:
      1. The "Incremented integer" is optional, and can always be zero.
      2. The byte sequence 030F000000000C will always result in a successful tare.
      3. Make sure you correctly calculate the XOR for BYTE 7 or the TARE command will be ignored.
      4. The Half Decent Scale disconnects if a heartbeat of "03 0a 03 ff ff 00 0a" is not received at least every 5 seconds. If you want to disable this requirement, byte 6 should be 00. If you want to keep this requirement, byte 6 should be 01. Setting byte 6 to 01 has no negative effect on models that do not support the heartbeat feature.
      And here is a code fragment showing you how to build a 7 byte tare command:

        proc tare_counter_incr {} {
        
            if {[info exists ::decent_scale_tare_counter] != 1} {
                set ::decent_scale_tare_counter 0
            } elseif {$::decent_scale_tare_counter >= 255} {
                set ::decent_scale_tare_counter 0
            } else {
                incr ::decent_scale_tare_counter
            }
        
            # alternatively: the tare counter can in fact be any not-recently-used integer, such as this random digit-picker
            # set ::decent_scale_tare_counter [expr {int(rand() * 255)}]
        
        }
        
        proc decent_scale_tare_cmd {} {
            tare_counter_incr
            set cmd [decent_scale_make_command "0F" [format %02X $::decent_scale_tare_counter]]
            return $cmd
        }
        


      The Decent Scale will send an answer back after a Tare command. That answer has this structure:

      BYTE 1BYTE 2BYTE 3BYTE 4BYTE 5BYTE 6BYTE 7
      Model
      03 = Decent
      Type
      0F
      Data (1)
      Tare counter
      Data (2)
      00
      Data (3)
      00
      Data (4)
      FE
      XOR validation


      Sending Commands to turn LEDs on or off

      BYTE 1BYTE 2BYTE 3BYTE 4BYTE 5BYTE 6BYTE 7
      Model
      03 = Decent
      Type
      0A
      Data (1)
      Weight LED on/off
      00=off
      01=on
      02=power off
      Data (2)
      Timer LED on/off
      00=off
      01=on
      Data (3)
      00=grams
      01=ounces
      Data (4)
      00
      XOR validation
      calculated
      Notes:
      1. Battery life is extended by keeping LEDs off.
      2. The scale will continue to function, even with both LEDs off.
      3. The Power Off command was added in firmware v1.2.
      4. The single LED that shows "weight is changing" will light up, even if both LEDs have been turned off.
        1. As of v1.1 firmware, the LED above the square button no longer lights up during weight change.
        2. Instead, that LED lights up when either of the buttons is pressed.
        3. This is to provide visual feedback to the user, that the button press was sensed.
        Example: all LED On Command, using grams:
          03 0A 01 01 00 00 09
          
        Example: all LED On Command, using ounces: [requires v1.1 firmware]
          03 0A 01 01 01 00 08
          
        Example: all LED Off Command:
          03 0A 00 00 00 00 09
          
        Example: Power Off Command:
          03 0A 02 00 00 00 0B
          
        The Decent Scale will send an answer back after an LED on/off command (firmware v1.1), in the weight data feed, with grams vs oz information, battery power level and firmware version. It has this structure:

        BYTE 1BYTE 2BYTE 3BYTE 4BYTE 5BYTE 6BYTE 7
        Model
        03 = Decent
        Type
        0A
        Data (1)
        00
        Data (2)
        Weight units on LED
        00=grams
        01=oz
        Data (3)
        Battery level
        Between 3% (low) 100% (full).
        FF (255) = USB powered
        Data (4)
        firmware version
        FE=v1.0
        02=v1.1
        03=v1.2
        XOR validation
        The battery level is always reported at 100%, due to a limitation in the scale's PCB.
        Here is an example of reading battery level from the Light Blue app log:
          16:02:36.005 - Characteristic (36F5) wrote new value: <030a0101 000009> (we used the app to send this value, turning on the LED)
          16:02:36.061 - Characteristic (36F5) read: (null)
          16:02:36.135 - Characteristic (FFF4) notified: 030a0000 64026f (64 hex = 100%, running on batteries)
          
          15:59:50.421 - Characteristic (36F5) wrote new value: <030a0101 000009> (we used the app to send this value, turning on the LED)
          15:59:50.485 - Characteristic (36F5) read: (null)
          15:59:50.522 - Characteristic (FFF4) notified: 030a0000ff0ac7  (FF hex = 255%, running on USB power)
          


        Sending Commands to control the timer

        BYTE 1BYTE 2BYTE 3BYTE 4BYTE 5BYTE 6BYTE 7
        Model
        03 = Decent
        Type
        0B
        Data (1)
        Timer start/stop/reset
        00=stop
        02=reset to zero
        03=start
        Data (2)
        00
        Data (3)
        00
        Data (4)
        00
        XOR validation
        calculated
        Example: Timer Start Command
          03 0B 03 00 00 00 0B
          
        Example: Timer Stop Command
          03 0B 00 00 00 00 08
          
        Example: Timer Reset Command
          03 0B 02 00 00 00 0A
          

        Large sample code for receiving various data from the Decent Scale

        Larger bluetooth.tcl example on Github

        The follow code fragment (in the Tcl language, but hopefully you can understand it) can:
        1. receive weight information
        2. receive a button press
        3. receive the current timer value
        set ::de1(cuuid_decentscale_read) "0000FFF4-0000-1000-8000-00805F9B34FB"
        set ::de1(cuuid_decentscale_write) "000036F5-0000-1000-8000-00805F9B34FB"
        set ::de1(cuuid_decentscale_writeback) "83CDC3D4-3BA2-13FC-CC5E-106C351A9352" 
        
        if {$cuuid eq $::de1(cuuid_decentscale_read)} {
            # decent scale
            parse_decent_scale_recv $value weightarray
        
            if {[ifexists weightarray(command)] == [expr 0x0F] && [ifexists weightarray(data6)] == [expr 0xFE]} {
                # tare cmd success is a msg back to us with the tare in 'command', and a byte6 of 0xFE
                msg "- decent scale: tare confirmed"
        
                return
            } elseif {[ifexists weightarray(command)] == 0xAA} {
                msg "Decentscale BUTTON $weightarray(data3) pressed"
                if {[ifexists $weightarray(data3)] == 1} {
                    # button 1 "O" pressed
                    decentscale_tare
                } elseif {[ifexists $weightarray(data3)] == 2} {
                    # button 2 "[]" pressed
                }
            } elseif {[ifexists weightarray(command)] != ""} {
                msg "scale command received: [array get weightarray]"
        
            }
        
            if {[info exists weightarray(weight)] == 1} {
                set sensorweight [expr {$weightarray(weight) / 10.0}]
                #msg "decent scale: ${sensorweight}g [array get weightarray] '[convert_string_to_hex $value]'"
                #msg "decentscale recv read: '[convert_string_to_hex $value]'"
                ::device::scale::process_weight_update $sensorweight $event_time
            } else {
                msg "decent scale recv: [array get weightarray]"
            }
        }
        
        proc parse_decent_scale_recv {packed destarrname} {
            upvar $destarrname recv
            unset -nocomplain recv
        
               ::fields::unpack $packed [decent_scale_generic_read_spec] recv bigeendian
        
               if {$recv(command) == 0xCE || $recv(command) == 0xCA} {
                   unset -nocomplain recv
                   ::fields::unpack $packed [decent_scale_weight_read_spec2] recv bigeendian
               } elseif {$recv(command) == 0xAA} {
                   msg "Decentscale BUTTON pressed: [array get recv]"
               } elseif {$recv(command) == 0x0C} {
                   unset -nocomplain recv
                   ::fields::unpack $packed [decent_scale_timing_read_spec] recv bigeendian
                   msg "Decentscale time received: [array get recv]"
               }
        }
        
        proc decent_scale_generic_read_spec {} {
            set spec {
                model {char {} {} {unsigned} {}}
                command {char {} {} {unsigned} {}}
                data3 {char {} {} {unsigned} {}}
                data4 {char {} {} {unsigned} {}}
                data5 {char {} {} {unsigned} {}}
                data6 {char {} {} {unsigned} {}}
                xor {char {} {} {unsigned} {}}
            }
            return $spec
        }
        
        proc decent_scale_weight_read_spec2 {} {
            set spec {
                model {char {} {} {unsigned} {}}
                wtype {char {} {} {unsigned} {}}
                weight {Short {} {} {signed} {}}
                rate {Short {} {} {unsigned} {}}
                xor {char {} {} {unsigned} {}}
            }
            return $spec
        }