Thursday, November 19, 2009

USB Scale Hacking

A while ago I purchased some Radio Shack USB scales on clearance for $5 each. I had always intended to get around to futzing with them but haven't until just last night. Linux made it way too easy.

Plugging it in, I see the device identifies itself as some quasi-proprietary HID device.

Bus 002 Device 008: ID 2233:6323 RadioShack Corporation USB Electronic Scale
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.10
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x2233 RadioShack Corporation
idProduct 0x6323 USB Electronic Scale
bcdDevice 1.00
iManufacturer 1
iProduct 2
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 34
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 4
bmAttributes 0x80
(Bus Powered)
MaxPower 100mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 0 No Subclass
bInterfaceProtocol 0 None
iInterface 0
** UNRECOGNIZED: 09 21 10 01 00 01 22 22 00
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0008 1x 8 bytes
bInterval 10

Graciously, Linux hooked it up as a raw hid device...

usb 2-1: new low speed USB device using ohci_hcd and address 8
usb 2-1: configuration #1 chosen from 1 choice
generic-usb 0003:2233:6323.0006: hiddev99,hidraw4: USB HID v1.10
Device [RadioShack Corporation USB Electronic Scale]
on usb-0000:00:0b.0-1/input0

I decided to have a peek at what it output:

caskey@arnold:~$ sudo cat /dev/hidraw4 |hexdump -C
00000000 ab 12 13 81 32 a0 e0 d5 ab 12 13 81 32 a0 e0 d5 |....2.......2...|
00000010 ab 12 13 81 32 a0 e0 d5 ab 12 13 81 32 a0 e0 d6 |....2.......2...|

Looks like 8 bytes repeating over and over again about every 250ms. The only thing that changes is the last two bytes. Just guessing, I wrote a little perl program to pull out those two bytes and interpret them as a signed short. I tried it both little and big endian and the numbers ramp up smoothly when I treat it as a big endian number.

Zero appears to hover around -7978, and if I subtract that out, I get a value that hangs around zero, then goes up when I put something on it. Unfortunately I don't have any calibrated weights so I randomly placed my leatherman and a post-it note to see how sensitive it was.

My leatherman registered at "383" units, while a post-it note was between 1 and 2 units. The data is noisy as the values bounce back and forth by +/-2 or so units. I worked a bit of smoothing into my perl program and tried a few more things.

A dead 7ah SLA battery came in at 6472/6473 units, and my coffee cup full of pens was 2103 units. I really wanted to know what these units were. Therefore I tried to think of what I had which would have a reliable weight. After a bit of googling, I discovered that a US nickel nominally weights 5 grams. Since it is late at night and my wife's asleep, I raided her coin jar for nickels.

Obviously any given nickel is likely to weigh +/- some amount, so I took 10 of them and placed them on the scale. They came out to exactly 128 units. *bing* the light goes on. I fished out another 10 and it came in at 258. That was some pretty strong evidence that the two bytes are 100-grams and 1/256th of 100 grams each. I took the number and modified my program to print out the "units" divided by 256 and adjusted to Kg.

My leatherman now came out as 0.149 Kg, and the twenty nickels coins came out to 0.1009 Kg. Darn close in my book. I'm sure this scale is no good for such fine measurements, but I'm happy.

After all that, I remembered that I had a 1Lb weight from an old exercise cuff lying in a shoebox. I put that on my scale and it read out:

W: (-6815/112.787110749634) '1163' -> 1161.2 0.4535 Kg or 0.99 Lbs
W: (-6817/118.757755212152) '1161' -> 1161.1 0.4535 Kg or 0.99 Lbs
W: (-6816/124.439867451545) '1162' -> 1161.1 0.4535 Kg or 0.99 Lbs
W: (-6815/129.847874078967) '1163' -> 1161.2 0.4535 Kg or 0.99 Lbs

And in the world of cheap USB scales, that's perfectly fine for me.

Oh, and here's the really dirty perl code I used:

caskey@arnold:~$ sudo cat /dev/hidraw4 | ./scalecat
caskey@arnold:~$ cat scalecat

my $ALP = 0.1;
my $CORR = -7978;
my $MOVINGAVG = 0.0;
my @weight = (0, 0, 0, 0, 0, );
my $wta = 0;
while(1) {
read STDIN, $foo, 8;
my($a,$b,$c,$d,$e,$f,$w) = unpack("C6s>", $foo);
my($wt) = $w - $CORR;
$MOVINGAVG = (0.95 * $MOVINGAVG) + (0.01 * $wt);
if(int($MOVINGAVG) == $wt && $CORR != $w) {
print "*** ZEROING ***\n";
$CORR = $w;
if(($wta-$wt)**2 < 9) {
$wta= (int(10*(($wt * ($ALP)) + ($wta *(1.0-$ALP)))))/10;
} else {
print "+++ SKIP +++\n";
$wta = $wt;
my $kg = (int(($wta/256.0)*1000))/10000;
my $lb = (int(($kg/0.45359237)*100))/100;
print "W: ($w/$MOVINGAVG) '$wt' -> $wta $kg Kg or $lb Lbs\n";

I love it when projects are quick and easy. This was nothing like trying to get the Griffin Powermate to work a few years ago. Linux does the heavy lifting and presents me with the data in a nice easy to play with format.


  1. This could not be more timely. I grabbed one of these scales for demonstrations at work last week. Thank you for decoding the output format, I doubt I would have figured that out on my own.

  2. I'm glad it was of use to you. I hope your demo goes well.