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

Fixing missing GpioController and ShouldDispose #1016

Merged
merged 6 commits into from
Apr 27, 2020

Conversation

Ellerbach
Copy link
Member

@@ -66,6 +67,7 @@ public Gpio(int registerSelectPin, int enablePin, int[] dataPins, int backlightP
}

_controller = controller ?? new GpioController(PinNumberingScheme.Logical);
_shouldDispose = shouldDispose;
Copy link
Member

Choose a reason for hiding this comment

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

I would recommend to by default set this to false whenever the controller parameter wasn't null. So basically, if somebody passed in a controller, let's default shouldDispose to false.

Copy link
Member Author

Choose a reason for hiding this comment

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

The current behavior was that everything was disposed if you passed one GpioController. So the shouldDispose is then by default true.
Also this create consistency across all the bindings.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would prefer false as well, but it would be a breaking change for existing bindings.
@krwq Your oppinion might be useful here.

Copy link
Member

Choose a reason for hiding this comment

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

One thing we should definitely do is to always dispose when controller is null (or throw an exception when user passes null and asks for not disposing).

Considering our current guidelines is to always dispose and to not re-use GpioController IMO we should always dispose but I'm not strongly opinionated. I think it's much easier to explain what this method does if we consistently dispose or not dispose - if we choose to not dispose then we should change guidelines to match.

Copy link
Member Author

Choose a reason for hiding this comment

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

OK, so let me adjust the code to:

  • dispose whatever the shouldDispose parameter is if gpioController is null
  • In case gpioController is not null, then follow the shouldDispose wish

Recommendatin is not to reuse GpioController but in some cases like when using FT4222 or a controller that can only be created once, you don't really have other options.

@@ -20,9 +20,11 @@ public abstract class DCMotor : IDisposable
/// Constructs generic <see cref="DCMotor"/> instance
/// </summary>
/// <param name="controller"><see cref="GpioController"/> related with operations on pins</param>
protected DCMotor(GpioController controller)
/// <param name="shouldDispose">True to dispose the Gpio Controller</param>
protected DCMotor(GpioController controller, bool shouldDispose = true)
Copy link
Member

Choose a reason for hiding this comment

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

You don't really need to default shouldDispose here since this is a protected constructor and we are only calling it from our subclasses.

Copy link
Member Author

Choose a reason for hiding this comment

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

Right, I'll adjust this one.

/// <returns><see cref="DCMotor"/> instance</returns>
/// <remarks>
/// PWM pin <paramref name="speedControlPin"/> can be connected to either enable pin of the H-bridge.
/// or directly to the input related with the motor (if H-bridge allows inputs to change frequently).
/// <paramref name="directionPin"/> should be connected to H-bridge input corresponding to one of the motor inputs.
/// Connecting motor directly to GPIO pin is not recommended and may damage your board.
/// </remarks>
public static DCMotor Create(int speedControlPin, int directionPin, GpioController controller = null)
public static DCMotor Create(int speedControlPin, int directionPin, GpioController controller = null, bool shouldDispose = true)
Copy link
Member

Choose a reason for hiding this comment

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

Same comment as the CharacterLCd here, we should probably have a Create Overload that doesn't take a shouldDispose, and set it to false in case controller was passed in. Another way to do this is to default it to false instead, and only set it to true if the controller that was passed in was null.

Copy link
Member Author

Choose a reason for hiding this comment

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

Same comment then for the previous one, it's more about consistency.

Copy link
Member

Choose a reason for hiding this comment

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

I see, I'm fine with keeping it as is for consistency

/// <returns><see cref="DCMotor"/> instance</returns>
/// <remarks>
/// <paramref name="speedControlPin"/> can be connected to either enable pin of the H-bridge.
/// or directly to the input related with the motor (if H-bridge allows inputs to change frequently).
/// Connecting motor directly to GPIO pin is not recommended and may damage your board.
/// </remarks>
public static DCMotor Create(int speedControlPin, GpioController controller = null)
public static DCMotor Create(int speedControlPin, GpioController controller = null, bool shouldDispose = true)
Copy link
Member

Choose a reason for hiding this comment

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

Wait with your latest changes you are not really disposing anything right? It looks like you are only taking in the extra parameter but not really assigning to anything, and also not checking it before disposing.

