Skip to content
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

Updated Protocol + Scan worker #70

Merged
merged 2 commits into from
Apr 21, 2017
Merged

Updated Protocol + Scan worker #70

merged 2 commits into from
Apr 21, 2017

Conversation

dcyoung
Copy link

@dcyoung dcyoung commented Apr 14, 2017

This PR brings a few changes:

  1. Updated protocol to match the new firmware that is installed on devices shipping out today.
  2. Updated to perform checks for status byte codes indicating failures that do not result in errors.
  3. Provide methods (and update existing methods) to wait until the device is ready before attempting to change motor speed or start scanning.
  4. Add scan worker to accumulate scans in a queue using a background thread.
  5. Return complete scans between 0-360 degrees (other than first scan which is inherently partial).
  6. Offer alternative non-blocking functions where possible (currently only available in C library).
  7. Increase delay between stop commands in the stop_scanning function to account for the device logging session data after responding to the first stop.

I have tested the examples, and the bindings on windows. I have tested the examples and bindings on ubuntu 14.04, but @kent-williams mentioned he might have experienced an issue with the python bindings on 16.04. The travis builds might not indicate anything as they're using the dummy build. So i'd love to get additional testing feedback if you have the time @daniel-j-h

This PR needs get merged ASAP, as people will be receiving devices very soon (early/mid next week). I'll go ahead and merge this in a few days if there aren't any complaints. But please add your feedback if you have it!

@daniel-j-h
Copy link
Collaborator

daniel-j-h commented Apr 16, 2017 via email

Signals the `sweep_device_s` to start scanning.
Will only succeed if the motor speed is \>0Hz and stable.
User is responsible for checking these conditions before calling. User can check that motor speed has stabilized using `sweep_device_get_motor_ready` and that motor speed is \> 0Hz using `sweep_device_get_speed`.
Will NOT start an internal background thread. User is responsible for keeping up with incoming scans by calling `sweep_device_get_scan_direct`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm why do we mix waiting for motor to stabilize and worker thread here? (And in the PR in general)
Shouldn't the two be orthogonal concepts?

What I'm a bit worried about is the API is starting to get much more confusing than just start scanning - get scans.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea behind the non-blocking versions of certain methods, was to provide options for low-level development, where blocking or threading wasn't available.

Copy link
Author

@dcyoung dcyoung Apr 19, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've gone ahead and removed the alternative methods from the API for now. We can merge this PR, and worry about the naming conventions etc later. The only changes to the public API will be the addition of two new methods (get_motor_ready and wait_until_motor_ready). As @MikeGitb suggested, we can work on the implementation details and naming conventions for the alternative bits AFTER this PR... and merge the updated API later when we're comfortable with it.

void sweep_device_stop_scanning(sweep_device_s device, sweep_error_s* error)
```

Signals the `sweep_device_s` to stop scanning.
Blocks for ~20ms to allow time for trailing data stream to collect and flush internally.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this depend on motor speed? 20ms is the upper bound?

```

Blocks until the `sweep_device_s`'s motor speed has stabilized. This is useful whenever the motor speed is changed. The worst case is a 6 second wait for motor stabilization and calibration. If the motor is already spinning at the desired speed, this returns almost instantly. For visual reference, the blue LED on the device will blink while the motor speed is changing, and stop blinking when the speed has stabilized. If the motor speed has NOT yet stabilized, the device will respond to certain commands (`DS` or `MS`) with a status code indicating a failure to execute the command. Therefore, it is best practice to avoid this entirely by calling `sweep_device_wait_until_motor_ready` before calling a command that requires motor speed is stabilized.
In case of error a `sweep_error_s` will be written into `error`. Will timeout.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does "will timeout" mean here? That there is an internal timeout of 6s and it will error out if this limit is exceeded?

```

Returns 0 if `sweep_device_s`'s motor speed has stabilized.
Returns 1 if not.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We require C99+ in the API, so we have stdbool.h - please use bool here.

https://github.com/scanse/sweep-sdk/blob/master/libsweep/include/sweep/sweep.h#L42

bool sweep_device_is_motor_ready(..)

@@ -245,7 +289,16 @@ Opaque type representing a single full 360 degree scan from a `sweep_device_s`.
sweep_scan_s sweep_device_get_scan(sweep_device_s device, sweep_error_s* error)
```

Blocks waiting for the `sweep_device_s` to accumulate a full 360 degree scan into `sweep_scan_s`.
Returns the ordered readings (1st to last) from a single scan.
Retrieves the oldest scan from a queue of scans accumulated in a background thread. Blocks until a scan is available. To be used after calling `sweep_device_start_scanning`, NOT after `sweep_device_attempt_start_scanning`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and below is what I meant - there now is a direct dependency between functions. And the naming

get_scan vs get_scan_direct
start_scan vs attempt_start_scan

