read temperature from DHT11, using pi4j

2020-05-29 03:06发布

问题:

I'm trying to read temperature data from a DHT11 temperature sensor, using pi4j. I followed the code written in c and python in this site: http://www.uugear.com/portfolio/dht11-h ... or-module/ But it's not working. when I test the instruction 'dht11Pin.getState()' it's always in HIGH state, never changing. Is there anything wrong in my code?

Below is my code:

import java.util.concurrent.TimeUnit;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.pi4j.component.ObserveableComponentBase;
import com.pi4j.io.gpio.GpioController;
import com.pi4j.io.gpio.GpioFactory;
import com.pi4j.io.gpio.GpioPinDigitalMultipurpose;
import com.pi4j.io.gpio.Pin;
import com.pi4j.io.gpio.PinMode;
import com.pi4j.io.gpio.PinPullResistance;
import com.pi4j.io.gpio.PinState;
import com.pi4j.io.gpio.RaspiPin;

public class DHT11 extends ObserveableComponentBase {

private static final Pin DEFAULT_PIN = RaspiPin.GPIO_04;
private static final int MAXTIMINGS = 85;
private int[] dht11_dat = { 0, 0, 0, 0, 0 };
private GpioPinDigitalMultipurpose dht11Pin;
private static final Logger LOGGER = LogManager.getLogger(DHT11.class
        .getName());

public DHT11() {
    final GpioController gpio = GpioFactory.getInstance();
    dht11Pin = gpio.provisionDigitalMultipurposePin(DEFAULT_PIN,
            PinMode.DIGITAL_INPUT, PinPullResistance.PULL_UP);
}

public DHT11(int pin) {
    final GpioController gpio = GpioFactory.getInstance();
    dht11Pin = gpio.provisionDigitalMultipurposePin(LibPins.getPin(pin),
            PinMode.DIGITAL_INPUT, PinPullResistance.PULL_UP);
}

public double getTemperature() {
    PinState laststate = PinState.HIGH;
    int j = 0;
    dht11_dat[0] = dht11_dat[1] = dht11_dat[2] = dht11_dat[3] = dht11_dat[4] = 0;
    StringBuilder value = new StringBuilder();
    try {

        dht11Pin.setMode(PinMode.DIGITAL_OUTPUT);
        dht11Pin.low();
        Thread.sleep(18);
        dht11Pin.high();
        TimeUnit.MICROSECONDS.sleep(40);
        dht11Pin.setMode(PinMode.DIGITAL_INPUT);

        for (int i = 0; i < MAXTIMINGS; i++) {
            int counter = 0;
            while (dht11Pin.getState() == laststate) {
                counter++;
                TimeUnit.MICROSECONDS.sleep(1);
                if (counter == 255) {
                    break;
                }
            }

            laststate = dht11Pin.getState();

            if (counter == 255) {
                break;
            }

            /* ignore first 3 transitions */
            if ((i >= 4) && (i % 2 == 0)) {
                /* shove each bit into the storage bytes */
                dht11_dat[j / 8] <<= 1;
                if (counter > 16) {
                    dht11_dat[j / 8] |= 1;
                }
                j++;
            }
        }
        // check we read 40 bits (8bit x 5 ) + verify checksum in the last
        // byte
        if ((j >= 40) && checkParity()) {
            value.append(dht11_dat[2]).append(".").append(dht11_dat[3]);
            LOGGER.info("temperature value readed: " + value.toString());
        }

    } catch (InterruptedException e) {

        LOGGER.error("InterruptedException: " + e.getMessage(), e);
    }
    if (value.toString().isEmpty()) {
        value.append(-1);
    }
    return Double.parseDouble(value.toString());
}

private boolean checkParity() {
    return (dht11_dat[4] == ((dht11_dat[0] + dht11_dat[1] + dht11_dat[2] + dht11_dat[3]) & 0xFF));
}

}

回答1:

I started with the original poster's java code, and replaced the com.pi4j.io.gpio package references with the com.pi4j.wiringpi package. I had recently installed the newest pi4j package and wiringpi version on my Raspberry Pi.

Using that package the Java code below works approximately the same as the c version of this program. I am getting about 80% - 85% accurate responses with a DHT-11. Which is about the same as I was getting using wiringPi in c.

package gpio;
import com.pi4j.wiringpi.Gpio;
import com.pi4j.wiringpi.GpioUtil;

public class DHT11 {
    private static final int    MAXTIMINGS  = 85;
    private final int[]         dht11_dat   = { 0, 0, 0, 0, 0 };

    public DHT11() {

        // setup wiringPi
        if (Gpio.wiringPiSetup() == -1) {
            System.out.println(" ==>> GPIO SETUP FAILED");
            return;
        }

        GpioUtil.export(3, GpioUtil.DIRECTION_OUT);
    }

    public void getTemperature(final int pin) {
        int laststate = Gpio.HIGH;
        int j = 0;
        dht11_dat[0] = dht11_dat[1] = dht11_dat[2] = dht11_dat[3] = dht11_dat[4] = 0;

        Gpio.pinMode(pin, Gpio.OUTPUT);
        Gpio.digitalWrite(pin, Gpio.LOW);
        Gpio.delay(18);

        Gpio.digitalWrite(pin, Gpio.HIGH);
        Gpio.pinMode(pin, Gpio.INPUT);

        for (int i = 0; i < MAXTIMINGS; i++) {
            int counter = 0;
            while (Gpio.digitalRead(pin) == laststate) {
                counter++;
                Gpio.delayMicroseconds(1);
                if (counter == 255) {
                    break;
                }
            }

            laststate = Gpio.digitalRead(pin);

            if (counter == 255) {
                break;
            }

            /* ignore first 3 transitions */
            if (i >= 4 && i % 2 == 0) {
                /* shove each bit into the storage bytes */
                dht11_dat[j / 8] <<= 1;
                if (counter > 16) {
                    dht11_dat[j / 8] |= 1;
                }
                j++;
            }
        }
        // check we read 40 bits (8bit x 5 ) + verify checksum in the last
        // byte
        if (j >= 40 && checkParity()) {
            float h = (float) ((dht11_dat[0] << 8) + dht11_dat[1]) / 10;
            if (h > 100) {
                h = dht11_dat[0]; // for DHT11
            }
            float c = (float) (((dht11_dat[2] & 0x7F) << 8) + dht11_dat[3]) / 10;
            if (c > 125) {
                c = dht11_dat[2]; // for DHT11
            }
            if ((dht11_dat[2] & 0x80) != 0) {
                c = -c;
            }
            final float f = c * 1.8f + 32;
            System.out.println("Humidity = " + h + " Temperature = " + c + "(" + f + "f)");
        } else {
            System.out.println("Data not good, skip");
        }

    }

    private boolean checkParity() {
        return dht11_dat[4] == (dht11_dat[0] + dht11_dat[1] + dht11_dat[2] + dht11_dat[3] & 0xFF);
    }

    public static void main(final String ars[]) throws Exception {

        final DHT11 dht = new DHT11();

        for (int i = 0; i < 10; i++) {
            Thread.sleep(2000);
            dht.getTemperature(21);
        }

        System.out.println("Done!!");

    }
}


回答2:

If you're always getting a High State it might be good to double check if the wiring is correct (or if any of the wires are broken, test it with a led).

I've used adafruit's tutorial in C and python and it worked on my DHT22.



回答3:

I've the same issue and, unfortunately, I've read that Java cannot read data from DHT11/22 in this way for timing problems.

I've found in the Raspberry Forum a thread where you can find some solutions using SPI or pigpio. Another full Java possible solution is there.

I've received my sensor yesterday and I have not already tried this solutions. When I'll try, I'll let you know.

[EDIT]

Hi, I've solved the problem calling a python script (which uses the Adafruit Driver) and reading it's output. The python script is simply the example published in the Adafruit's library. I've only changed the output at line 48 in

print '{0:0.1f}   {1:0.1f}'.format(temperature, humidity)

The Java method that updates the values with new values is:

public void update() {
    String cmd = "sudo python DHTReader.py 11 4";
    try {
        String ret = "";
        try {
            String line;
            Process p = Runtime.getRuntime().exec(cmd.split(" "));
            p.waitFor();
            BufferedReader input = new BufferedReader
                    (new InputStreamReader(p.getInputStream()));
            while ((line = input.readLine()) != null) {
                output += (line + '\n');
            }
            input.close();
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
        ret.trim();
        if (ret.length() == 0) // Library is not present
            throw new RuntimeException(LIB_NOT_PRESENT_MESSAGE);
        else{
            // Error reading the the sensor, maybe is not connected. 
            if(ret.contains(ERROR_READING)){
                String msg = String.format(ERROR_READING_MSG,toString());
                throw new Exception(msg);
            }
            else{
                // Read completed. Parse and update the values
                String[] vals = ret.split("   ");
                float t = Float.parseFloat(vals[0].trim());
                float h = Float.parseFloat(vals[1].trim());
                if( (t != lastTemp) || (h != lastHum) ){
                    lastUpdate = new Date();
                    lastTemp = t;
                    lastHum = h;
                }
            }
        }
    } catch (Exception e) {
        System.out.println(e.getMessage());
        if( e instanceof RuntimeException)
            System.exit(-1);
    }
}

To make it work you have to install the Adafruit library as described in the linked page and change DHTReader.py with the path of the scipt. I'm working to build a "library". If you need, when I've finished, I'll publish it on GitHub.



回答4:

I got a solution with Java Native Interface JNI and WiringPi.

I am using java openjdk 7 in the raspberry pi. This is important to for support JNI capabilites of the JVM. I connected the DHT11 sensor to GPIO1 or pin 1.

From the package root at src/main/java you can install the library. I prepared a script in that you can run with the command:

sudo sh jniDHT11SensorReaderBuilder.sh

Then to test if it works try to run the DHT11SensorReader class with the command

sudo java org.mandfer.dht11.DHT11SensorReader

If it is all fine and you want more values every 1.5 seconds try to run the exercise 20 from the project root folder.

sh runPi.sh org.mandfer.sunfunpi4j.Ex20_DHT11_Native

If you have any issue, leave me a comment.

I hope it helps. Marc Andreu,



回答5:

Eric Smith's excellent code works fine for me with two small mods, First I edited this line:

if (counter > 16)

To:

if (counter > 30)

According to specs of dht11, the "1" bit is transmitted when the delay of the "Gpio.HIGH" is about 70us, and "0" bit is transmitted if the delay is 26-28us. It is clear that Java takes some time to execute, so it is safe to assume that if the delay is more than 30us the data must be "1". But this might be different value if the execution time of Java program is different in your machine (perhaps the processor of pi is faster/slower, there is more background programs etc). Therefore, the 30 is not necessarily the right value for every situation.

According to specs 1, it can be also noted that sender (raspberry pi, called MCU in the specs), should also send Gpio.HIGH for at least 18ms. After that, the MCU should send 20-40 us "Gpio.HIGH". I tested with System.nanoTime() how much time it takes for the Java to execute setting the Gpio.Pinmode to the "Input". It took something like 20us, so I added:

Gpio.delayMicroseconds(7);

...just to be sure that the Pin is HIGH for at least 20us so that the Sensor can register that signal and start sending its temperature and humidity data. After these changes, the temperature data is read almost always right, the success rate is something like 90%. Im not sure can the modifications work with another system, but hopefully these kind of modifications can make other experiments more successful!

(p.s. I also made the eternal loop so that the class is created everytime over and over again when the method is called.)



回答6:

I found that the RPi3b loaded with Raspian was too slow to use the code examples shown here already. Probably something to do with a java>pi4j>wiringpi propagation delay. My approach was as follows; after sending the command to activate the sensor, I read and time level changes on the required pin and save the values into an array. Then the parsing is done later. I get a 95% success rate with this code. I have it running in a Runnable class with a loop, so it has its own thread. If you find your timings are not quite right, try adjusting the counter offset. Also enable the println marked for debugging, it helps indicate which bits were not received (indicated by a 0).

public void scopeSensor(int pin){

    int x = 0;
    int lastState = 1;
    int valueRead = 1;
    int counter = 0; 
    int limit = 84;
    int timeout = 0;
    int[] results = new int[limit];    
    int[] pinState = new int[limit]; 


    //set pin low for 18ms to request data        
    Gpio.pinMode(pin, Gpio.OUTPUT);
    Gpio.digitalWrite(pin, Gpio.LOW);
    Gpio.delay(18);        

    //get ready to recieve data back from dht11
    Gpio.pinMode(pin, Gpio.INPUT);
    Gpio.pullUpDnControl(pin, Gpio.PUD_UP); //activate internal pullup



    while (x < limit) //84 sample changes to cover DHT11
    {           
        timeout = 0;
        counter = 2; //offset for time taken to perform read by pi
        while (valueRead == lastState && timeout < 300){
             Gpio.delayMicroseconds(1); 
            valueRead = Gpio.digitalRead(pin);

                    counter++;  
                    timeout++;
        }         

        if (timeout < 300)
        {
        results[x] = counter;
        pinState[x] = lastState;
        lastState = valueRead;
        }

        x++;
    }

    //reset our bytes
    dht11_dat[0] = dht11_dat[1] =dht11_dat[2]=dht11_dat[3]=dht11_dat[4]=0;
    int pointer = 0;
    for (int i = 4; i<x; i=i+2){
        //shift left so we are ready for next result

            pointer = ((i-4) / 2) / 8;

            dht11_dat[pointer] = dht11_dat[pointer] <<= 1;

            //if more than 30, mark bit as 1
        if (results[i] > 30){
            dht11_dat[pointer] = dht11_dat[pointer] |= 1;
        }        

    //for debugging only
       // System.out.println(Integer.toString(pinState[i]) + "," + Integer.toString(results[i]));           

    }

    int checksumByte = ((dht11_dat[0] + dht11_dat[1] + dht11_dat[2] + dht11_dat[3]) & 0xff);
    if (dht11_dat[4]  != checksumByte){
        System.out.println("Warning: Bad checksum value!");
    }


        System.out.println("                                                                    Temp: " +  Integer.toString((dht11_dat[2])) + "  RH: " +  Integer.toString((dht11_dat[0])));

         WriteToFile.writeTextToFile("RH-T.csv", Integer.toString((dht11_dat[0])) + "," + Integer.toString((dht11_dat[2])));
}

the run method:

@Override
public void run() {
    ReadTempRH dht = new ReadTempRH();

    while (NbSerialApp.runThreads){
        try {
            Thread.sleep(2000);
        } catch (InterruptedException ex) {
            Logger.getLogger(ReadTempRH.class.getName()).log(Level.SEVERE, null, ex);
        }
        //getTempRH(7);
        scopeSensor(7);
    }
}

ReadTempRH constructor:

private final int[] dht11_dat = {0,0,0,0,0};

public ReadTempRH() {

    //setup wiringPi
    if (Gpio.wiringPiSetup() == -1){
        System.out.println("GPIO setup failed!");
        return;            
    }

    GpioUtil.export(3, GpioUtil.DIRECTION_OUT);
    System.out.println("GPIO setup complete!");

}

Sorry my code is a little messy, I haven't had time to tidy things up! But you should get the idea. I am normally a c# guy and Netbeans doesn't work like VS in the tidying up front!