How difficult to support N USB scales?

stevenkan

Ars Legatus Legionis
16,060
I'm running Eric Jiang's usbscale application on my Raspberry Pi to log the weight of my beehive every 10 minutes from a 70 lb. Stamps.com scale (OEMed by Onyx).

It works pretty well, but now I'm thinking I want to run 2 scales in parallel, both to accommodate the higher weight of the colony, and also to keep the contraption balanced, because my new time-lapse rig is twice the size of last year's. I want to read the weight from 2 scales and add them up.

The problem is that usbscale doesn't currently support more than 1 connected scale, and the original author isn't actively maintaining it. From my read of the code (and I'm not a programmer), it looks at the USB device tree, compares each one to the list of supported Device IDs, and returns a reading from the first match that it finds.

The brute force method would be to have 2 Raspberry Pis, and have one read the weight from the other, but that's inelegant.

How much work would it take to restructure usbscale so that it builds an array of supported scales and then takes some sort of optional argument to either return N readings or the Nth reading or ????

As I said, I'm not a programmer, but I'm curious as to how much work this looks like.
 

andrewme

Senator
7,080
Subscriptor
It shouldn't be too hard for someone who knows C, especially if they have a pair of the scales available to quickly test. Could be as quick as an hour or two. Someone who knows C but doesn't have the hardware could likely get it done too, but if they don't get it exactly right the first time, there might be a couple rounds of back-and-forth with someone who does have the hardware to get it right.

Since you care about the sum of the two scales, it should be relatively simple, because you don't have to care which scale is which over time. It'd be another matter if you were trying to weigh two different things and you needed to know for sure which reading came from scale A and which from scale B - they may or may not have serial numbers that can be read over USB. USB topology could be used perhaps, but might change across enumerations/boots, or if a device is moved from one port to another.
 

Drizzt321

Ars Legatus Legionis
30,925
Subscriptor++
Hm, maybe change the find_scale() to take in another argument to give the Nth scale in the list. To get a full list of devices to output to STDOUT, run a loop until it returns NULL, and that's all of your scale devices.

Then as your input for reading, give a single or comma separate list of device numbers to read.

Alternatively do more work and get the serial number out on the CLI and use that to determine which one(s) to read.

(I'm not C or linux programmer expert, but I think it looks sorta straightforward.
 

Fruity Pebbles

Ars Praetorian
580
Subscriptor
I would change find_scale() to find_scales() and have it return an array of device descriptor pointers instead of just a single pointer. Then iterate through that array and print the readings from each scale.

Although for general quality of life I'd probably first change to compiling it as C++ so I could use vector and friends, or perhaps even be tempted to rewrite it in Python.
 
  • Like
Reactions: continuum

Drizzt321

Ars Legatus Legionis
30,925
Subscriptor++
I would change find_scale() to find_scales() and have it return an array of device descriptor pointers instead of just a single pointer. Then iterate through that array and print the readings from each scale.

Although for general quality of life I'd probably first change to compiling it as C++ so I could use vector and friends, or perhaps even be tempted to rewrite it in Python.

LOL.

I'll agree re-writing in Python probably is actually going to end up being an easier thing. I'm sure there are quite a few reasonably friendly USB on Linux libraries and/or information out there for Python.
 

Fruity Pebbles

Ars Praetorian
580
Subscriptor
I would change find_scale() to find_scales() and have it return an array of device descriptor pointers instead of just a single pointer. Then iterate through that array and print the readings from each scale.

Although for general quality of life I'd probably first change to compiling it as C++ so I could use vector and friends, or perhaps even be tempted to rewrite it in Python.

LOL.

I'll agree re-writing in Python probably is actually going to end up being an easier thing. I'm sure there are quite a few reasonably friendly USB on Linux libraries and/or information out there for Python.

There's at least one Python binding for libusb (which is what the C code uses). For example: https://pypi.org/project/libusb/ Hopefully that would make a Python rewrite straightforward.
 

Jonathon

Ars Legatus Legionis
16,979
Subscriptor
I would change find_scale() to find_scales() and have it return an array of device descriptor pointers instead of just a single pointer. Then iterate through that array and print the readings from each scale.

Although for general quality of life I'd probably first change to compiling it as C++ so I could use vector and friends, or perhaps even be tempted to rewrite it in Python.

LOL.

I'll agree re-writing in Python probably is actually going to end up being an easier thing. I'm sure there are quite a few reasonably friendly USB on Linux libraries and/or information out there for Python.

There's at least one Python binding for libusb (which is what the C code uses). For example: https://pypi.org/project/libusb/ Hopefully that would make a Python rewrite straightforward.
I've used pyusb in the past; it's libusb-based but provides a more Pythonic API (Python libusb is a pretty barebones wrapper around the libusb C API).

On the other hand, mapping more directly to the C API might be a good thing for directly porting code that's already using that C API...
 

stevenkan

Ars Legatus Legionis
16,060
Hmmm. I can't even get started. I'm trying to compile this on my Mac (10.14.6), and I'm getting:

Code:
make
cc -Os -Wall usbscale.c -lm -lusb-1.0 -o usbscale
usbscale.c:311:30: warning: implicit declaration of function 'le16toh' is invalid in C99 [-Wimplicit-function-declaration]
    double weight = (double) le16toh(dat[5] << 8 | dat[4]);
                             ^
1 warning generated.
Undefined symbols for architecture x86_64:
  "_le16toh", referenced from:
      _main in usbscale-9c6ed0.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [usbscale] Error 1
 

Jonathon

Ars Legatus Legionis
16,979
Subscriptor
Hmmm. I can't even get started. I'm trying to compile this on my Mac (10.14.6), and I'm getting:

Code:
make
cc -Os -Wall usbscale.c -lm -lusb-1.0 -o usbscale
usbscale.c:311:30: warning: implicit declaration of function 'le16toh' is invalid in C99 [-Wimplicit-function-declaration]
    double weight = (double) le16toh(dat[5] << 8 | dat[4]);
                             ^
1 warning generated.
Undefined symbols for architecture x86_64:
  "_le16toh", referenced from:
      _main in usbscale-9c6ed0.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [usbscale] Error 1
That's a glibc-ism; it won't compile on Mac.

Compiling on Linux will work, or just get rid of the function call (little endian to host byte order is a no-op on Intel and Apple Silicon). So you'd have double weight = (double) (dat[5] << 8 | dat[4]); instead.
 

stevenkan

Ars Legatus Legionis
16,060
Hmmm. I can't even get started. I'm trying to compile this on my Mac (10.14.6), and I'm getting:

Code:
make
cc -Os -Wall usbscale.c -lm -lusb-1.0 -o usbscale
usbscale.c:311:30: warning: implicit declaration of function 'le16toh' is invalid in C99 [-Wimplicit-function-declaration]
    double weight = (double) le16toh(dat[5] << 8 | dat[4]);
                             ^
1 warning generated.
Undefined symbols for architecture x86_64:
  "_le16toh", referenced from:
      _main in usbscale-9c6ed0.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [usbscale] Error 1
That's a glibc-ism; it won't compile on Mac.

Compiling on Linux will work, or just get rid of the function call (little endian to host byte order is a no-op on Intel and Apple Silicon). So you'd have double weight = (double) (dat[5] << 8 | dat[4]); instead.

Thanks! It now compiles and runs, but I'm at my office, away from the scales right now, so I won't be able to check until later.
 

sryan2k1

Ars Legatus Legionis
45,776
Subscriptor++
Since I am a hack, here's how I'd do it with minimal effort.

https://github.com/erjiang/usbscale/blo ... usbscale.c


At or around line 388 add:
Code:
int foundScale = 0;


Then on line 437, remove "return dev;" and change it to this:

Code:
if (foundScale == 0)
{
    foundScale++;
}
else
{
    return dev;
}


This will skip the first scale it finds, and always return the second. This will blow up if there are not two scales. Run both binaries, one for "scale one" and one for "scale two".


You could make it a lot cleaner or take command line arguments but since I have no scales to test with, this should work.

Edit: Since I'm an extra hack, your cronjob could grep the output of lsusb and count how many scales it finds to determine if it needs to run just the "scale 1" binary, or both.
 

stevenkan

Ars Legatus Legionis
16,060
It’s worth a try, but I wouldn’t count on libusb always returning device handles in the same order across runs. Maybe it does (or maybe it will as long as the bus doesn’t require any enumerations between getting the first and getting the second), but maybe not.

For my particular application the order of enumeration doesn't matter. I just need to read both supported scales, in any order, and sum the readings.

And it works!!!

Code:
pi@PiLapse:~/share/usbscale-master $ lsusb
Bus 001 Device 007: ID 1446:6a73 X.J.GROUP Stamps.com Model 510 5LB Scale
Bus 001 Device 008: ID 413d:2107  
Bus 001 Device 009: ID 1446:6a73 X.J.GROUP Stamps.com Model 510 5LB Scale
Bus 001 Device 003: ID 0424:ec00 Microchip Technology, Inc. (formerly SMSC) SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Microchip Technology, Inc. (formerly SMSC) SMC9514 Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
pi@PiLapse:~/share/usbscale-master $ usbscale
0 oz
pi@PiLapse:~/share/usbscale-master $ usbscale
0 oz
pi@PiLapse:~/share/usbscale-master $ usbscale
487.1 oz
pi@PiLapse:~/share/usbscale-master $ ./usbscale2
1.6 oz
pi@PiLapse:~/share/usbscale-master $

Theres's some odd behavior where sometimes the scale reads "0" for no apparent reason, so I need to put that into some sort of error-check loop to discard and retry those readings, but it works!

The original usbscale correctly reads the scale that the hive box is currently sitting on, and the modified usbscale2 correctly reads the new scale that I just bought, that has just an empty cat food can on it.

Thank you, sryan2k1, for this beautiful hack! I would like to buy you a beer.
 

stevenkan

Ars Legatus Legionis
16,060
And here's the latest reading on the original scale. It doesn't look like it matches up with the reading above (487.1 oz) because I have a correction column in there to net out non-biomass objects that I've added to the pile of stuff, such as additional bee boxes or the 2nd scale, which is currently sitting on top of the stack:

WeightLog042022.png


I'll rearrange everything this weekend, if I can find the time.
 

stevenkan

Ars Legatus Legionis
16,060
Is the relatively consistent pattern of high/low due to their sleep/inactivity vs gathering (in day light)?

They gather nectar during daylight hours, and then they both consume and dehydrate it at night. I believe most of the night-time weight loss is due to dehydration, as the amount of loss appears to be roughly correlated with the amount of daytime gain, which wouldn't be the case if it were consumption.

Once the nectar is dehydrated below about ~18% water (and inverted due to an enzyme added by the bees), then it's honey that will last nearly forever without fermenting or spoiling.

Here's a zoomed-in view from last year/season, with the night-time loss mislabeled as consumption. You can actually see the dip as the foragers leave at daybreak!

ColonyWeight.png
 

andrewme

Senator
7,080
Subscriptor
It’s worth a try, but I wouldn’t count on libusb always returning device handles in the same order across runs. Maybe it does (or maybe it will as long as the bus doesn’t require any enumerations between getting the first and getting the second), but maybe not.

For my particular application the order of enumeration doesn't matter. I just need to read both supported scales, in any order, and sum the readings.

You don't have to care about the order of your pair of readings since you're adding them, but the technique absolutely depends on libusb (and the kernel) enumerating devices in a consistent order to different callers over time.

Imagine this, which is entirely possible:

Code:
./usbscale
# asks libusb to enumerate devices
# libusb produces handle to scale #1
# prints scale #1 weight
./usbscale2
# asks libusb to enumerate devices
# libusb produces handle to scale #2
# code skips, asks libusb for next device
# libusb produces handle to scale #1
# prints scale #1 weight

It's probably not the end of the world if you accidentally read the same scale twice and calculate a bogus total weight, but it could definitely happen.

It might seem to be totally fine until some other device on the USB bus glitches between readings and causes a re-enumeration of the bus, leading to getting the devices in the opposite order.

Anticipating and handling edge cases is an important part of programming.
 

stevenkan

Ars Legatus Legionis
16,060
Ah, before I can deploy this, I need to re-read the scale if it returns "0 oz", which one of the scales returns quite frequently, for reasons unknown.

What's the proper syntax for a comparison? I know bash doesn't have floats, but even if I try to compare the original strings, e.g.:

Code:
#!/bin/bash

TheWeight1="0 oz"
ZeroWeight="0 oz"

while true
do
        TheWeight1=`sudo usbscale`
        echo $TheWeight1
        if (($TheWeight1 != $ZeroWeight))
        then
                TheWeight1=$(echo "$TheWeight1" | sed 's/oz//')
                break
        else
                echo Reading 1 Failed
        fi
done

I get:

Code:
490.3 oz
./GetNonZeroWeight.sh: line 11: ((: 490.3 oz != 0 oz: syntax error: invalid arithmetic operator (error token is ".3 oz != 0 oz")
Reading 1 Failed

I've been googling for an hour, and I've tried every syntax I can think of, but obviously not the correct one! Why does it compare ".3 oz" instead of "490.3 oz"?
 

Jonathon

Ars Legatus Legionis
16,979
Subscriptor
Ah, before I can deploy this, I need to re-read the scale if it returns "0 oz", which one of the scales returns quite frequently, for reasons unknown.

What's the proper syntax for a comparison? I know bash doesn't have floats, but even if I try to compare the original strings, e.g.:

Code:
#!/bin/bash

TheWeight1="0 oz"
ZeroWeight="0 oz"

while true
do
        TheWeight1=`sudo usbscale`
        echo $TheWeight1
        if (($TheWeight1 != $ZeroWeight))
        then
                TheWeight1=$(echo "$TheWeight1" | sed 's/oz//')
                break
        else
                echo Reading 1 Failed
        fi
done

I get:

Code:
490.3 oz
./GetNonZeroWeight.sh: line 11: ((: 490.3 oz != 0 oz: syntax error: invalid arithmetic operator (error token is ".3 oz != 0 oz")
Reading 1 Failed

I've been googling for an hour, and I've tried every syntax I can think of, but obviously not the correct one! Why does it compare ".3 oz" instead of "490.3 oz"?
(( always does arithmetic operations, on integers. "490.3 oz" is neither a number nor an integer, so its parser is getting confused.

If you're going to do a string comparison here, you want something more like this:

Code:
#!/bin/bash

zero_weight="0 oz"

weight_to_test_1="1.1 oz"
weight_to_test_2="0 oz"

if [[ "$weight_to_test_1" != "$zero_weight" ]]; then
  echo "Weight 1 not 0"
fi

if [[ "$weight_to_test_2" != "$zero_weight" ]]; then
  echo "Weight 2 not 0"
fi

If you want to strip off the "oz" and do actual floating point comparisons on those numbers, you don't want to do this in bash-- move to a real programming language like Python. It's probably possible, but it'll be terrible.
 

stevenkan

Ars Legatus Legionis
16,060
In case anyone's wondering, here's the contraption (click image for interactive panorama):



and here's a closer view, where you can see where the whole contraption was balanced on that one scale (between the two sheets of particle board, with the LCD display):



Having 2 scales between the boards made the whole stack a whole lot more stable, which is helpful both for the beekeeping part and the data collection part.

Thanks!

p.s. here I am, attempting to shim that 2nd scale between the two boards, all while 50,000 bees attempt to murder me.
 

stevenkan

Ars Legatus Legionis
16,060
Code:
if (foundScale == 0)
{
    foundScale++;
}
else
{
    return dev;
}


This will skip the first scale it finds, and always return the second.

Believe it or not, now I want to add a 3rd scale, because the weight of the stack is getting close to saturating both scales, and it's still pretty unstable. I just bought another scale off of eBay for $19.

Can just change
Code:
 if (foundScale == 0)
to
Code:
 if (foundScale < 2)
and compile usbscale3?
 

sryan2k1

Ars Legatus Legionis
45,776
Subscriptor++
If you look further up the thread the "foundScale" logic was added to simply skip the first scale it finds and goes to the second. The rest of the program's logic/error handling stays the same, it just opens a different USB device handle.

If there are not 2 (or 3) scales the program exits gracefully stating that no supported USB device was found.
 

stevenkan

Ars Legatus Legionis
16,060
Interesting. Scale #1 appears to have gone offline a few day ago. I hadn't checked my logs until recently, and presently I'm getting only 1 reading instead of 2.

When I discovered this, lsusb was returning:

Code:
lsusb
Bus 001 Device 007: ID 1446:6a73 X.J.GROUP Stamps.com Model 510 5LB Scale
Bus 001 Device 008: ID 413d:2107  
Bus 001 Device 009: ID 1446:6a73 X.J.GROUP Stamps.com Model 510 5LB Scale
Bus 001 Device 003: ID 0424:ec00 Microchip Technology, Inc. (formerly SMSC) SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Microchip Technology, Inc. (formerly SMSC) SMC9514 Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

but then I rebooted the pi (but the scales remained powered-on because of my poor-man's UPS), and now it returns:

Code:
lsusb
Bus 001 Device 005: ID 413d:2107  
Bus 001 Device 004: ID 1446:6a73 X.J.GROUP Stamps.com Model 510 5LB Scale
Bus 001 Device 003: ID 0424:ec00 Microchip Technology, Inc. (formerly SMSC) SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Microchip Technology, Inc. (formerly SMSC) SMC9514 Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

I'm suiting up to visit the bees on Saturday, so I'll be able to see what's going on with the scales.

usbscale returns:
Code:
usbscale
830.9 oz

whilst usbscale2 returns:
Code:
usbscale2
No USB scale found on this computer.
 

stevenkan

Ars Legatus Legionis
16,060
Some updates:
  1. Ants killed that hive :cry:, but now I have freedom to disassemble the whole rig and make changes.
  2. erjiang made an experimental branch of usbscale that reads multiple scales. I haven't tried this yet.
    1. int i = 0;
      while ((dev = devs[i++]) != NULL) {
      if (!is_scale(dev)) {
      continue;
      }
      found_scale = true;
      . . .
  3. erjiang also defined an alias for le16toh on macOS:
    1. #if defined(APPLE)
      #include <libkern/OSByteOrder.h>
      #define le16toh(x) OSSwapLittleToHostInt16(x)
      #endif
  4. but not in the same branch :LOL:
 
Last edited:

stevenkan

Ars Legatus Legionis
16,060
Some updates:
  1. Ants killed that hive :cry:, but now I have freedom to disassemble the whole rig and make changes.
  2. erjiang made an experimental branch of usbscale that reads multiple scales. I haven't tried this yet.
    1. int i = 0;
      while ((dev = devs[i++]) != NULL) {
      if (!is_scale(dev)) {
      continue;
      }
      found_scale = true;
      . . .
  3. erjiang also defined an alias for le16toh on macOS:
    1. #if defined(APPLE)
      #include <libkern/OSByteOrder.h>
      #define le16toh(x) OSSwapLittleToHostInt16(x)
      #endif
  4. but not in the same branch :LOL:
Ugh. USB Multi-Scale fails with:

Code:
usb-multi-scale: ../../libusb/os/threads_posix.h:46: usbi_mutex_lock: Assertion `pthread_mutex_lock(mutex) == 0' failed.

I definitely have multiple, recognized scales attached:

Code:
 lsusb
Bus 001 Device 004: ID 1446:6a73 X.J.GROUP Stamps.com Model 510 5LB Scale
Bus 001 Device 005: ID 1446:6a73 X.J.GROUP Stamps.com Model 510 5LB Scale
Bus 001 Device 006: ID 1446:6a73 X.J.GROUP Stamps.com Model 510 5LB Scale
Bus 001 Device 009: ID 413d:2107 
Bus 001 Device 003: ID 0424:ec00 Microchip Technology, Inc. (formerly SMSC) SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Microchip Technology, Inc. (formerly SMSC) SMC9514 Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

and the ugly/brilliant hack of usbscale1, usbscale2 . . usbscaleN works as expected 😂:

Code:
pi@PiLapse:/usr/local/bin $ usbscale1
111 oz
pi@PiLapse:/usr/local/bin $ usbscale2
113.9 oz
pi@PiLapse:/usr/local/bin $ usbscale3
228 oz
pi@PiLapse:/usr/local/bin $ usbscale4
No USB scale found on this computer.
 

stevenkan

Ars Legatus Legionis
16,060
1 item$8.95
Shipping$9.00
Tax$0.85
Order total$18.80

So definitely a bargain. Scale #3 was $28.16, all-in, and Scale #2 was $41.08.

I have a vague recollection that the first one was $50, direct from stamps.com as an inducement to open an account, but I might be mistaken about that. It's currently $99 from stamps.com.

It hasn't arrived yet, so I don't know if it works.
 

stevenkan

Ars Legatus Legionis
16,060
It hasn't arrived yet, so I don't know if it works.
I now have 4 USB scales under my instrumented hive, connected to my Pi. I think I may be slightly insane:

Code:
pi@PiLapse:~ $ usbscale1
136.4 oz
pi@PiLapse:~ $ usbscale2
76.7 oz
pi@PiLapse:~ $ usbscale3
172.6 oz
pi@PiLapse:~ $ usbscale4
0 oz
pi@PiLapse:~ $ usbscale4
111.6 oz

usbscale still has a condition wherein it sometimes reads a scale as 0, but I think I've handled it one level up.
 

stevenkan

Ars Legatus Legionis
16,060
Ugh. USB Multi-Scale fails with:

Code:
usb-multi-scale: ../../libusb/os/threads_posix.h:46: usbi_mutex_lock: Assertion `pthread_mutex_lock(mutex) == 0' failed.

I definitely have multiple, recognized scales attached:

Code:
 lsusb
Bus 001 Device 004: ID 1446:6a73 X.J.GROUP Stamps.com Model 510 5LB Scale
Bus 001 Device 005: ID 1446:6a73 X.J.GROUP Stamps.com Model 510 5LB Scale
Bus 001 Device 006: ID 1446:6a73 X.J.GROUP Stamps.com Model 510 5LB Scale
Bus 001 Device 009: ID 413d:2107
Bus 001 Device 003: ID 0424:ec00 Microchip Technology, Inc. (formerly SMSC) SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Microchip Technology, Inc. (formerly SMSC) SMC9514 Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
I bought a bunch of the Onyx 5 lb. scales and had them shipped to the developer of usbscale so he could troubleshoot his multi-scale app. He checked in some changes, so I need to get this all hooked back up again so I can test and be ready for next swarm season.
 
Last edited:
  • Like
Reactions: MilleniX

stevenkan

Ars Legatus Legionis
16,060
I bought a bunch of the Onyx 5 lb. scales and had them shipped to the developer of usbscale so he could troubleshoot his multi-scale app. He checked in some changes, so I need to get this all hooked back up again so I can test and be ready for next swarm season.

He checked in some changes months ago, but this is the first chance I've had to recompile and test it.

usbscale now accepts an optional "--index N" argument, which is nice because it doesn't break existing scripts.

lsusb returns:

Bash:
lsusb
Bus 001 Device 013: ID 1446:6a73 X.J.GROUP Stamps.com Model 510 5LB Scale
Bus 001 Device 008: ID 1446:6a73 X.J.GROUP Stamps.com Model 510 5LB Scale
Bus 001 Device 007: ID 1446:6a73 X.J.GROUP Stamps.com Model 510 5LB Scale
Bus 001 Device 006: ID 214b:7250 Huasheng Electronics USB2.0 HUB
Bus 001 Device 005: ID 04b0:040e Nikon Corp. DSC D70s (ptp)
Bus 001 Device 004: ID 413d:2107 
Bus 001 Device 003: ID 0424:ec00 Microchip Technology, Inc. (formerly SMSC) SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Microchip Technology, Inc. (formerly SMSC) SMC9514 Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

and usbscale returns:

Bash:
pi@PILapse:~ $ sudo usbscale
0 oz
pi@PILapse:~ $ sudo usbscale
208.5 oz
pi@PILapse:~ $ sudo usbscale --index 1
0 oz
pi@PILapse:~ $ sudo usbscale --index 1
208.5 oz
pi@PILapse:~ $ sudo usbscale --index 2
167.9 oz
pi@PILapse:~ $ sudo usbscale --index 3
128.7 oz
pi@PILapse:~ $ sudo usbscale --index 4
No scale with index 4 found on this computer.

So it works! I still have an issue with scales sometimes returning "0 oz." for no reason, but I can usually get a reading just by re-reading it, so I've handled it one level up.