does not really make it clear when to use which version.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we give any guarantees wrt. the queue? How large is it? Should the user be able to specify?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For non low-level development, the API should be unchanged. The "attempt_..." methods are provided as niche case alternatives... and not meant to be advertised. I went ahead and separated these methods out of the main README so that the distinction is clear. See if that makes more sense.

If you have ideas for a better naming convention, please share! I'm not very content with the current "attempt_" and "direct_" pattern.

// create a thread
std::thread th = std::thread(sweep_device_accumulate_scans, device);
// detach the thread so that it runs in the background and cleans itself up
th.detach();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, shouldn't we block (i.e. .join()) in the device's destructor? That is, set the stop scanning atomic flag, then wait until done.

Copy link
Author

@dcyoung dcyoung Apr 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand it, you cannot join a detached thread. The detached background thread cleans itself up properly when it's method returns or terminates. Seeing how the behavior of the thread exists in the "background" with no need to synchronize, it seems like a much simpler solution to avoid explicitly managing the thread. I could be wrong, but I believe this is the exact kind of intended use for detached threads.

sweep_error_s readyerror = nullptr;
int32_t motor_ready;
// Only check for 8 seconds (16 iterations with 500ms pause)
for (auto i = 0; i < 16; ++i) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why 16? Why 8 seconds?

return;
}
// pause for 500ms
std::this_thread::sleep_for(std::chrono::milliseconds(500));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

sweep.start_scanning()

# get_scans is coroutine-based generator lazily returning scans ad infinitum
# get_scans is coroutine-based generator lazily returning scans ad
# infinitum
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why break here?

const uint8_t status_bytes[2] = {response.cmdStatusByte1, response.cmdStatusByte2};
int32_t status_code = sweep::protocol::ascii_bytes_to_integral(status_bytes);
switch (status_code) {
case 11:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is 11? Can we symbolize this

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

11 is a failure status code that indicates an invalid parameter. These details are outlined in the protocol docs, but I think it might be best to include them in the protocol implementation. Perhaps making a container of possible status codes for each type of response?

@MikeGitb
Copy link
Contributor

If this is to be released soon, may I suggest to not put the low level functions into the public API just yet?
As changing the API afterwards is usually undesirable I think it would better to have one or two iterations on it before settling for a design, but as there are probably only very few users of it it should imho not block the merge of this PR.

Talking about blocking the merge: For substantial changes like this I often take a triaging approach: Only hold of the merge, until the API is OK and the code appears to be bugfree (that is the tricky part) and then address implementation issues in separate (smaller) PRs.

@dcyoung
Copy link
Author

dcyoung commented Apr 19, 2017

@MikeGitb I like the idea of waiting to add these "alternative" methods to the API until they are more fleshed out and the naming convention is settled. The rest of the API should remain unchanged. The implementation differs, and we've added new methods relevant to "motor ready", but the core start/stop methods etc are the same. I'll remove the alternative methods from the API in the next few hours and update the PR.

Edit: Updated!

Copy link
Author

@dcyoung dcyoung left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor fixes

@@ -34,6 +34,65 @@ void sweep_error_destruct(sweep_error_s error) {
delete error;
}

// ------------------ Blocking Methods (blocking) ---------------- //
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reorganize the methods to match sweep.h/sweep.cc and remove the outdated comments

@@ -42,10 +42,18 @@ sweep = new Sweep('/dev/ttyUSB0');
sweep.startScanning();
sweep.stopScanning();

// waits until motor is ready
sweep.waitUntilMotorReady();
// code === 0 -> ready (motor ready), code !== 0 -> not ready
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update sweepjs bindings reflect boolean return, instead of a motor ready code

@@ -62,10 +62,12 @@ class Sweep:
def start_scanning(self) -> None
def stop_scanning(self) -> None

def get_motor_speed(self) -> int
def wait_until_motor_ready(self) -> None
def get_motor_ready(self) -> int (0 indicates motor is ready)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indicate that a bool is returned

@dcyoung
Copy link
Author

dcyoung commented Apr 21, 2017

Just pushed an update to the MAJOR version, and added a table for firmware compatibility in the README. If the appveyor + travis builds look good, I'm going to go ahead and merge this.

@dcyoung dcyoung merged commit b867c37 into master Apr 21, 2017
@dcyoung dcyoung deleted the scan-worker branch April 21, 2017 18:13
@dcyoung
Copy link
Author

dcyoung commented Apr 22, 2017

Unfortunately, I realized after merging this that the "wait_until_motor_ready" method was not working properly when being called externally. It works fine internally. This might have something to do with the error handling.

Luckily, this method is not actually necessary, given that "start_scanning" and "set_motor_speed" call it internally. Therefore, I stripped the method from the public API and quickly pushed the fix to master. I avoided re-basing master in case anyone already grabbed it. But my apologies if anyone has been working with it during the few hours before the fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants