-
Notifications
You must be signed in to change notification settings - Fork 113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RSDK-7152 Analog Reader API rdk changes #3946
Conversation
Warning your change may break code samples. If your change modifies any of the following functions please contact @viamrobotics/fleet-management. Thanks!
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple small but important bugs, and then a whole bunch of optional suggestions and discussion questions that don't have obvious answers (so, maybe ignore those and leave things as-is).
components/board/board.go
Outdated
@@ -77,12 +77,19 @@ type Board interface { | |||
// An Analog represents an analog pin that resides on a board. | |||
type Analog interface { | |||
// Read reads off the current value. | |||
Read(ctx context.Context, extra map[string]interface{}) (int, error) | |||
Read(ctx context.Context, extra map[string]interface{}) (int, AnalogRange, error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussion question: what are the pros and cons of this approach vs. making the new struct AnalogValue
and having all 4 fields in it (the 3 currently in AnalogRange
and the value itself)? I find the new function signature to Read
surprising, because I'd expect it to return the data we read and an error, and having a third return value is unexpected. AnalogValue
would also more closely match the protobuf structure (though matching the protobuf structure is not a goal in and of itself; it's just an unsurprising and natural thing to do).
I'm not opposed to this approach, I just think it's surprising, and am wondering if there's a reason you went with this choice instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can change to returning anAnalogValue
if you prefer it. I thought that adding a new return would be less breaking since if you still just want the bit value only you can get it and do _
for the analogrange, but still a breaking change either way so probably doesn't matter that much
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have a strong preference; I just think it's something to consider, in the hopes that on further thought one design is better than the other. Leave as-is if you want. 🤷
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
okay I changed it to one return value, prob better to just have one if theres not a great reason to have 2
components/board/numato/board.go
Outdated
stepSize = max / 1024 | ||
default: | ||
} | ||
stepSize *= 1000 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we multiplying stepSize
by a thousand? The whole idea of "maximum voltage divided by maximum bit size" seemed like a good idea to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the scope doc converted the example calculation to mV/bit
, I think it makes it more readable otherwise its very small values but willing to discuss changing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense! Put a comment here pointing out the right units.
(apologies for the confusion where I remarked on this in the test)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...wait, so Min and Max are in volts, but StepSize is in millivolts? Put a comment in the definition of the struct explaining that.
...or rather, it seems surprising that they have different units, and perhaps the way to make it less surprising, instead of documenting the surprise, is to change the units so they're either all volts or all millivolts. I checked the scope doc, but it doesn't explicitly mention the units on any of these values (two examples mention mV and rejected alternative 3 mentions units, but I don't think this was ever laid out clearly).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah thats fair ill just keep it in volts/bit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All my feedback is either optional or trivial. LGTM!
// AnalogValue contains all info about the analog reading. | ||
// Value represents the reading in bits. | ||
// Min and Max represent the range of raw analog values. | ||
// StepSize is the precision per bit of the reading. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Thanks for this comment!
components/board/numato/board.go
Outdated
case 0xC05: | ||
// 1 channel usb relay module numato - 10 bit resolution | ||
max = 5.0 | ||
stepSize = max / 1024 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussion question: none of these ever change, right? Would it be simpler to calculate them once on initialization, and then just use the stored values within Read()
? I'm unsure: we don't really care about them until here, but it feels wasteful to have a switch statement (which contains a memory fence, which slows everything down) every time we read the value. and yet, such an optimization is probably small enough it's not even worth worrying over, and this version is just fine. I dunno; what do you think? Maybe leave as-is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah that makes sense to me, ill do this at the start instead.
components/board/numato/board.go
Outdated
} | ||
|
||
if productID != 0x800 && productID != 0x802 && productID != 0x805 && productID != 0xC05 { | ||
logger.Warnf("analog range and step size is not supported for numato with product id %d", productID) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change is
to are
.
|
||
import "go.viam.com/utils/usb" | ||
|
||
// getSerialDevices returns all devices connected by USB on a mac. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment doesn't really match code: I think this returns descriptions of devices, not devices themselves. and yet, I can get that from the function signature, so it doesn't necessarily need to be in the comment, too. but I still don't easily see what this function is for. I think the point is that we use it to find the productID of the numato board plugged in, and use that to figure out what capabilities the board has? That's the part that really belongs in a comment: supply the "why" that isn't easily gotten from the code (which supplies the "what").
Same suggestion in linux_utils.go, too.
components/board/numato/board.go
Outdated
// 32 channel usb numato has 10 bit resolution | ||
max = 3.3 | ||
stepSize = max / 1024 | ||
case 0x800: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mild preference to sort the cases in the switch statement, so it's easier to find the one you care about.
as.lastError.Store(&errValue{err != nil, err}) | ||
as.mu.Lock() | ||
as.analogVal = analogVal | ||
as.mu.Unlock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes it look like the point of the mutex is to protect analogVal
. If that's the case, we should also lock the mutex before line 65. I wonder if lines 70-71 (locking and deferring unlocking the mutex) should just be moved to the top of the function for simplicity.
Another option is to change Read
to never return as.analogVal
itself, but always to create a new board.AnalogValue
struct instead and copy data into that. Then, as.analogVal
is constant after this line, and we don't need the mutex at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
did the second option and removed the mutex
@@ -104,6 +110,14 @@ func (as *AnalogSmoother) Start() { | |||
as.workers = utils.NewStoppableWorkers(func(ctx context.Context) { | |||
consecutiveErrors := 0 | |||
var lastError error | |||
|
|||
// Store the full analog reader value | |||
analogVal, err := as.Raw.Read(ctx, nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there's a race condition here: if someone calls Read()
before the background goroutine has gotten to here, the min/max/stepsize will all be 0. I suspect this paragraph should happen in SmoothAnalogReader()
instead of here, so the entire struct is initialized before it can be used.
Numato was weird due to the driver being able to support multiple numato versions that all have different ADCs. the driver will search for the product id and if its one of the 4 that we "officially" support we return the correct values, otherwise we just return zeros.