1

RPi::Pin objects -- looks like they can't be properly destroyed?

In this (perl) Test::More test fragment, I create a pin, use it (I watch the light blink), then destroy the pin object and try to create another. That fails as shown below.

subtest 'pin level reuse' => sub {
my $pinNumber = 4;
my $pin = $wpi->pin ($pinNumber);
$pin->mode (OUTPUT);

# Blink to show it's working
diag "Light will blink";
$pin->write(1); sleep (.2); $pin->write(0); sleep (.2);
$pin->write(1); sleep (.2); $pin->write(0);
pass ('link');

# Now, can I destroy the pin object?
$pin = 0;
pass ('delete pin');

# Now, new pin object?
$pin = $wpi->pin ($pinNumber);
$pin->mode (OUTPUT);
pass ('create new pin');

# Blink to show it's working
diag "Light will blink";
$pin->write(1); sleep (.2); $pin->write(0); sleep (.2);
$pin->write(1); sleep (.2); $pin->write(0);
pass ('link');
};                              # subtest 'pin level reuse'

This fails:

# Subtest: pin level reuse
    # Light will blink
    ok 1 - link
    ok 2 - delete pin
    1..2
ok 6 - pin level reuse
not ok 7 # TODO & SKIP Reusing pins not working yet
# Looks like you planned 6 tests but ran 7.
sc@ordovik:~/repo $ t/Button-002-PI.t
2020/02/29 23:06:11 onPi 1
1..6
ok 1 - 'buttonInterruptQ' isa 'MCE::Shared::Object'
ok 2 - use RPi::WiringPi;
ok 3 - use RPi::Pin;
ok 4 - use Slideshow::Light;
ok 5 - use Slideshow::Button;
# Subtest: pin level reuse
    # Light will blink
    ok 1 - link
    ok 2 - delete pin

cleaned up, exiting...

original error:
pin 4 is already in use... can't create second object

Is this just a limitation? Or there other API calls that I have to make that will eventually free up the pin? (They should be in the destructor for the RPi::Pin object surely, if so?)

dd-b
  • 113
  • 1
  • 8
  • Have you look at [undef](https://perldoc.perl.org/functions/undef.html)? – Polar Bear Mar 01 '20 at 18:54
  • 1
    See code above -- do you think `undef ($pin);` will produce different results from the `$pin = 0;` that I used? Just tried it, and it doesn't; still fails with the same message. This is what I would expect; assigning a new value makes the old eligible for garbage collection, and undef shouldn't do anything different (from the docs), and doesn't in my test. – dd-b Mar 01 '20 at 19:03
  • As long as reference to variable used in the code it will exist. You can create a variable with same name in new context `{ my $pin = $wpi->pin ($pinNumber); .... }` which should mask previous variable with same name. If you wrap your **$pin** code in `{}` the variable will exist only inside of this block. – Polar Bear Mar 01 '20 at 19:11
  • Yes, and assigning zero over the (last, or only) reference will make it no longer exist. Masking it with a lexical variable in an interior scope will definitely NOT work, it just makes it invisible in the interior scope, doesn't make it go away. – dd-b Mar 01 '20 at 22:52
  • Assigning zero does what it suppose to do -- assign **0** and it still continue to exist. If you do not believe then check if with **say 'pin defined' if defined $pin;** – Polar Bear Mar 02 '20 at 00:31
  • 1
    The variable $pin exists, but it no longer contains a reference to the pin object. The reference count on the pin object should have gone to zero (since there are no remaining references to it) and it should be cleaned up -- but it appears not to release some underlying hold it has on the physical pin – dd-b Mar 02 '20 at 03:28
  • You need to look into `RPi::WiringPi` and `RPi::Pin` modules source code to see how resources allocated and made free. Otherwise it is a guess game. It might be that there is object method which frees resources, or may be some other way to free resources, or may be some other variable has a reference on the object (which must be made free before releasing pin). Try to run code in debug mode and see what happens in the code. – Polar Bear Mar 02 '20 at 04:34
  • Until the pin is "unregistered" from the main Pi object, the pin will be seen as still in use, until the Pi object goes out of scope, or its `cleanup()` method is called (this happens on `DESTROY` by default). See my answer for further details. I'm the author of all of the RPi:: distributions on the CPAN. – stevieb Mar 30 '20 at 00:55
  • @PolarBear `undef` won't work in this case, as I put a registration hold on each pin, even if the pin object is destroyed during runtime. This is an intentional safety feature. – stevieb Mar 30 '20 at 00:58

1 Answers1

1

RPi::WiringPi is very sensitive with arbitrarily blowing away pins and other devices. They are all registered internally to prevent bad things from happening. I wrote it with a safety-first mentality.

For example, it has checks built in so that if you try to start up a second script using the same pin number, things will fail. You really don't want to disable and change the mode of a pin if that pin is being used for something by a script far away. You're seeing this error because you're trying to re-use an already-in-use pin within the same script.

Also, once you have an RPi::Pin object as $pin, you can't just arbitrarily set it to 0, as you've now blown away all of the object's functionality and data. The object is an object of type RPi::Pin. Changing it to 0 sets it as an integer.

When the $wpi object goes out of scope, or clean() is called, it resets all of the pins in use back to their default settings as they were when the script was called, and unregisters them for re-use.

It's not wise to just destroy a pin object, because then you don't know what state it's left in. If you want to re-use a pin:

$wpi->unregister_pin($pin);
$pin = $wpi->pin($pin_number);

However, unregister_pin() although left public, is more of an internal thing. Instead of wanting to delete a pin and re-initialize it, it's far better to just change the mode or other settings on the live pin. Essentially, a pin is something that is almost always in use for the full run of an application, not something that get generated and destroyed numerous times per run.

Better yet, for unit testing, I compartmentalize things into small scope:

my $pin = 18;

{ # test 1
    my $wpi = RPi::WiringPi->new;
    my $pin = $wpi->pin($num);

    $pin->mode(OUTPUT);

    is $pin->mode, OUTPUT, "pin mode is output for pin $num";

    # end of scope; everything will be properly cleaned up and destroyed
}

{ # test 2
    my $wpi = RPi::WiringPi->new;
    my $pin = $wpi->pin($num);

    is $pin->mode, INPUT, "pin mode is input for pin $num";
}    
stevieb
  • 9,065
  • 3
  • 26
  • 36