public DhtBase(int pin, PinNumberingScheme pinNumberingScheme = PinNumberingScheme.Logical)
/// <param name="gpioController"><see cref="GpioController"/> related with operations on pins</param>
/// <param name="shouldDispose">True to dispose the Gpio Controller</param>
public DhtBase(int pin, PinNumberingScheme pinNumberingScheme = PinNumberingScheme.Logical, GpioController gpioController = null, bool shouldDispose = true)
Copy link
Member

Choose a reason for hiding this comment

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

Same here, it doesn't look like you are actually checking this value before disposing.

{
if (disposing)
if (_shouldDispose)
Copy link
Member

Choose a reason for hiding this comment

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

This is not really disposing, this is only really turning the LED off. I realize you didn't introduce this, but now that you are touching this do you mind fixing that? Just dispose the controller after calling Off. Also, I believe Off should be called regardless in this case.

@@ -104,24 +106,22 @@ public void Off()

#region IDisposable Support

private bool _disposedValue = false; // Para detectar llamadas redundantes
private bool _shouldDispose;
Copy link
Member

Choose a reason for hiding this comment

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

The pourpuse of the previous line was different though, it was meant to make sure that the double call to Dispose() wouldn't crash the device


/// <summary>
/// Disposes the <see cref="Lights"/> instance
/// </summary>
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
if (disposing)
Copy link
Member

Choose a reason for hiding this comment

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

I think you shouldn't change this condition from my comment above. This was really just meant to catch the case of double call to Dispose()


/// <summary>
/// Disposes the <see cref="Motors"/> instance
/// </summary>
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
if (disposing)
Copy link
Member

Choose a reason for hiding this comment

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

Same comment here.

@@ -147,10 +150,13 @@ private bool TryGetDistance(out double result)
/// <inheritdoc/>
public void Dispose()
{
if (_controller != null)
if (_shouldDispose)
{
Copy link
Member

Choose a reason for hiding this comment

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

NIT: you can also change the bellow code to just be:

_controller?.Dispose();
_controller = null;

in order to remove the if-check

{
_sensor = sensor;
_ce = ce;
_irq = irq;
PacketSize = packetSize;
_shouldDispose = shouldDispose;
Copy link
Member

Choose a reason for hiding this comment

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

You are not checking this value on the Dispose() call and are always disposing.

@@ -39,10 +40,12 @@ public abstract class Pcx857x : GpioDriver
/// The GPIO controller for the <paramref name="interrupt"/>.
/// If not specified, the default controller will be used.
/// </param>
public Pcx857x(I2cDevice device, int interrupt = -1, GpioController gpioController = null)
/// <param name="shouldDispose">True to dispose the Gpio Controller</param>
public Pcx857x(I2cDevice device, int interrupt = -1, GpioController gpioController = null, bool shouldDispose = true)
Copy link
Member

Choose a reason for hiding this comment

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

NIT: Try not to default the values on abstract classes so that we make sure that we are flowing this value from all of the subclasses. That's the same feedback that I was suggesting on DCMotor

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, that said, I just added on top, the initial class had anyway some default values.

@joperezr
Copy link
Member

Left a few minor comments where you missed Disposing or other minor things, but looks good in general. Thanks a lot for fixing @Ellerbach

@Ellerbach
Copy link
Member Author

Left a few minor comments where you missed Disposing or other minor things, but looks good in general. Thanks a lot for fixing @Ellerbach

OK, so I have adjusted the misses and also made consistency to always dispose when the controller is null. Please check again all is ok! Thanks again

@@ -269,7 +277,11 @@ internal virtual void ReadThroughI2c()
/// <inheritdoc/>
public void Dispose()
{
_controller?.Dispose();
if (_shouldDispose)
{
Copy link
Member

Choose a reason for hiding this comment

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

NIT: looks like here we are missing to also reassign _controller to Null, I know it wasn't introduced by your change but can we do that in case double Dispose is called?

Copy link
Member Author

Choose a reason for hiding this comment

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

It's a readonly value, so I don't think it can be assigned to null?

@@ -76,7 +80,10 @@ protected virtual void Dispose(bool disposing)
{
if (disposing)
{
Off();
if (_shouldDispose)
Copy link
Member

Choose a reason for hiding this comment

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

Looks like my last comment on here hasn't been addressed yet. For convenience, this was it:

This is not really disposing, this is only really turning the LED off. I realize you didn't introduce this, but now that you are touching this do you mind fixing that? Just dispose the controller after calling Off. Also, I believe Off should be called regardless in this case.

LedArray[1].Dispose();
LedArray[2].Dispose();
LedArray[3].Dispose();
if (_shouldDispose)
Copy link
Member

Choose a reason for hiding this comment

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

NIT, looks like we forgot to also dispose the controller here.

Copy link
Member Author

Choose a reason for hiding this comment

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

the controller should not be disposed in this dispose as every individual Led is disposing it. I moved out of the if the individual calls to each Led.

Copy link
Member

Choose a reason for hiding this comment

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

would that be a problem though? it will mean that perhaps the first LED will dispose it, and then the second one will try to call Off() before disposing it again and that will fail. Wouldn't it be better to just have the LEDs dispose and have the Ligths Dispose dispose the actual controller?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, great catch. Adding Off(); before disposing. Then the controller will be disposed at the Lights level, not at every individual level.

@@ -91,8 +95,11 @@ protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_motorArray[0].Dispose();
_motorArray[1].Dispose();
if (_shouldDispose)
Copy link
Member

Choose a reason for hiding this comment

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

not disposing the controller here either.

@pgrawehr
Copy link
Contributor

While working on #1044: It might really be worth considering to make the GpioController a mandatory parameter, even if that's breaking. Otherwise, the interface is error-prone, as people leave out the paramter, even if they need to provide one (i.e. for Arduino, etc). This will either cause an exception, because the standard GpioController is likely not available, or worse still, use the wrong controller if there are multiple (and make people wonder why it doesn't work).
Also, as I designed it now, using a GpioController without a Board reference should be deprecated.

@Ellerbach
Copy link
Member Author

While working on #1044: It might really be worth considering to make the GpioController a mandatory parameter, even if that's breaking

I did change all the constructors to add the GpioController as a paramter. If null, then the default one will be used. If not null, then the shouldDispose parameter will decide if to be disposed or not at the end.

@pgrawehr
Copy link
Contributor

I did change all the constructors to add the GpioController as a paramter. If null, then the default one will be used. If not null, then the shouldDispose parameter will decide if to be disposed or not at the end.

Yes, I know. But I think this might not be optimal. I'm concerned about the fact that the default one will be wrong or just not work at all. I.e. if one is working with an Arduino or an FT4222, attempting to use the default GpioController will just throw an exception, because the default controller for such boards cannot be auto-detected.

So it might be better to require the GpioController to be not null upon constructing the bindings, to avoid this kind of possible error. This would prevent the above misstake as well as later prevent "dangling" controllers that are not associated to a board. (Note: the exception to this rule would of course be bindings where the controller truly is optional, such as the MCP chips, where it's optionally used for interrupt handling only).

@Ellerbach
Copy link
Member Author

I'm concerned about the fact that the default one will be wrong or just not work at all. I.e. if one is working with an Arduino or an FT4222, attempting to use the default GpioController will just throw an exception, because the default controller for such boards cannot be auto-detected

Yes and no. I get your point. Now, if you are using an Arduino or a FT4222, you know it and it's on purpose. So you will create the GpioController before and pass it to the constructor. So in short, you'll do the work of passing it. Now, for the very fast majority, the build in driver is just what they need. So they'll just pass it. I guess the important is to make sure we can, if needed pass an Arduino or a FT4222 or any other GpioController we'll build over time. But keep it simple for most people.

@pgrawehr
Copy link
Contributor

Now, if you are using an Arduino or a FT4222, you know it and it's on purpose. So you will create the GpioController before and pass it to the constructor. So in short, you'll do the work of passing it. Now, for the very fast majority, the build in driver is just what they need. So they'll just pass it. I guess the important is to make sure we can, if needed pass an Arduino or a FT4222 or any other GpioController we'll build over time. But keep it simple for most people.

I'm not sure that "most people" will be working on (typically) a Raspi. Right now, that's certainly the case, but with Arduino and FT4222 supoort, this library could become feasible also as platform for school projects and rapid prototyping. On the other hand, keeping it simple is certainly a good concept.

Maybe some more complex solutions are necessary, like keeping a static reference somewhere, so that the GpioController can be "auto-detected". Has other drawbacks, I know, but just my 2ct.

@joperezr joperezr self-assigned this Apr 24, 2020
Copy link
Member

@joperezr joperezr left a comment

Choose a reason for hiding this comment

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

Changes look good @Ellerbach thanks for the contribution and sorry for the delay. I will hold off on merging this so we don't conflict with #1038 but will merge it once that one goes in.

@joperezr joperezr merged commit 8e1f3ef into dotnet:master Apr 27, 2020
@github-actions github-actions bot locked and limited conversation to collaborators Dec 12, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add a default constructor on bindings to take GpioController
4 participants