Pi4J equivalent code for Adafruit's basic photocell tutorial


7 posts
by khooke » Fri Dec 14, 2012 12:10 am
I've just followed this Adafruit tutorial:
http://learn.adafruit.com/basic-resisto ... ll-reading

and then written what I think is the equivalent code using the Pi4J library (looping 1000 times before quitting):
Code: Select all
        GpioController controller = GpioFactory.getInstance();
        GpioPinDigitalOutput pin0 = controller.provisionDigitalOutputPin(
                RaspiPin.GPIO_01, "GPIO_1");
       
        for (int i = 0; i < 1000; i++)
        {
            int value = 0;
            pin0.export(PinMode.DIGITAL_OUTPUT);
            pin0.setState(PinState.LOW);       
            pin0.export(PinMode.DIGITAL_INPUT);

            while (pin0.getState() == PinState.LOW)
            {
                value++;
            }
            System.out.println("Value: " + value);
            System.out.println("Pin state: " + pin0.getState());
        }

I realize from the explanation on the Adafruit site that this is a workaround to determine a relative light level by how long it takes for the capacitor to charge before a high level is detected (at least that's what I think is happening), so I'm not at this point interested in an exact reading, I'm just curious how this works.

Firstly, is my code above an equivalent implementation of the Python code example?
Code: Select all
import RPi.GPIO as GPIO, time, os
 
DEBUG = 1
GPIO.setmode(GPIO.BCM)
 
def RCtime (RCpin):
    reading = 0
    GPIO.setup(RCpin, GPIO.OUT)
    GPIO.output(RCpin, GPIO.LOW)
    time.sleep(0.1)
 
    GPIO.setup(RCpin, GPIO.IN)
    # This takes about 1 millisecond per loop cycle
    while (GPIO.input(RCpin) == GPIO.LOW):
        reading += 1
        return reading
 
while True:
    print RCtime(18) # Read RC timing using pin #18


Secondly, there's a noticeable difference between how many iterations the Python code takes before it returns a high reading, compared with the Java code.

For example, for Java using Pi4J:
normal room light level: 0 iterations before returns high level
hand over sensor: 30-40 iterations before returns high level

For Python:
normal room light level: 50-60 iterations before returns high level
hand over sensor: 170-180 iterations before returns high level

I know Pi4J has an state change listener api that's probably preferable than this constant polling approach, but I'd like some thoughts/feedback on whether I'm using the Pi4J api correctly? (or should I just go straight to using the listener approach?)

Thanks! Kevin
Posts: 19
Joined: Sun Oct 07, 2012 6:38 am
Location: CA, USA
by savageautomate » Fri Dec 14, 2012 1:25 am
I am not a Python programmer, but the Java code looks equivalent to me :ugeek:

Are you running on the OpenJDK or the Oracle JDK?
If you are using the OpenJDK, then if you switch to Oracle JDK, the loop iterations may go faster.

See benchmarks here: http://www.savagehomeautomation.com/projects/raspberry-pi-java-virtual-machine-benchmarks.html

(Oracle JRE installation instructions: http://www.savagehomeautomation.com/projects/raspberry-pi-installing-oracle-java-development-kit-jdk-170u.html)

I would suggest using the pin listeners and events to monitor state changes. The listeners are a non-blocking approach so your program can go about doing its other business and the pin state changes are monitored in native C threads via interrupts (meaning not a Java loop behind the scenes still consuming CPU cycles). I think this is close to the most efficient possible way to detect pin state changes.

-Robert
Robert Savage | Follow me @savageautomate
http://www.pi4j.com | http://www.pislices.com
http://www.savagehomeautomation.com
User avatar
Posts: 184
Joined: Thu Aug 16, 2012 3:20 pm
Location: USA
by khooke » Fri Dec 14, 2012 6:10 am
Thanks, I will try out the listeners as that does seem like a much better approach. I am using the Oracle Java SE Embedded JRE.

I'll probably look into getting an analog sensor and an a/d converter at some point, but wanted to try out the capicitor approach since that's what came in the Adafruit starter kit and I wanted to start playing with stuff :D
Posts: 19
Joined: Sun Oct 07, 2012 6:38 am
Location: CA, USA
by khooke » Sat Dec 15, 2012 2:17 am
Hi Robert - how do you use addListener() when it takes GpioPinInput when I need to use the pin in both input and output modes to set the pin state to low, and then wait for the event to trigger when it reaches high?

Instead of provisioning the pin as digialInputPin or digitalOutputPin, I used the more generic provisionPin and then casted it to one of the other as needed, but this approach doesn't seem to work?

Can you use a pin in input and output modes if you're using a listener as well?

Here's my code:
Code: Select all
public void runMonitor()
{
        GpioController controller = GpioFactory.getInstance();
        GpioPin pin = controller.provisionPin(RaspiPin.GPIO_00, "GPIO_0",PinMode.DIGITAL_OUTPUT );
       
        ((GpioPinDigitalOutput)pin).setState(PinState.LOW);

        pin.export(PinMode.DIGITAL_INPUT);
        controller.addListener(new ChangeListener(), (GpioPinDigitalInput)pin);

        while(true)
        {
            //run until quit
            Thread.sleep(500);
        }
}

    public void stateChangeCallback(GpioPin pin)
    {
        // check elapsed time here to get relative light level and do System.out.println()

        pin.export(PinMode.DIGITAL_OUTPUT);
        //reset back to low
        ((GpioPinDigitalOutput)pin).setState(PinState.LOW);
    }


    public class ChangeListener implements GpioPinListenerDigital
    {

        public void handleGpioPinDigitalStateChangeEvent(
                GpioPinDigitalStateChangeEvent event )
        {
            if(event.getState() == PinState.HIGH)
            {
                stateChangeCallback(event.getPin());
            }
        }
       
    }
Posts: 19
Joined: Sun Oct 07, 2012 6:38 am
Location: CA, USA
by savageautomate » Sat Dec 15, 2012 9:19 am
Hi khooke,

That is an interesting problem ..... hmmmm...
Your project wants to drain the capacitor by setting the pin to low once it if flipped to a high state.

Under the covers all GPIO pins are based on this impl class:
http://pi4j.com/apidocs/com/pi4j/io/gpi ... nImpl.html

The provisioning methods perform some setup on the pins for their targeted purpose. So when you provision an input pin, it does setup some native thread listening for interrupts under the covers. Provisioning output pins will not do this.

Try provisioning it as an input pin and then cast the returned object to a GpioPinImpl (if it will let you). Before you attempt to use it to control the pin state, you may have to use the .setMode() method to change the direction to output. After you finish your set instruction, immediately set it back to input pin.

I'm not sure if under the covers in the WiringPi or sysfs layers if changing mode like this will mess of the event listeners. Worth a trial run to see.

Thanks, Robert
Robert Savage | Follow me @savageautomate
http://www.pi4j.com | http://www.pislices.com
http://www.savagehomeautomation.com
User avatar
Posts: 184
Joined: Thu Aug 16, 2012 3:20 pm
Location: USA
by savageautomate » Sat Dec 15, 2012 9:21 am
Also use the .setMode() method instead of the .export() method. The .export() may try to do more than we really need to change pin direction on the fly.
Robert Savage | Follow me @savageautomate
http://www.pi4j.com | http://www.pislices.com
http://www.savagehomeautomation.com
User avatar
Posts: 184
Joined: Thu Aug 16, 2012 3:20 pm
Location: USA
by savageautomate » Sat Dec 22, 2012 6:03 am
FYI, I have added support for a new GpioPinDigitalMultipurpose interface to support cases like this where you need to work with a single pin in input and output modes.

See this new example:
https://github.com/Pi4J/pi4j/blob/74a3562bdd81f146e60a2d862fc2c647d469b367/pi4j-example/src/main/java/MultipurposePinGpioExample.java

Please note that you must manually manage the DIGITAL_INPUT or DIGITAL_OUTPUT modes using the setMode(mode) method. Attempting to perform a control operation on an INPUT pin will result in an exception.

This new feature is available in the latest 0.0.5-SNAPSHOT build:
https://oss.sonatype.org/content/groups/public/com/pi4j/pi4j-distribution/0.0.5-SNAPSHOT/
Robert Savage | Follow me @savageautomate
http://www.pi4j.com | http://www.pislices.com
http://www.savagehomeautomation.com
User avatar
Posts: 184
Joined: Thu Aug 16, 2012 3:20 pm
Location: USA