sharpbrick / powered-up

.NET implementation of the LEGO PoweredUp Protocol
MIT License
94 stars 19 forks source link

More usage examples for continuous motor control? #186

Closed LiamDobbelaere closed 2 years ago

LiamDobbelaere commented 2 years ago

Hi, I was trying powered-up the other day and I noticed a pretty big delay when trying to control motors using a controller. I assume this is because I keep sending StartPowerAsync and awaiting it, which seems to be expensive performance wise?

All of this happens inside of a while-loop, so the code is running as fast as it can, though waits for the Bluetooth commands to go through.

If StartPowerAsync isn't the way to control motor speed continuously, what is? I'd love some more examples on how this can be accomplished.

if (currentAccelerationValue != lastAccelerationValue)
{
    await aMotor.StartPowerAsync(currentAccelerationValue);
    lastAccelerationValue = currentAccelerationValue;
}

if (currentTurnValue != lastTurnValue)
{
    await bMotor.GotoPositionAsync((sbyte)(currentReading.LeftThumbstickX * 90), 100, 100, SpecialSpeed.Brake);
    lastTurnValue = currentTurnValue;
} 
tthiery commented 2 years ago

Yes, there is a Bluetooth Ping Pong. Problem with the protocol is that hubs have a buffer ... so if you send them commands too fast, they might drop them.

So let me see how I can help you. From your code snippet, I assume you have somewhere input for currentAccelerationValue and currentTurnValue which you synchronize into the motor using that loop.

Depending on your use case I would recommend on of the following:

LiamDobbelaere commented 2 years ago

The detailed feedback is much appreciated, thanks! I'll give it a shot later and see if I can get it to work faster. It's good to know about the command drops and out-of-order delivery if you send too fast. Maybe a threshold will work just fine.

LiamDobbelaere commented 2 years ago

For whoever comes across this, I ended up solving it using a command pool. Basically, waiting for every command to complete is too slow, but sending them all at once overloads the device's buffer.

public partial class MainWindow : Window {
    List<Task> commandPool = new List<Task>();
    const int maxCommands = 2; // can be tweaked
// ...

private void TimerTick(Object stateInfo) {
// ...
    // trying to send commands every tick
    if (commandPool.Count < maxCommands) {
        commandPool.Add(aMotor.StartPowerAsync((sbyte) -currentAccelerationValue));
    }

    // remove completed commands every tick
    commandPool.RemoveAll(t => t.IsCompleted);
// ...
tthiery commented 2 years ago

Cool. If you have other questions, just create an issue